Почему этот расчет дает разные результаты в 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
.