diff --git a/lib/flt/bin_num.rb b/lib/flt/bin_num.rb index 4675239..61f5858 100644 --- a/lib/flt/bin_num.rb +++ b/lib/flt/bin_num.rb @@ -206,7 +206,7 @@ def number_of_digits # Specific to_f conversion TODO: check if it represents an optimization if Float::RADIX==2 def to_f - if special? + if special? || zero? super else ::Math.ldexp(@sign*@coeff, @exp) diff --git a/lib/flt/num.rb b/lib/flt/num.rb index 171214d..227c664 100644 --- a/lib/flt/num.rb +++ b/lib/flt/num.rb @@ -2703,10 +2703,24 @@ def to_f else 0.0/0.0 end + elsif zero? + @sign*0.0 else - # to_rational.to_f - # to_s.to_f - (@sign*@coeff*(num_class.radix.to_f**@exp)).to_f + f = nil + f ||= to_s.to_f if num_class.radix == 10 # very precise, but slow + unless f + c = @coeff.to_f + if c.finite? + # This is fast to compute but may introduce roundoff error, and overflow or yield NaN + f = if @exp >= 0 + @sign*c*(num_class.radix.to_f**@exp) + else + @sign*c/(num_class.radix.to_f**(-@exp)) + end + end + f = nil unless f && f.finite? + end + f ||= to_r.to_f # not so slow, also may introduce roundoff error due to arithmetic end end diff --git a/test/test_to_rf.rb b/test/test_to_rf.rb index b574c52..46003a5 100644 --- a/test/test_to_rf.rb +++ b/test/test_to_rf.rb @@ -31,6 +31,131 @@ def test_to_f assert d.to_f.is_a?(Float) assert_equal f, d.to_f end + assert DecNum.nan.to_f.nan? + assert DecNum.infinity.to_f.infinite? + assert DecNum.infinity.to_f > 0 + assert DecNum.infinity(-1).to_f.infinite? + assert DecNum.infinity(-1).to_f < 0 + assert 1.0/DecNum('-0').to_f < 0 + assert 1.0/DecNum('+0').to_f > 0 + + data = [] + [10, 100, 1000, 10000].each do |n_digits| + data << DecNum('+0.'+'1'*n_digits) + data << DecNum('-0.'+'1'*n_digits) + end + + srand 1023022 + data = [] + DecNum.context(:precision=>15, :elimit=>90) do + data += [DecNum('1.448997445238699'), DecNum('1E23'),DecNum('-6.22320623338259E+16'), + DecNum('-3.83501075447972E-10'), DecNum('1.448997445238699')] + data += %w{ + 1E23 + 1.448997445238699 + -6.22320623338259E+16 + -3.83501075447972E-10 + 1.448997445238699 + 1.23E-30 + 1.23456789E-20 + 1.23456789E-30 + 1.234567890123456789 + 0.9999999999999995559107901499 + 0.9999999999999996114219413812 + 0.9999999999999996669330926125 + 0.9999999999999997224442438437 + 0.9999999999999997779553950750 + 0.9999999999999998334665463062 + 0.9999999999999998889776975375 + 0.9999999999999999444888487687 + 1 + 1.000000000000000111022302463 + 1.000000000000000222044604925 + 1.000000000000000333066907388 + 1.000000000000000444089209850 + 1.000000000000000555111512313 + 1.000000000000000666133814775 + 1.000000000000000777156117238 + 1.000000000000000888178419700 + }.map{ |num| DecNum(num) } + data += Array.new(10000){random_num(DecNum)} + end + + assert_equal 2, Float::RADIX + + data.each do |x| + expected = Flt::Num.convert_exact(x, 2, Flt::BinNum::FloatContext).to_f + assert_equal expected, x.to_s.to_f + + relative_error_limit = Float::EPSILON + rel_err = (x.to_f - expected).abs/expected.abs + assert expected.abs*relative_error_limit > (x.to_f - expected).abs, "#{x.to_f} != #{expected} (#{x})" + end end + def test_to_f_bin + BinNum.context(BinNum::FloatContext) do + ['0.1', '-0.1', '0.0', '1234567.1234567', '-1234567.1234567', '1.234E7', '1.234E-7'].each do |n| + f = Float(n) + b = BinNum(n, :fixed) + assert b.to_f.is_a?(Float) + assert_equal f, b.to_f + end + assert BinNum.nan.to_f.nan? + assert BinNum.infinity.to_f.infinite? + assert BinNum.infinity.to_f > 0 + assert BinNum.infinity(-1).to_f.infinite? + assert BinNum.infinity(-1).to_f < 0 + assert 1.0/BinNum('-0', :fixed).to_f < 0 + assert 1.0/BinNum('+0', :fixed).to_f > 0 + + data = [] + [10, 100, 1000, 10000].each do |n_digits| + data << BinNum('+0.'+'1'*n_digits) + data << BinNum('-0.'+'1'*n_digits) + end + + srand 1023022 + data = [] + data += [BinNum('1.448997445238699', :fixed), BinNum('1E23', :fixed),BinNum('-6.22320623338259E+16', :fixed), + BinNum('-3.83501075447972E-10', :fixed), BinNum('1.448997445238699', :fixed)] + data += %w{ + 1E23 + 1.448997445238699 + -6.22320623338259E+16 + -3.83501075447972E-10 + 1.448997445238699 + 1.23E-30 + 1.23456789E-20 + 1.23456789E-30 + 1.234567890123456789 + 0.9999999999999995559107901499 + 0.9999999999999996114219413812 + 0.9999999999999996669330926125 + 0.9999999999999997224442438437 + 0.9999999999999997779553950750 + 0.9999999999999998334665463062 + 0.9999999999999998889776975375 + 0.9999999999999999444888487687 + 1 + 1.000000000000000111022302463 + 1.000000000000000222044604925 + 1.000000000000000333066907388 + 1.000000000000000444089209850 + 1.000000000000000555111512313 + 1.000000000000000666133814775 + 1.000000000000000777156117238 + 1.000000000000000888178419700 + }.map{ |num| BinNum(num, :fixed) } + data += Array.new(10000){random_num(BinNum)} + + data.each do |x| + expected = x.to_decimal_exact(exact: true).to_s.to_f + assert_equal expected, x.to_f + end + end + + end + + end \ No newline at end of file