Почему этот расчет дает разные результаты в 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("%llX\n%0.25f\n", *reinterpret_cast<unsigned long long*>(&v), v);
}
Это похоже на то, что boost::thread
по умолчанию использует 53-битную внутреннюю точность для математики с плавающей запятой, а основной поток использует 64-битную точность. Если состояние блока FPU reset с _fpreset()
после создания boost::thread
, результат будет таким же, как в основном потоке.
Я использую Embarcadero С++ 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("%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;
}
Вывод:
3EAABB3194A6E99A
0.0000007966525939409087744
3EAABB3194A6E99A
0.0000007966525939409087744
3EAABB3194A6E999
0.0000007966525939409087488
3EAABB3194A6E99A
0.0000007966525939409087744
Вопрос:
- Почему это происходит? Разве новый поток не должен наследовать среду с плавающей точкой из родительского потока?
- Это ошибка в компиляторе или в Boost, или мои ожидания ошибочны?
Ответы
Ответ 1
Разница заключается в том, что реализация 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, которые использует С++ Builder (32 бит), и не устанавливают их обратно к тем, какими они были (это проблема, с которой вы можете столкнуться и в Delphi).
оба std::thread
и boost:thread
используют вызовы API Win32 для обработки потоков.
Что-то подсказывает мне, что вы ожидали этого уже, поэтому тест с print_number_2()
, который выполняет _fpreset()
.
Ответ 2
Это: *reinterpret_cast<unsigned long long*>(&v)
поведение undefined, поскольку v
не unsigned_long_long
. Если вы хотите скопировать двоичное представление double
в интегральный тип, используйте memcpy()
. Обратите внимание, что даже при memcpy()
в реализации реализовано, как будет выглядеть двоичное представление, но вам гарантировано, что вы сможете "загрузить обратно то, что вы сохранили". Ничего больше AFAIK.
Ответ 3
Это не разница между вычислениями FPU с точностью до 64 и 53 бит, разница в ROUNDING. Единственное различие между этими двумя результатами - это наименее значимый бит ответа. Похоже, что стартовый код повышения потока не правильно инициализирует флаги FPU, а режим округления по умолчанию - вниз или прерывается, а не ближе.
Если это так, то это может быть ошибка в boost:: thread. Это может также произойти, если другая библиотека меняет флаги FPU (через _controlfp_s или аналогичную функцию), или если новый поток является частью пула потоков, предыдущий пользователь потока изменил флаги, а пул не выполнил reset перед повторным использованием потока.
Ответ 4
Чтобы справиться, вам нужен лучший компилятор.
Это похоже на то, что boost:: thread по умолчанию использует 53-битную внутреннюю точность для математики с плавающей запятой, а основной поток использует 64-битную точность. Если статус блока FPU равен reset с помощью _fpreset() после создания boost:: thread, результат будет таким же, как и в основном потоке.
Это безумие. Если ваш компилятор использует другой модуль FP (т.е. X87 против 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 = С++ 14 -O3 -c test test.c -pthread -lboost_thread -lboost_system
3EAABB3194A6E999
+0,0000007966525939409086685
3EAABB3194A6E999
+0,0000007966525939409086685
3EAABB3194A6E999
+0,0000007966525939409086685
Как отметил в своем ответе @lorro, вы нарушаете правила псевдонимов в reinterpret_cast
.