Является ли стандарт IEEE 754-2008 детерминированным?

Если я начну с тех же значений и выполню одни и те же примитивные операции (сложение, умножение, сравнение и т. д.) с 64-битными значениями двойной точности IEEE 754-2008, получу ли я тот же результат, независимо от базовой машины?

Более конкретно: поскольку ECMAScript 2015 указывает, что числовые значения

примитивное значение, соответствующее 64-битному двоичному формату двойной точности IEEE 754-2008

Могу ли я заключить, что одни и те же операции дают здесь один и тот же результат, независимо от среды?


person Joachim Breitner    schedule 11.02.2017    source источник
comment
Обратите внимание, что этот вопрос не касается алгебраических тождеств, которые могут выполняться или не выполняться.   -  person Joachim Breitner    schedule 12.02.2017
comment
Некоторые связанные ссылки, которые могут заинтересовать читателей этого вопроса: randomascii.wordpress .com/2013/07/16/детерминизм с плавающей запятой   -  person Joachim Breitner    schedule 12.02.2017
comment
Возможно связанный вопрос: stackoverflow.com/questions/10334782/   -  person Joachim Breitner    schedule 12.02.2017
comment
comment
Меня тоже интересует этот вопрос, но я хочу отметить, что альтернативой в настоящее время может быть арифметика с фиксированной точкой на каком-то родном языке (например, C или Rust) + WebAssembly с API, открытым для стороны JavaScript. Представьте каждый оператор как функцию, которая работает с TypedArrays, содержащими ваше представление с фиксированной точкой. Это 100% гарантированный детерминизм на любой мыслимой платформе.   -  person Ivan Perevezentsev    schedule 19.01.2018


Ответы (1)


(Здесь много сносок, чтобы отпугнуть толпу, но они не влияют на ваши вопросы о ECMAScript.)

ИЭЭЭ 754

Если я начну с тех же значений и выполню одни и те же примитивные операции (сложение, умножение, сравнение и т. д.) с 64-битными значениями двойной точности IEEE 754-2008, получу ли я тот же результат, независимо от базовой машины?

Да

Стандарт IEEE 754-2008 (и IEEE 754-2019) точно определяет операции сложения, вычитания, умножения, деления и извлечения квадратного корня для всех значений с плавающей запятой, за исключением различий между различными значениями NaN1 Реализации стандарта2 согласуются со всеми входными данными. То же самое касается трехстороннего сравнения (‹, = или ›, определенного для чисел, включая бесконечности; вызывает исключение для NaN) или четырехстороннего сравнения (‹, =, › или неупорядоченного, определенного для всех значений с плавающей запятой). включая NaN).

Эти пять арифметических операций не только точно определены для всех входных данных, но и для числовых входных данных точно определены для правильного округления: операция сложения с плавающей запятой ???? ⊕ ???? определена для получения fl(???? + ????), что является результат округления суммы действительных чисел ???? + ???? в соответствии с текущим режимом округления,3, который по умолчанию возвращает ближайшее число с плавающей запятой или, в случае ничьей, ближайшее число, чье наименьшее значащая цифра четная.

ECMAScript 2015 (и 2021)

Более конкретно: поскольку ECMAScript 2015 указывает, что числовые значения

примитивное значение, соответствующее 64-битному двоичному формату двойной точности IEEE 754-2008

Могу ли я заключить, что одни и те же операции дают здесь один и тот же результат, независимо от среды?

Да

Операции +, -, * и / с числами в ECMAScript 2015 точно определены для всех входных данных в соответствии со стандартом IEEE 754.4 Например, определение дополнение в ECMAScript 2015, в частности, гласит:

Результат сложения определяется по правилам двоичной арифметики двойной точности IEEE 754-2008:

Определение добавления в ECMAScript 2021 остается практически таким же, обновленным вместо этого цитировать IEEE 754-2019:

Абстрактная операция Number::add принимает аргументы x (число) и y (число). Он выполняет сложение в соответствии с правилами двоичной арифметики двойной точности IEEE 754-2019, производя сумму своих аргументов.

Точно так же равенство в ECMAScript 2015 и равенство в ECMAScript 2021 определяется в соответствии со стандартами IEEE 754-2008 и IEEE 754- 2019, хотя и без явного цитирования. Реляционные операторы в ECMAScript 2015 и реляционные операторы в ECMAScript 2021 реализуют понятие упорядоченного сравнения IEEE 754, возвращая false, когда любой из входных данных является NaN, а в противном случае соответствующий порядок.

Math.sqrt в ECMAScript 2015 и Math.sqrt в ECMAScript 2021 может возвращать определяемое реализацией приближение (с учетом ограничений, касающихся крайних случаев) к квадратный корень, хотя IEEE 754 точно определяет операцию квадратного корня и делает это с самого начала в IEEE 754-1985. Однако с практической точки зрения крайне маловероятно, что реализация не сможет вернуть правильно округленный результат, как того требует IEEE 754.

Примечание. Многие операции, отличные от четырех или пяти основных арифметических операций (+, -, *, /; Math.sqrt), разрешены и, скорее всего, будут зависеть от реализации. к реализации. Например, одна реализация может использовать простую полиномиальную аппроксимацию для Math.log1p, в то время как другая может использовать табличный набор аппроксимаций, что дает немного разные результаты для некоторых входных данных. Это иногда используется как вектор для снятия отпечатков пальцев браузера. Но любое приближение, которое вы реализуете, используя только основные арифметические операции, будет согласовано во всех реализациях ECMAScript.

