72

In ruby why can I assign a negative sign to 0.0 float, is this feature useful in any way? Could someone explain this one to me?

-0.0
#=> -0.0

-0.0 * -1
#=> 0.0
Stefan
  • 109,145
  • 14
  • 143
  • 218
  • Comments are not for extended discussion; this conversation has been [moved to chat](https://chat.stackoverflow.com/rooms/201902/discussion-on-question-by-bruno-alexandre-moreira-pincho-why-do-we-have-0-0-and). – Samuel Liew Nov 05 '19 at 20:43
  • 1
    @ShadowRanger I'm fairly certain there's not an option to only move certain comments. – jhpratt Nov 06 '19 at 05:46

4 Answers4

105

You can assign a negative sign to a 0.0 float in Ruby because all IEEE 754 floating point numbers have a sign bit to indicate whether the number is positive or negative.

Here are the binary representations of 2.5 and -2.5:

[2.5].pack('f').unpack1('b*')
#=> "00000000000000000000010000000010"

[-2.5].pack('f').unpack1('b*')
#=> "00000000000000000000010000000011"

The last bit is the sign bit. Note that all the other bits are identical.

On the other hand, there is zero with a sign bit of 0:

['00000000000000000000000000000000'].pack('b*').unpack1('f')
#=> 0.0

and zero with a sign bit of 1:

['00000000000000000000000000000001'].pack('b*').unpack1('f')
#=> -0.0

Although 0.0 and -0.0 are numerically equal, they are not identical on the object level:

(0.0).eql?(-0.0)   #=> true
(0.0).equal?(-0.0) #=> false

Negative zeros have some special properties. For instance:

1 / 0.0    #=> Infinity
1 / -0.0   #=> -Infinity

Assigning - explicitly is not the only way to get -0.0. You may also get -0.0 as the result of a basic arithmetic operation:

-1.0 * 0 #=> -0.0
Stefan
  • 109,145
  • 14
  • 143
  • 218
  • 2
    See also the Wiki for [Signed Zero](https://en.wikipedia.org/wiki/Signed_zero#Properties_and_handling). Note surpisingly, `((-0.0)*(-0.0) #=> 0.0`). This question turned out to be more interesting than I originally thought. – Cary Swoveland Nov 04 '19 at 17:39
  • 9
    @CarySwoveland What's surprising about `(-0.0)*(-0.0) => +0.0`? It simply follows the sign rule exactly as it does in the `1/-0.0 = -inf` etc cases. The one thing that is weird is `sqrt(-0.0) = -0.0` since most people would expect only values `>= 0` for the square root... – Bakuriu Nov 04 '19 at 21:02
  • 7
    @Bakuriu They would technically be correct since `-0.0 >= 0.0` in IEEE 754 – flornquake Nov 04 '19 at 21:04
  • 17
    @Bakuriu, "Note" is old-English for "not". (Confession: I meant to write "Not suprisingly,". Excuse the typo.) – Cary Swoveland Nov 04 '19 at 21:06
  • 4
    @Bakuriu wouldn't the `sqrt(-0.0)=>0+0i` be the correct result, a complex zero? – Crowley Nov 04 '19 at 21:13
  • @flornquake Sorry, I meant a number `x` such that `1/(x-x) = +Infinity` xD – Bakuriu Nov 04 '19 at 21:14
  • 12
    @Crowley IEEE-754 specifically says that "**squareRoot** (−0) shall be −0." – LegionMammal978 Nov 04 '19 at 23:30
  • As supplemental material, this video starts with a pretty good explanation of floating point numbers: https://www.youtube.com/watch?v=5TFDG-y-EHs – Vaelus Nov 05 '19 at 01:39
  • 1
    I recently asked a question about `0.0` and `-0.0` in PHP, because perhaps surprisingly, they are treated as identical to each other. `(0.0 === -0.0) === true` in PHP. You're lucky that they're different in Ruby! – CJ Dennis Nov 05 '19 at 01:46
  • 4
    @CJDennis: `0.0` compares equal to `-0.0` according to IEEE754, even though they're distinct values. As this answer shows, Ruby has numeric comparison which finds them equal, but also object-representation or something comparison which can tell the difference. In most languages (like C/C++) the standard comparison operator finds them equal. In x86 assembly, the only way to compare and find them not equal is with a bitwise integer compare (not floating point at all, and could find NaN values equal to each other instead of unordered). – Peter Cordes Nov 05 '19 at 14:38
  • 4
    @Crowley even in real math (which IEEE754 isn't), "complex zero" is still just zero. – bug Nov 05 '19 at 15:14
  • Keep in mind that float == float is nonsensical once "normal math" has been used on the floats. That isn't a special feature of zero. – candied_orange Nov 05 '19 at 16:40
  • @candied_orange Real == Real is not sensible in physical systems as a general rule (see constructive mathematics), so floats not having a sensible == isn't all that unexpected – Yakk - Adam Nevraumont Nov 05 '19 at 18:51
  • 2
    @Yakk-AdamNevraumont 0 is a real and 0 == 0 to everyone except statisticians. float == float being nonsensical is because the way floats are stored and presented is fundamentally different, in a lossy way. Floats are 1's and 0's. They can be equal right down to the last bit. But how they get that way is a fundamentally different math. – candied_orange Nov 05 '19 at 19:43
  • @candied_orange It is true you can sometimes compare reals for equality, but you cannot compare two reals in general; there are sensible ways to construct reals (or find a real) that don't let you prove if they are equal or not to another real. But this is not a good place to talk about it. If you want to know more, go read https://www.springer.com/gp/book/9783642649059 or a similar source. TL;DR the "fuzziness" of mathematics over the Reals is fundamental to their nature. While float's "fuzziness" partly comes from another source, no remedy can exist to fully get rid of it. – Yakk - Adam Nevraumont Nov 05 '19 at 19:51
  • @Yakk-AdamNevraumont you're dragging me into a different discussion. Remember floats are not real. They're finite representations. My point is that float == float is nonsensical for reasons that have nothing to do with reals, zero, or even "rounding errors". Strings of digits with a decimal point in them do not convert 1 to 1 to the way floats are stored. So you often don't get what you expect. Use that surprise in your math and you're equality test will sometimes surprise you. – candied_orange Nov 05 '19 at 19:57
  • 1
    @PeterCordes PHP treats them identically in both cases. `0.0 == -0.0` (good), `0.0 === -0.0` (surprising). I would expect the object representation in PHP to find a difference, but it doesn't. – CJ Dennis Nov 05 '19 at 21:37
  • @Yakk-AdamNevraumont: Math isn't bound by physical laws of a physical world. It _absolutely_ makes sense to compare `sqrt(2)` to `sqrt(2)` and expect them to always be equal mathematically, regardless of any constraints in real world physical systems. – Mooing Duck Nov 05 '19 at 23:13
  • @CJDennis: I upvoted your comment before checking [what `===` is *supposed* to mean in PHP](https://www.php.net/manual/en/language.operators.comparison.php). It compares values for equality without doing type-coercion, i.e. checks that they're the same type and then compares values. Of course it considers them true. It's not a bitwise comparison or what C would call comparing the "object representation". It's neat that Ruby has that, but like I said it's not rare for languages to lack it. – Peter Cordes Nov 06 '19 at 00:04
  • @PeterCordes True, but it's still surprising that PHP treats them differently but makes it very hard to detect the difference. – CJ Dennis Nov 06 '19 at 00:08
  • @CJDennis: That's not surprising, it's how most languages are. Including C. They're supposed to be the same value but underflowed from a different direction so that makes some sense. You can do stuff like `copysign` in C to extract the sign, or of course `memcmp`, but there's no simple compare operator that considers them not equal. Hardware typically can't do it efficiently either, for FP values in registers (in assembly language). The only normal FP compare instructions use IEEE754 semantics. (Or sometimes with FP in SIMD regs you can use integer bitwise equality.) – Peter Cordes Nov 06 '19 at 00:14
  • @PeterCordes Either the difference is important and should be easily detectable, or it's unimportant and should not be detectable at all. The sign of `-0.0` is correctly `0` so it can't be detected that way. I asked a question and was given a very clever way of detecting it that I wouldn't have thought of. – CJ Dennis Nov 06 '19 at 00:18
41

Mathematical operations have real-number results, but we map those real results onto the nearest floating-point number, which is called "rounding". For every floating-point number, there is a range of real numbers that will round to that float, and sometimes it's useful to think of the float as being identified with that range of real numbers.

Since there is a finite supply of floating-point numbers, there must be a smallest positive float, and its opposite, the smallest (magnitude) negative float. But what happens to real number results even smaller than those? Well, they must "round to zero". But "a really small number greater than zero" and "a really small number less than zero" are pretty different things with pretty different mathematical behavior, so why should we lose the distinction between them, just because we're rounding? We don't have to.

So, the float 0 doesn't just include the real number 0, it also includes too-small-to-represent positive quantities. And the float -0 includes too-small-to-represent negative quantities. When you use them in arithmetic, they follow rules like "negative times positive equals negative; negative times negative equals positive". Even though we've forgotten almost everything about these numbers in the rounding process, we still haven't forgotten their sign.

hobbs
  • 223,387
  • 19
  • 210
  • 288
  • Well said, Calvin, but I have a quibble: it grates to read about "small" negative values close to zero, notwithstanding "(magnitude)". I think it would be preferable say, "...there must be a smallest positive float, and its opposite, a largest negative float. But what happens to real number results even closer to zero?" and similar small adjustments in wording in the second and third paragraphs. – Cary Swoveland Nov 04 '19 at 21:22
  • 15
    @CarySwoveland those will just cause an annoyance of equal magnitude (but opposite sign) to someone else :) – hobbs Nov 04 '19 at 21:56
  • 1
    @CarySwoveland that would be the greatest negative float, not the largest. At least in my mathematical education. – mephistolotl Nov 06 '19 at 05:40
  • 1
    @mephistolotl yeah, "least" and "greatest" point towards negative and positive infinity; "smallest" and "largest" point towards and away from zero. At least in my mind, but I think I'm being at least *somewhat* conventional here. – hobbs Nov 06 '19 at 05:49
  • @mephistolotl, my comment was motivated by mathematics. [Here](https://math.stackexchange.com/questions/131506/what-is-the-largest-negative-number-curiosity), at *math.stackexchange.com*, mathematicians address the question, "What is the largest negative number?". They conclude (what seems obvious) that one does not exist, because for every negative real number `a` there is another negative real number `b` such that `a < b < 0`. They refer to the limit zero, not negative infinity. – Cary Swoveland Nov 06 '19 at 06:21
  • @CarySwoveland I might have misunderstood your point. I, of course, agree that there is no greatest negative number. I was making the point that negative numbers near zero are small and great, while it seemed to me you were saying they are large and great. That is, in the mathematical terminology I'm familiar with, -2 is smaller than -3 and -2 is greater than -3. – mephistolotl Nov 08 '19 at 06:35
26

It's not a feature of Ruby, but the part of floating point number specification. See this answer. Negative zero is equal positive zero:

-0.0 == 0.0
# => true
mrzasa
  • 22,895
  • 11
  • 56
  • 94
2

An example of when you might need -0.0 is when working with a function, such as tangent, secant or cosecant, that has vertical poles which need to go in the right direction. You might end up dividing to get negative infinity, and you would not want to graph that as a vertical line shooting up to positive infinity. Or you might need the correct sign of a function asymptotically approaching 0 from below, like if you’ve got exponential decay of a negative number and check that it remains negative.

Davislor
  • 14,674
  • 2
  • 34
  • 49