Ruby: Converting from Float to Integer in Ruby Produces Strange Results

ruby: converting from float to integer in ruby produces strange results

Actually, all of this make sense.

Because 0.8 cannot be represented exactly by any series of 1 / 2 ** x for various x, it must be represented approximately, and it happens that this is slightly less than 10015.8.

So, when you just print it, it is rounded reasonably.

When you convert it to an integer without adding 0.5, it truncates .79999999... to .7

When you type in 10001580.0, well, that has an exact representation in all formats, including float and double. So you don't see the truncation of a value ever so slightly less than the next integral step.

Floating point is not inaccurate, it just has limitations on what can be represented. Yes, FP is perfectly accurate but cannot necessarily represent every number we can easily type in using base 10. (Update/clarification: well, ironically, it can represent exactly every integer, because every integer has a 2 ** x composition, but "every fraction" is another story. Only certain decimal fractions can be exactly composed using a 1/2**x series.)

In fact, JavaScript implementations use floating point storage and arithmetic for all numeric values. This is because FP hardware produces exact results for integers, so this got the JS guys 52-bit math using existing hardware on (at the time) almost-entirely 32-bit machines.

Addition error with ruby-1.9.2

You need bigdecimal for this to make work.


(BigDecimal('0.1') + BigDecimal("0.2")).to_f

See below link:

http://redmine.ruby-lang.org/issues/4394

Ruby number precision with simple arithmetic

You should read up on floating point representation in computers. This guide is a good place to start. Here's the short of it:

Because internally, computers use a
format (binary floating-point) that
cannot accurately represent a number
like 0.1, 0.2 or 0.3 at all.

When the code is compiled or
interpreted, your “0.1” is already
rounded to the nearest number in that
format, which results in a small
rounding error even before the
calculation happens.

By the way, I'm not sure why you think 4.21 + 5 = 9.21 or 4.23 + 5 = 9.23 is weird. If you think it's weird because one literal is an integer and one is a float, which ends up as a float response, that's how Ruby and some other languages handle differences in number types. It wouldn't be handy if Ruby dropped your float's precision and gave you just an integer back (9), so any integer literals are effectively changed to floats.

As for using floating-point numbers in a financial application, check out Developing financial application. The main takeaway is use the Decimal object vs. Float.

Strange result for Float

As is well known, a floating number in computer has errors. Notice that both numbers that you have are close to 1.825. In your first case, the number is small enough to be differentiated from 1.825.

1.824999999999999
# => 1.824999999999999

In your second case, having enough 9s makes the value close enough to be considered exactly as 1.825:

1.82499999999999999
# => 1.825

Then, when you apply round, you get 1.83.

ruby floats add up to different values depending on the order

Floating point operations are inexact: they round the result to nearest representable float value.

That means that each float operation is:

float(a op b) = mathematical(a op b) + rounding-error( a op b )

As suggested by above equation, the rounding error depends on operands a & b.

Thus, if you perform operations in different order,

float(float( a op b) op c) != float(a op (b op c))

In other words, floating point operations are not associative.

They are commutative though...

As other said, transforming a decimal representation 0.1 (that is 1/10) into a base 2 representation (that is 1/16 + 1/64 + ... ) would lead to an infinite serie of digits. So float(0.1) is not equal to 1/10 exactly, it also has a rounding-error and it leads to a long serie of binary digits, which explains that following operations have a non null rounding-error (mathematical result is not representable in floating point)

Why is Ruby's Float#round behavior different than Python's?

Summary

Both implementations are confront the same issues surrounding binary floating point numbers.

Ruby operates directly on the floating point number with simple operations (multiply by a power of ten, adjust, and truncate).

Python converts the binary floating point number to a string using David Gay's sophisticated algorithm that yields the shortest decimal representation that is exactly equal to the binary floating point number. This does not do any additional rounding, it is an exact conversion to a string.

