DEV Community

Cover image for How to make money with Java and C++ : IEEE 754 and the real life : ordering zeros.
hexfloor
hexfloor

Posted on • Edited on

How to make money with Java and C++ : IEEE 754 and the real life : ordering zeros.

TL;DR => use BigDecimal for Java and Boost.Multiprecision or std::decimal for C++ to handle money, detailed explanation with examples in the next article

Now that we have a spoiler and the problem is solved, let's see why we cannot just use double without trouble.
The word itself is a bit oldish, for zoomers an existence of non 64 bit processors should sound like a floppy disk.
Here and after Java stands for Java 17 and C++ stands for C++ 20, if not specified differently.
Let's start from basics, and here is IEEE 754, this is a technical standard for the floating-point arithmetic's, several concepts are to be mentioned : infinity, NaN, signed zero : -0, +0.
Both Java and C++ implement IEEE 754 for float and double types, it has been a bit of a challenge for Java regarding the double rounding problem, see here: strictfp.
The first surprise arises when an engineer sees that there are -0 and 0 and they are equal, are they ? They are equal as per IEEE 754, so, if I sort an array in Java and C++, am I going to get -0 and 0 mixed ?
And the answer is different for both languages, let's see.

IEEE 754 : signed zero in C++

Check the three-way comparison.
Here is an example :

#include <compare>
#include <iostream>

int main()
{
    double foo = -0.0;
    double bar = 0.0;

    std::cout<< (foo==bar ? "-0 and 0 are equal" : "-0 and 0 are NOT equal" ) 
<<std::endl;

    auto res = foo <=> bar;
    if (res < 0)
        std::cout << "-0 is less than 0";
    else if (res > 0)
        std::cout << "-0 is greater than 0";
    else // (res == 0)
        std::cout << "-0 and 0 are equal";
}
Enter fullscreen mode Exit fullscreen mode

Image description
And the consequence :

#include <iostream>
#include <iterator>
#include <bits/stdc++.h>


int main()
{
    auto show_me_the_money = [](auto& arr) {
        std::copy(std::begin(arr), std::end(arr), 
            std::ostream_iterator<double>(std::cout, " "));
        std::cout<<std::endl;
    };

    double arr[] = {1.0, -0.0, 0.0, -0.0, -1.0};
    show_me_the_money(arr);
    std::sort(arr, arr + sizeof(arr)/sizeof(arr[0]), std::less<double>());
    show_me_the_money(arr);
}
Enter fullscreen mode Exit fullscreen mode

Image description

Let me show you few tricks on how can we make precede the -0 before 0.
If C++ had been taught in Hogwarts it would have been taught by Severus Snape, as you can cast pretty much everything. Here are different ways to get a sign of a zero :

#include <iostream>
#include<math.h>  
union u {
    double d;
    uint64_t i;
};
int main()
{
    auto cast = [](auto& d) {
        uint64_t i = *reinterpret_cast<uint64_t *>(&d);
        return i;
    };
    auto unite = [](auto& d) {
        u _u;
        _u.d =d;
        return _u.i;
    };
    double foo = -0.0;
    double bar = 0.0;
    std::cout<<cast(foo)<<" "<<cast(bar)<<std::endl;
    std::cout<<unite(foo)<<" "<<unite(bar)<<std::endl;
    std::cout<<signbit(foo)<<" "<<signbit(bar)<<std::endl;
}
Enter fullscreen mode Exit fullscreen mode

Image description
I'll use the signbit function :

#include <iostream>
#include <iterator>
#include <bits/stdc++.h>
#include<math.h>  

int main()
{
    auto show_me_the_money = [](auto& arr) {
        std::copy(std::begin(arr), std::end(arr), 
            std::ostream_iterator<double>(std::cout, " "));
        std::cout<<std::endl;
    };
    std::less<double> less_double;
    auto less_zero = [&]( const auto& lhs, const auto& rhs ){
        auto res = less_double(lhs, rhs);
        return res != 0 ? res : signbit (lhs) > signbit(rhs);
    };

    double arr[] = {1.0, -0.0, 0.0, -0.0, -1.0};
    show_me_the_money(arr);
    std::sort(arr, arr + sizeof(arr)/sizeof(arr[0]), less_zero);
    show_me_the_money(arr);
}
Enter fullscreen mode Exit fullscreen mode

Image description

With a custom comparator I can order -0 and 0. The question is, how can we convert -0 to 0 ?
The answer is pretty simple : you have to add 0, let me show you :

#include <iostream>
#include <iterator>
#include <bits/stdc++.h>


int main()
{
    auto show_me_the_money = [](auto& arr) {
        std::copy(std::begin(arr), std::end(arr), 
            std::ostream_iterator<double>(std::cout, " "));
        std::cout<<std::endl;
    };

    double arr[] = {1.0, -0.0, 0.0, -0.0, -1.0};
    show_me_the_money(arr);
    std::for_each(std::begin(arr), std::end(arr), [](double& d) { d+=0.0;});
    std::sort(arr, arr + sizeof(arr)/sizeof(arr[0]), std::less<double>());
    show_me_the_money(arr);
}
Enter fullscreen mode Exit fullscreen mode

Image description

Alright, we have managed to handle zeros in C++.

IEEE 754 : signed zero in Java

Things in Java world looks simpler, see the source code of Double.compare method for the details.
I've used w3schools Java compiler for tests :

public class Main {
    public static void main(String args[]) {
      double foo = -0.0;
      double bar = 0.0;

      System.out.println("-0 and 0 are equal " + (foo == bar));
      System.out.println("-0 and 0 are ordered " + Double.compare(foo,bar));
    }
}
Enter fullscreen mode Exit fullscreen mode

Image description

Sorting zeros in an array is straight forward :

import java.util.*;

public class Main {
    public static void main(String args[]) {
      double [] arr = {1.0, -0.0, 0.0, -0.0, -1.0};
      System.out.println(Arrays.toString(arr));
      Arrays.sort(arr);
      System.out.println(Arrays.toString(arr));
    }
}
Enter fullscreen mode Exit fullscreen mode

Image description

Same adding 0 trick applies :

import java.util.*;

public class Main {
    public static void main(String args[]) {
      double [] arr = {1.0, -0.0, 0.0, -0.0, -1.0};
      System.out.println(Arrays.toString(arr));
      arr = Arrays.stream(arr).map(v -> v+= 0.0).toArray();
      System.out.println(Arrays.toString(arr));
    }
}
Enter fullscreen mode Exit fullscreen mode

Image description

Summary

Both C++ and Java implement IEEE 754 and following the standard you have two zeros which are equal. The results of a software we do might be used elsewhere, and having values with minus sign to be mixed with values with no sign can lead to the dramatic consequences. Still neither double nor float should be used for the money handling and I'll cover the reasons in the next article. Take care!

Top comments (0)