Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does this calculation give different result in boost::thread and std::thread?

When this floating point calculation is executed in boost::thread, it gives different result than when executed in std::thread or in main thread.

void print_number()
{
    double a = 5.66;
    double b = 0.0000001;
    double c = 500.4444;
    double d = 0.13423;
    double v = std::sin(d) * std::exp(0.4 * a + b) / std::pow(c, 2.3);

    printf("%llX\n%0.25f\n", *reinterpret_cast<unsigned long long*>(&v), v);
}

This seems to happen because boost::thread is by default using 53-bit internal precision for floating point math, while the main thread is using 64-bit precision. If status of FPU unit is reset with _fpreset() after the boost::thread has been created, the result is the same as in the main thread.

I am using Embarcadero C++ Builder 10.1 (compiler bcc32c version 3.3.1) and Boost 1.55.0. My environment is Windows 7, and I am building for 32-bit Windows target.

Working example:

#include <tchar.h>
#include <thread>
#include <boost/thread.hpp>
#include <cstdio>
#include <cmath>
#include <cfloat>

namespace boost { void tss_cleanup_implemented() {} }

void print_number()
{
    double a = 5.66;
    double b = 0.0000001;
    double c = 500.4444;
    double d = 0.13423;
    double v = std::sin(d) * std::exp(0.4 * a + b) / std::pow(c, 2.3);

    // Edit:
    // Avoiding the undefined behaviour by a reinterpret_cast, as
    // mentioned in some answers and comments.
    unsigned long long x;
    memcpy(&x, &v, sizeof(x));

    printf("%llX\n%0.25f\n", x, v);
}

void print_number_2()
{
    // Reset FPU precision to default
    _fpreset();
    print_number();
}

int _tmain(int argc, _TCHAR* argv[])
{
    print_number();

    std::thread t1(&print_number);
    t1.join();

    boost::thread t2(&print_number);
    t2.join();

    boost::thread t3(&print_number_2);
    t3.join();

    getchar();
    return 0;
}

Output:

3EAABB3194A6E99A
0.0000007966525939409087744
3EAABB3194A6E99A
0.0000007966525939409087744
3EAABB3194A6E999
0.0000007966525939409087488
3EAABB3194A6E99A
0.0000007966525939409087744

Question:

  • Why does this happen? Isn't a new thread supposed to inherit floating point environment from the parent thread?
  • Is this a bug in the compiler or in Boost, or are my expectations wrong?
like image 699
VLL Avatar asked Jul 14 '16 10:07

VLL


1 Answers

This: *reinterpret_cast<unsigned long long*>(&v) is undefined behaviour as v is not unsigned_long_long. If you want to copy the binary representation of a double to an integral type, use memcpy(). Note that, even with memcpy(), it's implementation defined how the binary representation will look like, but you're guaranteed that you can 'load back what you've saved'. Nothing more AFAIK.

like image 73
lorro Avatar answered Oct 03 '22 20:10

lorro