Числовая стабильность - дает ли Multiply/Divide более точное значение, чем Divide/Multiply?

Рассмотрим следующий код:

$result *= $oldFactor / $newFactor;  //using shorthand *= operator

что на самом деле это:

$result = $oldFactor / $newFactor * $result;  //division done first

Я также могу написать это вручную:

$result = $result * $oldFactor / $newFactor;  //multiplication done first

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

Кроме того, у меня есть ощущение, что для большинства повседневных человеческих чисел операция умножения произведет "большее число", прежде чем оно будет разделено (при условии, что используемые числа часто больше 1). И большие числа после разделения более численно устойчивы. Пример.. рассмотрим 5 * 7 / 2.3 где первая операция (мульт) является точной, так как эти числа представлены точно в двоичном виде. Затем деление делается, и это так точно, как мы собираемся получить. Но посмотрим 7 / 2.3 * 5где первая операция - деление, и мы уже производим число, которое не может быть точно представлено в двоичном виде, а следующая операция (мульт) преувеличивает любую неточность посредством умножения.

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

1 ответ

Если ваша платформа, набор инструментов и код соответствуют стандартной реализации арифметической модели IEEE-754 , то как умножение, так и деление дают результат с ошибкой не более половины ulp (единицы в последнем разряде) при округлении до ближайшего режима и при условии, что ввод был точным. Так что в принципе разницы нет.

Но есть тонкости, конечно. Как упоминалось в комментарии Раймонда Чена, это может зависеть от значений, с которыми вы работаете. Во-первых, при работе с большими числами ошибка 1/2 ulp больше, чем при работе с маленькими числами, поэтому, взяв 1 деление и 1 умножение с использованием одних и тех же входных данных, погрешность результата деления будет, конечно, меньше, чем у умножение.

С последовательностью 1 умножения и 1 деления и с использованием типа значительной точности, как double, верхняя ошибка в обоих случаях должна быть не более 1 ulp, а разница в порядке применения операндов в большинстве случаев должна быть очень небольшой. В примере из комментария этот эффект очень ярко выражен, так как у машины одноразрядный мантиссан.

Есть некоторые особенности, очевидно. Одним из них является отсутствие коммутативности арифметических операций с использованием чисел с плавающей запятой. Таким образом, выполнение (*,/) или (/,*) может дать вам немного разные результаты, например, на границе блока с плавающей запятой (один результат находится в верхней части блока экспоненты с плавающей запятой, а другой находится чуть выше, внизу следующего блока экспоненты с плавающей запятой), таким образом, один из результатов будет иметь двойную ошибку по сравнению с другим. Кроме того, может быть переполнение или недополнение. Поэтому, если сначала умножаются два больших числа, оно может переполниться до infи это загрязняет дальнейшие операции. При делении сначала очень маленьких чисел вы можете получить недополнение, то есть числовой результат, равный нулю, когда он должен был быть ненулевым, или даже получить субнормальный результат, который имеет уменьшенное количество цифр и, следовательно, большее значение. относительная ошибка.

Во всех случаях, если вам нужны крошечные ошибки в последовательности с несколькими операциями или если у вас есть последовательность с большим количеством операций в ваших вычислениях, вам следует провести анализ ошибок, априорный или апостериорный (выполнение анализа ошибок).

РЕДАКТИРОВАТЬ: Как сообщалось в комментарии к этому ответу, то, что я называю блоком экспоненты, на самом деле называется binade набором чисел с плавающей запятой, которые имеют одинаковую экспоненту.

Другие вопросы по тегам