Разность округлений 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?