Мы работаем над библиотекой числовых подпрограмм в C. Мы еще не уверены, будем ли мы работать с единой точностью ( float
) или double ( double
), поэтому мы определили тип SP
как псевдоним, пока не решим:
typedef float SP;
Когда мы запускаем наши модульные тесты, все они передают мою машину (64-разрядный Ubuntu), но они терпят неудачу у моего коллеги (32-разрядный Ubuntu, который был ошибочно установлен на 64-битной машине).
Используя команду bisect
Git, мы обнаружили, что различие, которое начало давать разные результаты между его машиной и моей:
-typedef double SP; +typedef float SP;
Другими словами, переход от двойной точности к одинарной точности дает на наших машинах разные результаты (примерно 1е-3 относительная разница в худших случаях).
Мы совершенно уверены, что мы никогда не сравниваем беззнаковые ints с отрицательными подписанными ints в любом месте.
Почему библиотека числовых подпрограмм дает разные результаты в 32-разрядной операционной системе и 64-битной системе?
ПОЯСНЕНИЯ
Боюсь, что я, возможно, не был достаточно ясен: у нас есть Git commit 2f3f671
который использует двойную точность, и где модульные тесты проходят одинаково хорошо на обеих машинах. Затем мы имеем Git commit 46f2ba
, где мы изменили на единую точность, и здесь тесты все еще проходят на 64-битной машине, но не на 32-битной машине.
Вы сталкиваетесь с тем, что часто называют ошибкой избыточной точности x87.
Короче говоря: исторически (почти) все вычисления с плавающей запятой на процессорах x86 выполнялись с использованием набора команд x87, который по умолчанию работает с 80-битным типом с плавающей запятой, но может быть настроен на работу как в одно-, так и в двойном -оценка (почти) некоторыми битами в регистре управления.
Если выполняются операции с одной точностью, а точность управляющего регистра x87 установлена на двойную или расширенную точность, тогда результаты будут отличаться от результатов, которые будут производиться, если одни и те же операции выполнялись с использованием одной точности (если компилятор не является чрезвычайно осторожно и сохраняет результат каждого вычисления и перезагружает его, чтобы заставить округление происходить в правильном месте.)
Ваш код, запущенный на 32-разрядном, использует блок x87 для вычисления с плавающей запятой (по-видимому, с набором регистров управления для двойной точности) и, таким образом, сталкивается с проблемой, описанной выше. Ваш код, работающий на 64-битной основе, использует инструкции SSE [2,3, …] для вычисления с плавающей запятой, которые обеспечивают собственные операции с однократной и двойной точностью и, следовательно, не несут чрезмерной точности. Вот почему ваши результаты различаются.
Вы можете обойти это (до точки), сообщив компилятору использовать SSE для вычисления с плавающей запятой даже на 32-битной ( -mfpmath=sse
с GCC). Даже тогда бит-точные результаты не гарантируются, потому что различные библиотеки, с которыми вы ссылаетесь, могут использовать x87 или просто использовать разные алгоритмы в зависимости от архитектуры.
IIRC, точность «double» требуется только > = точность «float». Таким образом, при одной реализации фактическое количество бит в «float» и «double» может быть одинаковым, а с другой – разными. Вероятно, это связано с 32-разрядной / 64-разрядной разницей на платформах, но может и не быть.