With the shortest string representation in-hand, Python rounds to the appropriate number of decimal places using exact string operations. The goal of the float-to-string conversion is to attempt to "undo" some of the binary floating point representation error (i.e. if you enter 6.6, Python rounds on the 6.6 rather that 6.5999999999999996.

In addition, Ruby differs from some versions of Python in rounding modes: round-away-from-zero versus round-half-even.

Detail

Ruby doesn't cheat. It starts with plain old binary float point numbers the same a Python does. Accordingly, it is subject to some of the same challenges (such 3.35 being represented at slightly more than 3.35 and 4.35 being represented as slightly less than 4.35):

>>> Decimal.from_float(3.35)
Decimal('3.350000000000000088817841970012523233890533447265625')
>>> Decimal.from_float(4.35)
Decimal('4.3499999999999996447286321199499070644378662109375')

The best way to see the implementation differences is to look at the underlying source code:

Here's a link to the Ruby source code: https://github.com/ruby/ruby/blob/trunk/numeric.c#L1587

The Python source is starts here: http://hg.python.org/cpython/file/37352a3ccd54/Python/bltinmodule.c
and finishes here: http://hg.python.org/cpython/file/37352a3ccd54/Objects/floatobject.c#l1080

The latter has an extensive comment that reveals the differences between the two implementations:

The basic idea is very simple: convert and round the double to a
decimal string using _Py_dg_dtoa, then convert that decimal string
back to a double with _Py_dg_strtod. There's one minor difficulty:
Python 2.x expects round to do round-half-away-from-zero, while
_Py_dg_dtoa does round-half-to-even. So we need some way to detect and correct the halfway cases.

Detection: a halfway value has the form k * 0.5 * 10**-ndigits for
some odd integer k. Or in other words, a rational number x is exactly
halfway between two multiples of 10**-ndigits if its 2-valuation is
exactly -ndigits-1 and its 5-valuation is at least
-ndigits. For ndigits >= 0 the latter condition is automatically satisfied for a binary float x, since any such float has nonnegative
5-valuation. For 0 > ndigits >= -22, x needs to be an integral
multiple of 5**-ndigits; we can check this using fmod. For -22 >
ndigits, there are no halfway cases: 5**23 takes 54 bits to represent
exactly, so any odd multiple of 0.5 * 10**n for n >= 23 takes at least
54 bits of precision to represent exactly.

Correction: a simple strategy for dealing with halfway cases is to
(for the halfway cases only) call _Py_dg_dtoa with an argument of
ndigits+1 instead of ndigits (thus doing an exact conversion to
decimal), round the resulting string manually, and then convert back
using _Py_dg_strtod.

In short, Python 2.7 goes to great lengths to accurately follow a round-away-from-zero rule.

In Python 3.3, it goes to equally great length to accurately follow a round-to-even rule.

Here's a little additional detail on the _Py_dg_dtoa function. Python calls the float to string function because it implements an algorithm that gives the shortest possible string representation among equal alternatives. In Python 2.6, for example, the number 1.1 shows up as 1.1000000000000001, but in Python 2.7 and later, it is simply 1.1. David Gay's sophisticated dtoa.c algorithm gives "the-result-that-people-expect" without forgoing accuracy.

That string conversion algorithm tends to make-up for some of the issues that plague any implementation of round() on binary floating point numbers (i.e. it less rounding of 4.35 start with 4.35 instead of 4.3499999999999996447286321199499070644378662109375).

That and the rounding mode (round-half-even vs round-away-from-zero) are the essential differences between the Python and Ruby round() functions.

Ruby Float problems

All numbers are ordered, but a String cannot do ordered comparisons against numbers, only equality comparisons to see if they are the same object. (A comparison that will always return false, obviously.)

I imagine this is the type of error you are seeing, something like: ArgumentError: comparison of String with Float failed.

The issue with low-order bits in Float objects could possibly interfere with your sort, though raising an actual exception seems highly unlikely. Like most decimal string fractions, 0.07 does not have an exact representation in the underlying Float format. It is conceivable that different ways of getting to 0.07 (literal constant vs. result of calculation) could result in a every-so-slightly different value.

But also be aware that if you attempt to print out more fraction digits than the quantity really has, you will see various remainders of the last bits that were there. These digits can be non-zero but they don't tell you anything useful about your number.

The safest thing to do is to scale to integral values, call #to_i, and compare the integers.

Floating point that make the calculation inaccurate

To preserve the accuracy of calculations use BigDecimal instead of Float:

 scores = params[:scores].split("\r\n").map { |n| BigDecimal(n) }

BigDecimal provides support for very large or very accurate
floating point numbers.

Decimal arithmetic is also useful for general calculation, because it
provides the correct answers people expect–whereas normal binary
floating point arithmetic often introduces subtle errors because of
the conversion between base 10 and base 2.



Related Topics



Leave a reply



Submit