3

In Flanagan and Matz's The Ruby Programming Language, I read this:

The Numeric classes perform simple type conversions in their == operators, so that (for example) the Fixnum 1 and the Float 1.0 compare as equal.

Given that even two Floats representing 1.0 can fail equality tests due to rounding, how can equality be guaranteed between a Fixnum and a Float? Couldn't it only be guaranteed between a Decimal and a Float?

Or is the book just being inexact because that's not a focus in the context of the chapter?


An edit, in hopes of adding clarity:

I just read that IEEE754 (floating point) can represent integers up to 224 exactly, and double up to 253. According to this question, 253+1 (9,007,199,254,740,993) is the first integer that cannot be represented exactly by double (and hence float). Then my question is, how does

9007199254­740993.0 == 90071­9925474099­3

evaluate to true? Shouldn't rounding have caused the left-hand side (not representable by double or float) to round to a value that wouldn't match the right-hand side (an exact integer)?

Community
  • 1
  • 1
Andrew Cheong
  • 29,362
  • 15
  • 90
  • 145
  • Did you try typing in `1 == 1.0` in irb and find it returns `true` like I did? – hd1 Apr 07 '13 at 03:10
  • @hd1 - Yes, but my question is _how_. Either I'm misunderstanding the pitfalls of floating point comparisons, or Ruby is doing some magic, like considering "closest possible appoximation" a passing case for the `==` operator. – Andrew Cheong Apr 07 '13 at 03:12
  • The only "magic" I see from the [source](http://www.ruby-doc.org/core-2.0/Fixnum.html) is a special method `rb_integer_float_eq` being called if one of the operators is a float. My checkout of ruby-core is being shipped right now, but if you're curious, I'd be happy to do a bit of digging once it arrives and I set things up. – hd1 Apr 07 '13 at 03:21
  • Not at all.. I'll post it as an answer in the next week or so and you can then accept. – hd1 Apr 07 '13 at 03:30
  • Comparing floats for equality seems like a bad idea and will just lead to suffering and misery in the future. – tadman Apr 07 '13 at 03:51

3 Answers3

4

As described by the source code of the function in charge of that comparison (rb_integer_float_eq), they both get promoted to double and then compared, so it will end-up being 1.0 == 1.0.

fmendez
  • 7,250
  • 5
  • 36
  • 35
2

Are you sure that expressions like 0.9 == 0.9 is not guaranteed? I don't think so. It succeeds on my machine, and even though there is rounding error, the algorithm should always map the same literal expression to the same float with the same rounding error. For example, if the expression "0.9" were to be expressed internally as 0.900001, it will always be so. It doesn't get mapped sometimes to 0.900000 and sometimes to 0.900002. So equality should be guaranteed.

Regarding comparison between Fixnum and Float, if a fixnum literal is converted to a float, it would also be mapped to the same float as if it were a float from the beginning, with the same rounding error. In other words, the following two processes end up with the same float:

  • Literal "1.0" → Some internal float with rounding error (say, 0.999999)
  • Literal "1" → Internal fixnum 1 → Some internal float with rounding error (0.999999)

Edit Or, as fmendez says, if integers are mapped internally precisely to a float, then floats that exactly correspond to an integer (like "1.0", "2.0", etc.) do not have any rounding error within the internal float expression. So equality would be guaranteed anyway.

sawa
  • 165,429
  • 45
  • 277
  • 381
  • They both get promoted to double before the comparison, so I don't think there's any loss of precision, hence it is possible to compare them. – fmendez Apr 07 '13 at 03:58
  • If they get promoted to double, they will both lose the same amount of precision with the same rounding error, hence it is possible to compare. – sawa Apr 07 '13 at 03:59
  • I was under the impression that in `C` when promoting from int or float to a double there wasn't any precision lost. My `C` knowledge is not that great so I could be totally off :S. – fmendez Apr 07 '13 at 04:03
  • If you are right, then that should mean that, when Ruby Float literals are converted to internal floats, there is zero rounding error if the literal corresponds exactly to an integer. And that would be the reason the comparison works. – sawa Apr 07 '13 at 04:07
  • @sawa - Ah, I think that's my misunderstanding. Could you help me by confirming the following? I _thought_ that the _reason_ floating point arithmetic was dangerous, was that an integer that can't be represented exactly, can be represented in two ways, _e.g._ `0.9999...9` versus `1.0000...1`, depending on how it was arrived at, _e.g._ by literal assignment or from subtraction of other numbers. (Of course, `1.0` _can_ be represented exactly, so my example isn't real.) Thus I thought the danger was that `0.9999...9` and `1.0000...1` would compare to `false`. This belief is _wrong_, correct? – Andrew Cheong Apr 07 '13 at 05:46
  • @sawa - This is a separate question. I just read that IEEE754 (floating point) can represent integers up to 2^24 exactly, and `double` up to 2^53. According to [this question](http://stackoverflow.com/questions/3793838/which-is-the-first-integer-that-an-ieee-754-float-is-incapable-of-representing-e), 2^53+1 (9,007,199,254,740,993) is the first integer that cannot be represented exactly by `double` (and hence `float`). Then my question is, how does `9007199254­740993.0 == 90071­9925474099­3` evaluate to `true`? What happens internally that that comparison works? (Am I _still_ misunderstanding?) – Andrew Cheong Apr 07 '13 at 05:51
  • Floating point arithmetic cannot guarantee equality in such cases. That is different from what is in the question, though. And `9007199254­740993.0 == 90071­9925474099­3` actually returns `false` on my machine. – sawa Apr 07 '13 at 06:12
  • 1
    @sawa - `@fmendez's answer may technically be the actual answer, but yours helped me understand the most. Thank you. – Andrew Cheong May 04 '13 at 05:14
0

In Squeak/Pharo Smalltalk, I've changed the equality/inequality comparison tests to always promote an inexact arithmetic value (floating point) to an exact arithmetic value (Integer Fraction ScaledDecimal).
See http://bugs.squeak.org/view.php?id=3374

This was done in various lisp flavours long before that.
See http://www.lispworks.com/documentation/lcl50/aug/aug-170.html

If you don't do that, you'll degrade some properties of equality tests, since 2^54+1 and 2^54-1 might then be both equal to the same float double(2^54)... Same with inequalities. You also go into all sort of troubles with Dictionary (Hashed Map) and Sets of numbers.

aka.nice
  • 9,100
  • 1
  • 28
  • 40