Почему int и decimal генерируют DivideByZeroException, а с плавающей точкой - нет?

Согласно http://msdn.microsoft.com/en-us/library/system.dividebyzeroexception.aspx только Int и Decimal будут генерировать исключение DivideByZeroException, когда вы делите их на 0, но когда вы делите число с плавающей запятой на 0, результат равен бесконечность, отрицательная бесконечность или NaN. Почему это? И каковы некоторые примеры, где результат +ve бесконечность, -ve бесконечность или NaN?

3 ответа

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

Ловушки могут быть использованы для остановки программы, но неисправимые ситуации встречаются крайне редко.
[...]
Флаги предлагают как предсказуемый поток управления, так и скорость. Их использование требует, чтобы программист знал об исключительных условиях, но пометка флага позволяет программистам откладывать обработку исключительных условий до тех пор, пока это не будет необходимо.

Это может показаться странным для разработчика, привыкшего к языку, на котором обработка исключений глубоко запекается, например C#. Разработчики стандарта IEEE 754 думают о более широком спектре реализаций (например, встроенных системах), где такие средства недоступны или нежелательны.

Ответ Майкла, конечно, правильный. Вот еще один способ взглянуть на это.

Целые числа точны. Когда вы делите семь на три в целых числах, вы фактически задаете вопрос: "Сколько раз я могу вычесть три из семи, прежде чем мне понадобится перейти к отрицательным числам?". Деление на ноль не определено, потому что вы не можете вычитать ноль из семи раз, чтобы получить что-то отрицательное.

Поплавки по своей природе неточны. Они обладают определенной степенью точности, и вам лучше предположить, что "реальное" количество находится где-то между данным поплавком и поплавком рядом с ним. Кроме того, числа с плавающей запятой обычно представляют физические величины, и они имеют ошибку измерения, намного большую, чем ошибка представления. Я думаю о плавании как о нечеткой размытой области, окружающей точку.

Поэтому, когда вы делите семь на ноль в числах с плавающей точкой, думайте об этом как о делении некоторого числа, разумно близкого к семи, на некоторое число, разумно близкое к нулю. Ясно, что число, достаточно близкое к нулю, может сделать отношение сколь угодно большим! И поэтому это сигнализируется вам, давая бесконечность в качестве ответа; это означает, что ответ может быть сколь угодно большим, в зависимости от того, где на самом деле лежит истинное значение.

Встроенный в процессор механизм с плавающей запятой вполне способен генерировать исключения для деления с плавающей запятой на ноль. В Windows есть специальный код исключения: STATUS_FLOAT_DIVIDE_BY_ZERO, код исключения 0xC000008E, "Деление с плавающей точкой на ноль". Также как и о других ошибках, о которых может сообщать FPU, таких как переполнение, недостаточность и неточные результаты (иначе говоря, ненормальные).

Будет ли это сделано, определяется регистром управления, программы могут изменять этот регистр с помощью вспомогательной функции, такой как _controlfp (). Например, библиотеки, созданные с помощью инструментов Borland, обычно делали это, снимая маскировку с этих исключений.

Это не сработало, мягко говоря. Это худшая из возможных глобальных переменных, которую вы можете себе представить. Смешивание таких библиотек с другими, которые ожидают, что деление на ноль будет генерировать бесконечность, а не исключение, просто не работает и практически невозможно иметь дело с.

Соответственно, в настоящее время для языковых сред выполнения нормой является маскировка всех исключений с плавающей запятой. На этом настаивает и CLR.

Работать с библиотекой, которая разоблачает исключения, довольно сложно, но имеет глупый обходной путь. Вы можете бросить исключение и поймать его снова. Код обработки исключений внутри CLR сбрасывает управляющий регистр. Пример этого показан в этом ответе.

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