Ошибка двойной точности C ++ с Visual Studio 2012 / Intel Compiler под Windows

у меня проблема с двойной точностью под 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.

0

Решение

Они оба верны.

Имея дело с математикой с плавающей запятой с любой точностью, вы не можете требовать, чтобы результат был точным числом до последней цифры. Ожидая получить ровно 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, где используется арифметика с фиксированной точкой.

2

Другие решения

Посмотрите, как реализован 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. Вы можете оценить ошибку, используя элементарную числовую математику.

0

По вопросам рекламы [email protected]