Почему один и тот же код дает разные числовые результаты на 32-битных 64-битных машинах?

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