How does your programming language handle “minus zero” (-0.0)?

The ubiquitous IEEE floating-point standard defines two numbers to represent zero, the positive and the negative zeros. You also have the positive and negative infinity. If you compute the inverse of the positive zero, you get the positive infinity. If you compute the inverse of the negative zero, you get the negative infinity.

It seems that programming languages handle these values in vastly different ways.

The Python language it throws an exception when dividing by zero.

JavaScript prints what I expect (-Infinity, Infinity, – infinity):

console.log(1/-0.0)
console.log(1/0.0)
console.log(1/Number.parseFloat("-0.0"))

Though the C++ standard probably says very little about negative zeros, in practice I am getting the results I would expect. In C++ (GNU GCC and LLVM clang), the following code prints out (-inf, inf, -inf) :

double minus_zero = -0.0;
double plus_zero = +0.0;
double parsed = strtod("-0.0", nullptr);
std::cout <<  1.0/minus_zero << std::endl;
std::cout <<  1.0/plus_zero << std::endl;
std::cout <<  1.0/parsed << std::endl;

Java also produces what I expect (-Infinity, Infinity, -Infinity) from the following code sample:

double minus_zero = -0.0;
double plus_zero = +0.0;
double parsed = Double.valueOf("-0.0");
System.out.println(1/minus_zero);
System.out.println(1/plus_zero);
System.out.println(1/parsed);

Swift also seems to do what I expect (-inf, inf, -inf) :

var minus_zero = -0.0
var plus_zero = +0.0
var parsed = Double("-0.0")!
print(1/minus_zero)
print(1/plus_zero)
print(1/parsed)

The Go programming language seems to be unaware of negative zeros when parsing literal constants. Thus the following code will print out +Inf, +Inf.

var minus_zero = -0.0
var plus_zero = 0.0
fmt.Println(1/minus_zero)
fmt.Println(1/plus_zero)

C# is aware of negative infinity, but when parsing strings the behaviour depends on your version. With .NET 5, the following code prints out what I expect (-infinity, infinity, -infinity) but with previous versions you may get -infinity, infinity, infinity.

double minus_zero = -0.0;
double plus_zero = +0.0;
double parsed = Double.Parse("-0.0");
Console.WriteLine(1/minus_zero);
Console.WriteLine(1/plus_zero);
Console.WriteLine(1/parsed);

Daniel Lemire, "How does your programming language handle “minus zero” (-0.0)?," in Daniel Lemire's blog, March 4, 2021, https://lemire.me/blog/2021/03/04/how-does-your-programming-language-handle-minus-zero-0-0/.

Published by

Daniel Lemire

A computer science professor at the University of Quebec (TELUQ).

