Разность округлений gcc между версиями
Я изучаю почему тестовый пример не работает
Проблематичный тест может быть сведен к выполнению (4.0/9.0) ** (1.0/2.6)
, округляя его до 6 цифр и проверять на известное значение (в виде строки):
#include<stdio.h>
#include<math.h>
int main(){
printf("%.06f\n", powf(4.0/9.0, (1.0/2.6)));
}
Если я скомпилирую и запустил это в gcc 4.1.2 в Linux, я получаю:
0.732057
Python соглашается, как и Wolfram | Alpha:
$ python2.7 -c 'print "%.06f" % (4.0/9.0)**(1/2.6)'
0.732057
Однако я получаю следующий результат по gcc 4.4.0 в Linux и 4.2.1 на OS X:
0.732058
A double
действует тождественно (хотя я не тестировал это подробно)
Я не уверен, как сузить это дальше. Это регрессия gcc? Изменение алгоритма округления? Я делаю что-то глупое?
Изменить: Распечатка результата до 12 цифр, цифра на 7-м месте - 4 против 5, что объясняет разницу округления, но не разницу значений:
gcc 4.1.2:
0.732057452202
gcc 4.4.0:
0.732057511806
Здесь gcc -S
вывод из обеих версий: https://gist.github.com/1588729
Ответы
Ответ 1
Недавняя версия gcc может использовать mfpr для вычисления времени с плавающей запятой. Я предполагаю, что ваш последний gcc делает это и использует более высокую точность для версии времени компиляции. Это разрешено по крайней мере стандартом C99 (я не смотрел в другом, если он был изменен)
6.3.1.8/2 в C99
Значения плавающих операндов и результатов плавающих выражений могут быть представлены в большей точности и дальности, чем требуемые типом; типы не изменено таким образом.
Изменить: результаты gcc -S подтверждают это. Я не проверял вычисления, но старый (после замены памяти на его постоянный контент)
movss 1053092943, %xmm1
movss 1055100473, %xmm0
call powf
вызов powf с предварительно вычисленными значениями для 4/9.0 и 1/2.6, а затем печать результата после продвижения по службе, чтобы удвоить, а новый только распечатать float 0x3f3b681f, чтобы удвоить.
Ответ 2
Я думаю, что старый gcc использовал double
под капотом. Выполняя вычисления в Haskell и распечатывая результаты до полной точности, я получаю
Prelude Text.FShow.RealFloat> FD ((4/9) ** (1/2.6))
0.73205748476369969512944635425810702145099639892578125
Prelude Text.FShow.RealFloat> FF ((4/9) ** (1/2.6))
0.732057511806488037109375
Таким образом, результат double
согласуется с тем, что было произведено gcc-4.1.2, и результат float
с тем, что делает gcc-4.4.0. Результаты gcc-4.5.1 производятся здесь для float
соответственно. double
согласны с результатами Haskell.
Как цитирует программист, компилятору разрешено использовать более высокую точность, старый gcc сделал, новый, по-видимому, не делает.
Ответ 3
Здесь много игроков. Gcc, скорее всего, просто собирается перенаправить вычисление на ваш процессор с плавающей запятой; вы можете проверить разборку для этого.
Вы можете проверить двоичные результаты с двоичным представлением (из того же wolfram/alpha):
float q=powf(4.0/9.0, (1.0/2.6));
unsigned long long hex=*reinterpret_cast<unsigned long long*>(&q);
unsigned long long reference=0x1f683b3f;
assert( hex==reference );
Но также возможно printf
. Это может быть и десятичное представление этого числа. Вы можете попробовать написать printf("%0.06f", 0.73205748 );
, чтобы проверить это.
Ответ 4
Вы должны иметь возможность различать формат округления по-разному и математику, дающую другой ответ, просто печатая больше (всех) значащих цифр.
Если он выглядит одинаково, когда округление не происходит, printf("%0.6f"
просто округляется по-разному.
Хорошо, со старой средой Linux + python, которую я должен передать, я получаю:
Python 2.4.3 (#1, Jun 11 2009, 14:09:37)
[GCC 4.1.2 20080704 (Red Hat 4.1.2-44)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> (4.0/9.0)**(1.0/2.6)
0.7320574847636997
который снова отличается.
Возможно, было бы проще спросить, сколько значимых цифр действительно значимо для этого unit test?