I have tested my code developed on a ubuntu 18.04 bionic docker image on a ubuntu 20.04 focal docker image. I saw that there were a problem with my unit test and I have narrowed the root cause to a simple main.cpp
#include <iostream>
#include <iomanip>
#include <math.h>
int main()
{
const float DEG_TO_RAD_FLOAT = float(M_PI / 180.);
float theta = 22.0f;
theta = theta * DEG_TO_RAD_FLOAT;
std::cout << std::setprecision(20) << theta << ' ' << sin(theta) << std::endl;
return 0;
}
On the bionic docker image, I have upgraded my version of g++ using the commands :
sudo apt-get install -y software-properties-common
sudo add-apt-repository ppa:ubuntu-toolchain-r/test
sudo apt install -y gcc-9 g++-9
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 90 --slave /usr/bin/g++ g++ /usr/bin/g++-9 --slave /usr/bin/gcov gcov /usr/bin/gcov-9
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-7 70 --slave /usr/bin/g++ g++ /usr/bin/g++-7 --slave /usr/bin/gcov gcov /usr/bin/gcov-7
My version of g++ are the same : 9.4.0.
On ubuntu 18.04, the program outputs : 0.38397243618965148926 0.37460657954216003418
On ubuntu 20.04, the program outputs : 0.38397243618965148926 0.37460660934448242188
As you can see the difference is on the sin(theta), on the 7th decimal.
The only difference I can think of is the version of libc which is 2.27 on the ubuntu 18.04 and 2.31 on the ubuntu 20.04.
I have tried several g++ options -mfpmath=sse, -fPIC,-ffloat-store, -msse, -msse2
but it had no effects.
The real problem is that on my Windows version of the code compiled with /fp:precise
,
I get the same results than the Ubuntu 18.04 :
0.38397243618965148926 0.37460657954216003418
Is there any way to force the g++ compiler to keep the same results as my Windows compiler please?
2
Answers
Whether or not there is any guarantee that the exact result of calls to the mathematical functions stay consistent with version changes aside, you are also relying on unspecified behavior.
Specifically, you are including
<math.h>
in a C++ program. This will makesin
from the C standard library available in the global namespace scope, but it is unspecified whether or not it will make thesin
overloads from the C++ standard library available in the global namespace scope.C’s
sin
function operates ondouble
s, while C++ adds an overload forfloat
. So it is unspecified whether you are calling the overload operating ondouble
or the one operating onfloat
. Depending on that you will get a differently rounded result.To guarantee a call to the
float
overload include<cmath>
instead and callstd::sin
instead ofsin
.Also, depending on optimization flags, GCC may not actually call the
sin
function and constant-fold the value itself. In that case the result may have a different rounding or accuracy.Well, investigating a slightly modified version of your test program:
The changes are that 1) use
cmath
andstd::sin
instead ofmath.h
, and 2) also print the hex representation of the calculated sine value. Using GCC 11.2 on Ubuntu 22.04 here.Without optimizations I get
which is the result you got on Ubuntu 20.04. With optimization enabled, however:
which is what you got on Ubuntu 18.04.
So why does it produce different results depending on optimization level? Investigating the generated assembler code gives a clue:
So what does this mean? Well, it calls
sinf
(which lives inlibm
, the math library part of glibc). Now, for the optimized version:Empty! What does that mean? It means that rather than calling
sinf
at runtime, the value was computed at compile time (GCC uses the MPFR library for constant folding floating point expressions).So the results differ because, depending on the optimization level, one is using two different implementations of the sine function.
Now, finally, lets look at the hex values my modified test program printed. You can see the unoptimized value ends in
e0
(the zero not being printed since it’s a fractional value) vsde
for the optimized one. If my mental hex arithmetic is correct, that is a difference of 2 ulp, and well, you can’t really expect implementations of trigonometric functions to differ by less than that.