22 thoughts on “How does your programming language handle “minus zero” (-0.0)?”

  1. FWIW Rust also produces what you would expect:

    fn main() {
    let minus_zero = -0.0;
    let plus_zero = 0.0;
    let parsed = "-0.0".parse::<f64>().unwrap();
    println!("{}", 1.0/minus_zero);
    println!("{}", 1.0/plus_zero);
    println!("{}", 1.0/parsed);
    }

    Output:

    -inf
    inf
    -inf

  2. JavaScript:

    const plusZero = 0;
    const minusZero = -0;
    const parsedMinus = parseInt("-0");
    console.log({
    minusZero: 1/minusZero, // -Infinity
    plusZero: 1/plusZero, // Infinity
    parsedMinus: 1/parsedMinus // -Infinity
    });

  3. Depending on the compiler and the optimisation option used, your test program measures compile time or runtime behaviour — which can but differ.
    For C and C++, you should but declare the doubles as volatile and feed a volatile char xxx[] = “-0.0”; to strtod()

    1. Are you aware of a compiler where the result of strtod would be computed at compile time?

      Specifically, a compiler where the following function would become trivial…

      bool touch() {
          return strtod("-0.0", nullptr) == 0;
      }
      

      I’d be very interested in knowing about such a system.

      1. Evaluation of the expression strtod(constant, NULL) during compile time is a legitimate optimisation any C/C++ compiler is allowed to perform. There are C/C++ compilers which evaluate for example signbit(constant), log(constant), strlen(constant) or strcmp(constant, constant) etc., so why shouldn’t they not (be able to) evaluate strtod(constant, NULL) or strtol(constant, NULL, 0) too? The compiler already has the parser for the numbers, these library functions are part of the language and their semantics well-defined! Unless you can prove or safely assume that no compiler will ever optimize these expressions it’s better to exercise defensive programming.

        1. I do not doubt that in all the examples provided in my blog post, an optimizing compiler is allowed to just memoize the output. Of course, it should ensure that the result is undistinguishable from what would happen if you were to run it without optimization. But I have never observe an optimizing compiler that would optimize away strtod and I’d be very interested in knowing about such a scenario.

          More to the point of your reply… Is your expectation that by marking the string as volatile, thus precluding some optimizations, we will get different results ?

          1. I still remember your blog post https://lemire.me/blog/2020/06/26/gcc-not-nearest/

            The interpretation of floating point numbers during compile time may differ from run-time.
            I just fed the following snippet to the (ancient) GCC on your EPYC system “Rome” and to LLVM 10.0:

            int main()
            {
            double minus_0 = -0.0;
            double plus_0 = +0.0;
            return 1.0/minus_0 == 1.0/plus_0;
            }

            gcc -O3 -o- -s demo.c

            main:
            movsd .LC0(%rip), %xmm0
            xorl %eax, %eax
            movl $0, %edx
            movapd %xmm0, %xmm1
            divsd .LC2(%rip), %xmm0
            divsd .LC1(%rip), %xmm1
            ucomisd %xmm0, %xmm1
            setnp %al
            cmovne %edx, %eax
            ret
            .align 8

            .LC0:
            .long 0
            .long 1072693248
            .align 8
            .LC1:
            .long 0
            .long -2147483648
            .align 8
            .LC2:
            .long 0
            .long 0
            .ident “GCC: (GNU) 8.3.1 20190311 (Red Hat 8.3.1-3)”

            clang -O3 -o- -s demo.c

            main: # @main
            xorl %eax, %eax
            retq
            .ident "AMD clang version 10.0.0 (CLANG: AOCC_2.2.0-Build#93 2020_06_25) (based on LLVM Mirror.Version.10.0.0)"

            You see the difference?

          2. To answer both of your questions:
            1. no, I don’t know a compiler which optimises strtod(“-0-0”, 0) and evaluates it at compile time (but some which optimise strcmp(constant, constant) for example);
            2. I expect that a volatile char zero[] = “-0.0”; strtod(zero, 0); should disable that optimisation — both GCC and clang but bail out with a warning “passing ‘volatile char *’ to parameter of type ‘const char *’ discards qualifiers”

        1. AFAIK the D language/compiler sports a similar feature and allows to evaluate code during compilation.

  4. fn main() {
    let minus = -0.0;
    let plus = 0.0;

    println!("{}", 1.0/minus);
    println!("{}", 1.0/plus);

    }

    $ rustc minus.rs
    $ ./minus
    -inf
    inf

  5. Same behaviour with D as well.

    void main() {
    import std.stdio, std.conv;
    double minus_zero = -0.0;
    double plus_zero = +0.0;
    double parsed = to!double("-0.0");

    writeln(1/minus_zero);
    writeln(1/plus_zero);
    writeln(1/parsed);
    }

    $ ldc2 a.d
    $ ./a
    -inf
    inf
    -inf
    $

  6. Related to https://github.com/golang/go/issues/30951 : I assume in most programming languages the equivalent of “double x = -0” is the same as “double x = 0”. Meaning: -0 is interpreted as an integer (and, so, 0, as there is no negative 0 integer), which is then converted to a double.

  7. Negative zeros are one of the issues that we have to handle in our bit reproducibility tests. When we are unable to justify a sign, we will do something like x = x + 0.0 which converts -0.0 into +0.0. And I think that this is the correct behavior via IEEE754. But I also don’t know if we can count on all platforms to do this.

  8. I just tried it in Ada.

    with Ada.Float_Text_IO;
    use Ada.Float_Text_IO;

    procedure Main is
    minus_zero : Float := -0.0;
    plus_zero : Float := +0.0;
    parsed : Float := Float'Value("-0.0");
    begin
    Put(1.0/minus_zero);
    Put(1.0/plus_zero);
    Put(1.0/parsed);
    end Main;

    Gave me “-Inf”, “+Inf”, “-Inf”

    1. Isn’t division by zero undefined?

      It is in the sense that it is not part of the real numbers, but here we are defining an extended set which includes -infinity and +infinity.

      Whether that’s wise to do so is another story.

Leave a Reply

You may subscribe to this blog by email.