Почему этот расчет дает разные результаты в boost:: thread и std:: thread?
Когда это вычисление с плавающей запятой выполняется в boost::thread, оно дает другой результат, чем при выполнении в std::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("%llXn%0.25fn", *reinterpret_cast<unsigned long long*>(&v), v);
}
Это, по-видимому, происходит потому, что boost::thread по умолчанию использует 53-битную внутреннюю точность для математики с плавающей точкой, в то время как основной поток использует 64-битную точность. Если состояние блока FPU сбрасывается с помощью _fpreset() после создания boost::thread, то результат будет таким же, как и в основном потоке.
Я использую Embarcadero C++ Builder 10.1 (компилятор bcc32c версии 3.3.1) и Boost 1.55.0. Моя среда-Windows 7,и я создаю для 32-разрядной целевой Windows.
Рабочий пример:
#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("%llXn%0.25fn", 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;
}
Вывод:
3EAABB3194A6E99A
0.0000007966525939409087744
3EAABB3194A6E99A
0.0000007966525939409087744
3EAABB3194A6E999
0.0000007966525939409087488
3EAABB3194A6E99A
0.0000007966525939409087744
Вопрос:
- Почему это происходит? Разве новый поток не должен наследовать среду с плавающей запятой от родительского потока?
- это ошибка в компиляторе или в Boost, или мои ожидания неверны?
4 ответа:
Разница, по-видимому, заключается в том, что реализация
std::threadделает_fpreset(), аboost::thread, очевидно, нет. ]}namespace boost { void tss_cleanup_implemented() { } }To (отформатировано немного для ясности):
Вы увидите, что все значения теперь совершенно одинаковы (namespace boost { void tss_cleanup_implemented() { _fpreset(); } }3EAABB3194A6E99A). Это говорит мне,что Boost не делает_fpreset(). Этот вызов необходим, потому что некоторые вызовы Windows API портят стандартные параметры FPU, используемые C++Builder (32 бит), и не возвращают их к тому, что они были (это проблема, с которой вы можете столкнуться и в Delphi).И
Что-то подсказывает мне, что вы уже ожидали этого, поэтому тест сstd::thread, иboost:threadиспользуют вызовы Win32 API для обработки потоков.print_number_2(), который делает_fpreset().
Это:
*reinterpret_cast<unsigned long long*>(&v)является неопределенным поведением, посколькуvне являетсяunsigned_long_long. Если вы хотите скопировать двоичное представлениеdoubleв интегральный тип, используйтеmemcpy(). Обратите внимание, что даже сmemcpy(), это реализация, определяющая, как будет выглядеть двоичное представление, но вы гарантированно можете "загрузить обратно то, что вы сохранили". Ничего больше АФАИК.
Это не разница между 64 и 53-битными вычислениями точности FPU, это разница вокруглении . Единственное различие между этими двумя результатами заключается в наименее значимой части ответа. Похоже, что начальный код потока boost неправильно инициализирует флаги FPU, а режим округления по умолчанию-down или chop, а не ближайший.
Если это так, то это может быть ошибка в boost::thread. Это также может произойти, если меняется другая библиотека флаги FPU (через _controlfp_s или аналогичную функцию), или если новый поток является частью пула потоков, предыдущий пользователь потока изменил флаги, и пул не сбросил их перед повторным использованием потока.
Для этого вам нужен лучший компилятор.
Это, по-видимому, происходит потому, что boost::thread по умолчанию использует 53-битную внутреннюю точность для математики с плавающей запятой, в то время как основной поток использует 64-битную точность. Если состояние блока FPU сбрасывается с помощью функции _fpreset () после создания потока boost::, то результат будет таким же, как и в основном потоке.
Это безумие. Если компилятор использует другой модуль FP (например, x87 vs SSE) для разных областей кода, вы должны сжечь этот компилятор с самым большим огнем, который вы можете найти.
Запуск этого кода под g++-6.1 и clang++-3.8 на Linux Mint 17.3 дает идентичные результаты для каждого типа потока.
#include <thread> #include <boost/thread.hpp> #include <cstdio> #include <cmath> 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); } int main() { print_number(); std::thread t1(&print_number); t1.join(); boost::thread t2(&print_number); t2.join(); }CXX-std=c++14-O3-c test test.c-pthread-lboost_thread-lboost_system
3EAABB3194A6E999
0.00000079665259394090866853EAABB3194A6E999
0.00000079665259394090866853EAABB3194A6E999
0.0000007966525939409086685
Как отметил @lorro в своем ответе, вы нарушаете правила псевдонимирования вreinterpret_cast.