Оператор % в ECMAScript 2015 и % в ECMAScript 2021 определен точно для всех входных данных, но не согласуется с операцией остатка IEEE 754: ECMAScript % использует усеченное деление, тогда как остаток IEEE 754 использует округление до ближайшего/привязки к четному. (ECMAScript % равен fmod в C, тогда как остаток IEEE 754 равен remainder в C.)

Другие языки

Приведенные выше ответы не всегда применимы к другим языкам. Например, подавляющее большинство реализаций C обеспечивают арифметику IEEE 754 binary64 для double и двоичную арифметику 32 для float, но стандарт C позволяет им использовать разные арифметические правила внутри выражений, при условии, что они определяют, какие правила через макрос FLT_EVAL_METHOD:

За исключением присваивания и приведения (которые удаляют весь дополнительный диапазон и точность), значения, получаемые операторами с плавающими операндами и значениями, подлежащими обычным арифметическим преобразованиям и плавающими константами, оцениваются в формате, диапазон и точность которого могут быть больше, чем требуется тип. Использование форматов оценки характеризуется определяемым реализацией значением FLT_EVAL_METHOD:

  • -1 неопределенный;
  • 0 оценивать все операции и константы только в пределах диапазона и точности типа;
  • 1 оценивать операции и константы типа float и double в диапазоне и точности типа double, оценивать long double операции и константы в диапазоне и точности типа long double;
  • 2 оценивает все операции и константы в диапазоне и точности типа long double.

Все остальные отрицательные значения для FLT_EVAL_METHOD характеризуют поведение, определяемое реализацией.

(C11, §5.2.4.2.2: Характеристики плавающих типов <float.h>, §9, стр. 30)

Это означает, что когда реализация определяет от FLT_EVAL_METHOD до 2, функция вроде

double
naive_fma(double x, double y, double z)
{
    return x*y + z;
}

будет реализовано как если бы было написано:

double
naive_fma(double x, double y, double z)
{
    return (long double)x*z + z;
}

Реализации C на архитектуре Intel IA-32 («i386») часто работают следующим образом: они используют Intel модуль x87 с плавающей запятой для вычисления выражений в 80-битной двоичной арифметике с плавающей запятой с 64-битной точностью («двойная расширенная точность»), а затем округление до IEEE 754 binary64 везде, где результаты хранятся в double переменная, переданная как double, аргумент или явно приведенная к double.5

Однако такой подход к вычислению выражений не разрешен в ECMAScript, так что вам не о чем беспокоиться. Реализация C, которая работает путем компиляции в ECMAScript очевидным способом, просто определила бы FLT_EVAL_METHOD как 0.


1 Содержимое полезных данных NaN может варьироваться от реализации к реализации. Однако является ли результат NaN и является ли результат NaN сигнальным или тихим, определяется стандартом.

2 Некоторое оборудование также обеспечивает нестандартные режимы работы, такие как сброс в ноль, который приводит к тому, что операции возвращают ноль, когда в соответствии с семантикой IEEE 754 они возвращали бы ненормальные числа; в этом случае аппаратное обеспечение не является реализацией стандарта. Если вы включите эти режимы, вы можете получить разные ответы, но обычно они не включены, и они нарушают теоремы, часто принимаемые числовыми алгоритмами, такими как лемма Стербенца, поэтому они используются только в специализированных приложениях. ECMAScript не поддерживает сбрасывание в ноль или другие нестандартные режимы работы, а также не поддерживает какие-либо реализации, о которых я знаю: вы можете полагаться на постепенное снижение значения ниже нормы, как это определено в IEEE 754.

3 IEEE 754 позволяет реализации поддерживать режим динамического округления с четырьмя определенными направлениями округления: к ближайшему/привязке к четному, вверх (в сторону положительной бесконечности), вниз (в сторону отрицательной бесконечность) и к нулю. В некоторых средах программы могут запрашивать и изменять текущий режим округления, например, в C с fegetround и fesetround, хотя поддержка этого в инструментальной цепочке часто ограничена и служит в основном для внесения небольших возмущений в числовые алгоритмы для проверки резких изменений в выходных данных. указывает на проблемы в алгоритме. ECMAScript не поддерживает изменение режима округления и не поддерживает какие-либо реализации, о которых мне известно: вам нужно иметь дело только с округлением до ближайшего/связями по умолчанию по умолчанию.

4 Семантика ECMAScript различает только одно значение NaN; в ECMAScript нет понятия полезной нагрузки NaN или сигнализации по сравнению с тихим NaN. Под капотом два NaN могут храниться с разными битовыми шаблонами, но ECMAScript не различает их семантически и не дает возможности различать их или исследовать битовые шаблоны под капотом.

5 Вычисление выражений с более высокой точностью может иногда приводить к ошибкам из-за двойного округления — например, добавить 0x1p+53 и 0x1.7ffp+1, и первое округление до 64-битной точности даст 0x1.000000000000018p+53, поэтому второе округление до 53-битной точности дает 0x1.000000000000002p+53, тогда как правильно округленная сумма с 53-битной точностью равна 0x1.000000000000001p+53. Так зачем это делать? На практике это почти всегда приводит к большей точности в числовых алгоритмах за счет использования более высокой промежуточной точности: вы можете позволить себе потерять тысячи ulps с 64-битной точностью и все же получить ответ, который находится в пределах пары ulps для 53-битной точности.

person Floating Pundit    schedule 11.02.2021