Почему деление на ноль в стандарте IEEE754 приводит к бесконечному значению?
Мне просто любопытно, почему в IEEE-754
любое ненулевое число с плавающей точкой, деленное на ноль, приводит к бесконечному значению? Это чепуха с математической точки зрения. Поэтому я считаю, что правильным результатом этой операции является NaN.
Функция f(x) = 1/x не определена, когда x=0, если x является действительным числом. Например, функция sqrt не определена для любого отрицательного числа и sqrt(-1.0f), если IEEE-754
производит NaN
значение. Но 1.0f/0 это Inf
,
Но по какой-то причине это не так в IEEE-754
, Для этого должна быть причина, может быть, некоторые причины оптимизации или совместимости.
Так какой в этом смысл?
4 ответа
Это чепуха с математической точки зрения.
Да. Нет. Вроде.
Дело в том, что числа с плавающей точкой являются приблизительными. Вы хотите использовать широкий диапазон показателей и ограниченное количество цифр и получать результаты, которые не являются полностью ошибочными.:)
Идея IEEE-754 заключается в том, что каждая операция может вызывать "ловушки", которые указывают на возможные проблемы. Они есть
- Незаконно (бессмысленная операция типа sqrt отрицательного числа)
- Переполнение (слишком большое)
- Underflow (слишком маленький)
- Деление на ноль (то, что вам не нравится)
- Неточно (эта операция может дать вам неверные результаты, потому что вы теряете точность)
Теперь многие люди, такие как ученые и инженеры, не хотят беспокоиться о написании процедур ловушек. Поэтому Кахан, изобретатель IEEE-754, решил, что каждая операция также должна возвращать разумное значение по умолчанию, если подпрограмм ловушек не существует.
Они есть
- NaN за незаконные ценности
- бесконечность со знаком для переполнения
- подписанные нули для Underflow
- NaN для неопределенных результатов (0/0) и бесконечности для (x/0 x!= 0)
- нормальный результат работы для Inexact
Дело в том, что в 99% всех случаев нули вызваны недостатком, и поэтому в 99% случаев бесконечность является "правильной", даже если она неверна с математической точки зрения.
Я не уверен, почему вы считаете, что это чепуха.
Упрощенное определение a / b
по крайней мере для ненулевых b
является уникальным числом b
s, которые должны быть вычтены из a
прежде чем вы доберетесь до нуля.
Расширяя это до случая, когда b
может быть нулем, число, которое нужно вычесть из любого ненулевого числа, чтобы добраться до нуля, действительно бесконечно, потому что вы никогда не достигнете нуля.
Еще один способ взглянуть на это - говорить с точки зрения ограничений. Как положительное число n
приближается к нулю, выражение 1 / n
приближается к "бесконечности". Вы заметите, что я цитировал это слово, потому что я твердо верю в то, что я не пропагандирую заблуждение, что бесконечность - это конкретное число:-)
NaN
зарезервировано для ситуаций, когда число не может быть представлено (даже приблизительно) каким-либо другим значением (включая бесконечности), оно считается отличным от всех этих других значений.
Например, 0 / 0
(используя наше упрощенное определение выше) может иметь любое количество b
вычитается из a
чтобы достичь 0. Следовательно, результат является неопределенным - это может быть 1, 7, 42, 3.14159 или любое другое значение.
Аналогично, такие вещи, как квадратный корень из отрицательного числа, который не имеет значения в реальной плоскости, используемой IEEE754 (для этого нужно перейти на комплексную плоскость), не могут быть представлены.
В математике деление на ноль не определено, потому что ноль не имеет знака, поэтому два результата одинаково возможны и исключительны: отрицательная бесконечность или положительная бесконечность (но не оба).
В (большинстве) вычислений 0.0 имеет знак. Поэтому мы знаем, к какому направлению мы приближаемся и какой знак будет иметь бесконечность. Это особенно верно, когда 0.0 представляет ненулевое значение, слишком маленькое, чтобы быть выраженным системой, как это часто бывает.
Единственный случай, когда NaN будет уместным, - это если система точно знает, что знаменатель действительно равен нулю. И это невозможно, если только нет особого способа обозначить это, что добавило бы накладные расходы.
ПРИМЕЧАНИЕ: я переписал это после ценного комментария от @Cubic.
Я думаю, что правильный ответ на это должен прийти из исчисления и понятия пределов. Учитывайте предел f(x)/g(x)
как x->0
при условии, что g(0) == 0
, Здесь есть два интересных случая:
- Если
f(0) != 0
тогда предел какx->0
это либо плюс или минус бесконечность, либо он не определен. Еслиg(x)
принимает оба знака в окрестностяхx==0
, тогда предел не определен (левый и правый пределы не совпадают). Еслиg(x)
имеет только один знак около 0, однако предел будет определен и будет либо положительной, либо отрицательной бесконечностью. Подробнее об этом позже. - Если
f(0) == 0
кроме того, тогда предел может быть любым, включая положительную бесконечность, отрицательную бесконечность, конечное число или неопределенное значение.
Во втором случае, вообще говоря, вы вообще ничего не можете сказать. Возможно, во втором случае NaN
единственный жизнеспособный ответ.
Теперь, в первом случае, зачем выбирать один конкретный знак, если он возможен или не определен? На практике это дает вам больше гибкости в тех случаях, когда вы что-то знаете о знаменателе, при относительно небольших затратах в тех случаях, когда вы этого не знаете. У вас может быть формула, например, где вы аналитически знаете, что g(x) >= 0
для всех x
скажем, например, g(x) = x*x
, В этом случае предел определен, и это бесконечность со знаком, равным знаку f(0)
, Вы можете использовать это как удобство в своем коде. В других случаях, когда вы ничего не знаете о признаке g
обычно вы не можете воспользоваться этим, но цена здесь заключается лишь в том, что вам нужно поймать в ловушку несколько дополнительных случаев - положительную и отрицательную бесконечность - в дополнение к NaN
если вы хотите полностью исправить ошибку, проверьте ваш код. Там есть некоторая цена, но она невелика по сравнению с гибкостью, полученной в других случаях.
Зачем беспокоиться об общих функциях, когда вопрос был о "простом делении"? Одна из распространенных причин заключается в том, что если вы вычисляете числитель и знаменатель с помощью других арифметических операций, вы накапливаете ошибки округления. Наличие этих ошибок можно абстрагировать в общий формат формул, показанный выше. Например f(x) = x + e
, где x
является аналитически правильным, точным ответом, e
представляет ошибку от округления, и f(x)
это число с плавающей запятой, которое вы фактически имеете на машине при исполнении.