Что говорит стандарт о функциях cmath, таких как std:: pow, std:: log и т.д.?

Стандарт ли гарантирует, что функции возвращают тот же результат во всех реализациях?

Возьмем, например, pow(float,float) для 32-битных поплавков IEEE. Является ли результат во всех реализациях идентичным, если те же два поплавка передаются в?

Или существует ли какая-то гибкость, которую стандарт допускает в отношении крошечных различий в зависимости от алгоритма, используемого для реализации pow?

Ответ 1

Нет, стандарт С++ не требует, чтобы результаты cmath-функций были одинаковыми во всех реализациях. Во-первых, вы не можете получить арифметику с плавающей точкой IEEE-754/IEC 60559.

Тем не менее, если реализация использует IEC 60559 и определяет __STDC_IEC_559__, то она должна придерживаться Приложения F стандарта C (да, ваш вопрос о С++, но стандарт С++ отменит стандарт C для заголовков C, таких как math.h). Приложение F гласит:

  • Тип float соответствует стандарту IEC 60559.
  • Тип double соответствует двойному формату IEC 60559.
  • Тип long double соответствует расширенному формату IEC 60559, иначе расширенный формат без стандарта IEC 60559, кроме формата IEC 60559 double.

Далее, он говорит, что нормальная арифметика должна соответствовать стандарту IEC 60559:

  • Операторы +, , * и / предоставляют IEC 60559 операции добавления, вычитания, умножения и деления.

Далее требуется sqrt следовать IEC 60559:

  • Функции sqrt в <math.h> обеспечивают работу квадратного корня IEC 60559.

Далее описывается поведение нескольких других функций с плавающей запятой, большинство из которых вам, вероятно, не интересует этот вопрос.

Наконец, он попадает в заголовок math.h и указывает, как различные математические функции (т.е. sin, cos, atan2, exp и т.д.) должны обрабатывать особые случаи (т.е. asin(±0) возвращает ±0, atanh(x) возвращает NaN и вызывает "неверное" исключение с плавающей запятой для | x | > 1 и т.д.). Но это не приводит к точному вычислению для обычных входов, а это означает, что вы не можете полагаться на все реализации, производя точно такие же вычисления.

Нет, это не требует, чтобы эти функции вели себя одинаково во всех реализациях, даже если все реализации определяют __STDC_IEC_559__.


Это все с теоретической точки зрения. На практике все хуже. Процессоры обычно реализуют арифметику IEC 60559, но могут иметь разные режимы округления (поэтому результаты будут отличаться от компьютера к компьютеру), а компилятор (в зависимости от флагов оптимизации) может сделать некоторые предположения, которые не соответствуют строгому стандарту в отношении вашего арифметика с плавающей запятой.

Таким образом, на практике это даже менее строго, чем теоретически, и вы, вероятно, увидите, что два компьютера производят несколько разные результаты в какой-то момент.


Реальный пример этого в glibc - реализация библиотеки GNU C. У них есть таблица известных пределов ошибок для их математических функций в разных ЦП. Если все C-математические функции были точными по битам, все эти таблицы отображали бы ULP с ошибками 0. Но они этого не делают. В таблицах показано, что в их математической функции C есть действительно различная погрешность. Я думаю, что это предложение - самое интересное резюме:

За исключением определенных функций, таких как sqrt, fma и rint, результаты которых полностью заданы ссылкой на соответствующие операции с плавающей запятой IEEE 754 и преобразованиями между строками и плавающей точкой, библиотека GNU C не нацелены на корректно округленные результаты для функций в математической библиотеке [...]

Единственными вещами, которые являются точными в glibc, являются вещи, которые должны быть точными в приложении F стандарта C. И, как вы можете видеть в их таблице, большинство вещей - нет.