у меня проблема с двойной точностью под Windows с Visual Studio и компилятором Intel.
#include <stdio.h>
#include <math.h>
int main()
{
double result = 42;
int rounds = 14;
for(int a=1; a<=rounds; a++)
{
result = sqrt(sqrt(result));
printf("Round %2.i is %.17g\n", a, result);
}
for(int a=1; a<=rounds; a++)
{
result = pow(result, 4);
printf("Round %2.i is %.17g\n", a, result);
}
return 0;
}
С этим кодом я создаю под Linux с этими настройками компилятора:
Компилятор Intel под Linux:
icc -O2 -fp-model double test.cpp -o test
Компилятор GCC под Linux:
g++ -O2 test.cpp -o test
и Matlab и Scilab под Windows правильного результата:
Round 1 is 2.5457298950218306
Round 2 is 1.2631446315995756
Round 3 is 1.0601401197016873
Round 4 is 1.0147073765340913
Round 5 is 1.0036567375971333
Round 6 is 1.0009129334669549
Round 7 is 1.0002281552726189
Round 8 is 1.0000570339386641
Round 9 is 1.0000142581797196
Round 10 is 1.0000035645258711
Round 11 is 1.0000008911302767
Round 12 is 1.0000002227824947
Round 13 is 1.000000055695619
Round 14 is 1.0000000139239045
Round 1 is 1.000000055695619
Round 2 is 1.0000002227824947
Round 3 is 1.0000008911302765
Round 4 is 1.0000035645258705
Round 5 is 1.0000142581797171
Round 6 is 1.0000570339386543
Round 7 is 1.0002281552725802
Round 8 is 1.0009129334668005
Round 9 is 1.0036567375965142
Round 10 is 1.0147073765315875
Round 11 is 1.0601401196912235
Round 12 is 1.263144631549705
Round 13 is 2.545729894619797
Round 14 is 41.999999973468661
С этим Intel Compileroptions под Linux
icc -O2 test.cpp -o test
и это gcc compileroptions под linux
g++ -O2 -ffast-math test.cpp -o test
и все настройки компилятора Visual Studio 2012 я получаю неправильный результат:
Round 1 is 2.5457298950218306
Round 2 is 1.2631446315995756
Round 3 is 1.0601401197016873
Round 4 is 1.0147073765340913
Round 5 is 1.0036567375971333
Round 6 is 1.0009129334669549
Round 7 is 1.0002281552726189
Round 8 is 1.0000570339386641
Round 9 is 1.0000142581797196
Round 10 is 1.0000035645258711
Round 11 is 1.0000008911302767
Round 12 is 1.0000002227824947
Round 13 is 1.000000055695619
Round 14 is 1.0000000139239045
Round 1 is 1.000000055695619
Round 2 is 1.0000002227824947
Round 3 is 1.0000008911302767
Round 4 is 1.0000035645258711
Round 5 is 1.0000142581797198
Round 6 is 1.0000570339386647
Round 7 is 1.0002281552726222
Round 8 is 1.0009129334669684
Round 9 is 1.0036567375971872
Round 10 is 1.0147073765343093
Round 11 is 1.0601401197025984
Round 12 is 1.2631446316039172
Round 13 is 2.5457298950568319
Round 14 is 42.000000002309839
Под Windows следующие настройки игнорируются, и я всегда получаю неправильный результат.
Компиляция Intel под Windows:
icl /O2 /fp:double test.cpp -o test.exe
Компиляции Visual Studio под Windows:
/fp:precise
/fp:strict
/fp:fast
производится все те же «неправильные номера» …
Почему эта ошибка и как я могу ее исправить, чтобы на всех платформах работала одинаковая двойная точность?
Большое спасибо и поздравления
Натали
Редактировать:
Для сравнения я зациклил код:
#include <stdio.h>
#include <math.h>
int main()
{
double x = 1;
double result = 0;
int rounds = 12;
while (x!=result)
{
x++;
result=x;
for(int a=1; a<=rounds; a++)
{
result = sqrt(sqrt(result));
}
for(int a=1; a<=rounds; a++)
{
result = pow(result, 4);
}
}
printf("The next possible with %2.i rounds is %.0f\n", rounds, result);
return 0;
}
Скомпилировано с работающими компиляторами Linux:
Компилятор Intel:
icc -O2 -fp-model double speedtest3.cpp -o speedtest3
Результат:
The next possible with 12 rounds is 3671078
real 0m1.950s
user 0m1.947s
sys 0m0.003s
Компилятор GCC:
g++ -O2 speedtest3.cpp -o speedtest3
Результат:
The next possible with 12 rounds is 3671078
real 0m3.445s
user 0m3.442s
sys 0m0.004s
Код с пау:
#include <stdio.h>
#include <math.h>
int main()
{
double x = 1;
double result = 0;
int rounds = 12;
while (x!=result)
{
x++;
result=x;
for(int a=1; a<=rounds; a++)
{
result = pow(result, 0.25);
}
for(int a=1; a<=rounds; a++)
{
result = pow(result, 4);
}
}
printf("The next possible with %2.i rounds is %.0f\n", rounds, result);
return 0;
}
Составлено с такими же параметрами:
Результат Intel Compiler:
The next possible with 12 rounds is 3671078
real 0m2.887s
user 0m2.885s
sys 0m0.004s
Результат компилятора GCC:
The next possible with 12 rounds is 3671078
real 0m5.905s
user 0m5.905s
sys 0m0.008s
Результаты:
sqrt почти в 2 раза быстрее, чем Pow
Компилятор Intel почти в 2 раза быстрее, чем компилятор gcc.
Они оба верны.
Имея дело с математикой с плавающей запятой с любой точностью, вы не можете требовать, чтобы результат был точным числом до последней цифры. Ожидая получить ровно 41.999999973468661, так же ошибочно, как и ожидать точно 42.000000000000000.
Вот пример того, как вы можете получить разные результаты: арифметика с плавающей запятой двойной точности в C ++ есть (как правило, зависит от компилятора и платформы) 64 бита. Тем не менее, аппаратное обеспечение процессора Intel с плавающей точкой вычисляет числа внутри 80 бит. В какой-то момент значения извлекаются из FPU и обрезаются на 64-битные. Что означает «в какой-то момент», зависит от того, что компилятор и его оптимизатор считают подходящим или оптимальным, и, следовательно, различные компиляторы / настройки приведут к округлению в разных частях вычисления, влияя на точные цифры результата.
Даже использование одной и той же двоичной программы на разных компьютерах может юридически изменить результат и при этом быть «правильным». Например, раньше у некоторых процессоров Intel не было аппаратного блока с плавающей запятой вообще (давным-давно, последним был 486SX). В этих случаях ваша программа будет выполнять вычисления в отдельной программной библиотеке, которая ничего не будет делать с 80-битной точностью.
Приложение:
Я хочу поставить вещи в перспективе. Вольфрам Альфа говорит, что расстояние между Нью-Йорком и Сан-Франциско составляет около 2577 миль. В этом масштабе расхождение, о котором вы так беспокоитесь, между двумя числами, все равно что сказать, что ваша собственная оценка расстояния «неверна на 1/10го дюймовый» (*). Если это расхождение имеет большое значение для вашей проблемы, арифметика с плавающей запятой является неправильным инструментом для работы.
* Относительная ошибка (42.000000002309839 — 41.999999973468661) /41.999999973468661 = 6.8669471471949833966026905617771e-10. 2577 миль = 163278720 дюймов. Эквивалентная дивергенция по этому измерению будет (163278720 дюймов * 6,8669471471949833966026905617771e-10) = 0,1121226 … дюймов. Все расчеты выполняются в Windows Calculator на Windows 7, где используется арифметика с фиксированной точкой.
Посмотрите, как реализован pow (double, double), Вот. Разные компиляторы разных производителей должны содержать разные реализации, что приводит к различиям.
Как было предложено, лучшим вариантом является использование
double h = x*x;
x = h*h;
То, что вы называете «неправильным», более точно, сравните:
delta is 2.3098394308362913e-09 42.00000000230983 - 42
delta is 2.6531338903623691e-08 42 - 41.999999973468661
Потом Что вы пытаетесь доказать или показать? Повторное выполнение некоторых числовых вычислений обычно хуже, чем непосредственное вычисление результата; поэтому pow (42, 1.0 / (1024 * 1024 * 64)) теоретически предпочтительнее выполнения взятия 4-х корней в цикле, и поэтому происходит обратный процесс: pow (результат, 1024 * 1024 * 64), который может позволить pow используйте минимальное количество умножений.
В любом случае, корень десятого числа 42 не может быть представлен в виде номера машины, и, таким образом, сотая степень этого числа должна отклоняться от числа 42. Вы можете оценить ошибку, используя элементарную числовую математику.