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

图片[1]-How to make money with Java and C++ : IEEE 754 and the real life : ordering zeros. - 拾光赋-拾光赋
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

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

图片[2]-How to make money with Java and C++ : IEEE 754 and the real life : ordering zeros. - 拾光赋-拾光赋
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

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

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

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

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

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!

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

© 版权声明
THE END
喜欢就支持一下吧
点赞8 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容