Деление на ноль в Haskell

Я обнаружил довольно странное несоответствие между поведением div и /.

*ghci> :t 1 `div` 0
1 `div` 0 :: Integral a => a
*ghci> :t 1 / 0
1 / 0 :: Fractional a => a
*ghci> 1 / 0
Infinity
*ghci> 1 `div` 0
*** Exception: divide by zero

Я был очень удивлен, заметив, что дробное деление на ноль приводит к Infinity, тогда как div правильно приводит к исключению. NaN тоже может подойти для /, но почему Infinity? Для такого результата нет математического обоснования. Вы знаете причину этого, пожалуйста?


person Riccardo T.    schedule 19.02.2012    source источник
comment
Математически результат 1 / 0 равен Infinity полностью оправдан. Это не единственное допустимое возвращаемое значение, но наиболее разумное. Обратите внимание, что вы также получите ошибку divide by zero, если оцените 1 / 0 :: Rational.   -  person Daniel Fischer    schedule 20.02.2012
comment
@DanielFischer: я бы не назвал это полностью математически оправданным, поскольку такого рода компактификация (с положительной и отрицательной бесконечностью) разрушает довольно много теорем, которые справедливы для ℝ, и некоторые из них предполагаются во многих программах.   -  person leftaroundabout    schedule 20.02.2012
comment
Вы не должны предполагать такие вещи при работе с числами с плавающей запятой. Даже основные свойства, такие как ассоциативность, не обязательно выполняются. Равенство также не рефлексивно для NaNs! (Например, (0/0) /= (0/0).   -  person Tikhon Jelvis    schedule 20.02.2012
comment
@leftaroundabout Вот почему это не единственное оправданное значение 1/0. Но компактификация Александрова также разрушает многие полезные свойства ℝ, не говоря уже о компактификации Чеха.   -  person Daniel Fischer    schedule 20.02.2012
comment
@DanielFischer: результат будет оправдан, если вычислить его как предел 1/x для x->0^+, но не только с помощью классического простого деления.   -  person Riccardo T.    schedule 20.02.2012
comment
Итак, как мы можем исправить это, чтобы выдавать ошибку для деления с плавающей запятой на 0, переполнения и прочей ерунды, такой как 0 ** 0 ?   -  person Sam Watkins    schedule 15.01.2014


Ответы (4)


Причина, по которой div не возвращает Infinity, проста — в типе Integer нет представления бесконечности.

/ возвращает Infinity, поскольку он соответствует стандарту IEEE 754 (который описывает представления чисел с плавающей запятой), поскольку тип Fractional по умолчанию — Double. Другие языки с числами с плавающей запятой (например, JavaScript) также демонстрируют такое поведение.

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

Prelude> 1/(-0)
-Infinity

Это тоже поведение из стандарта.

Если вы используете другой дробный тип, например Rational, вы получите ожидаемое поведение:

Prelude> 1 / (0 :: Rational)
*** Exception: Ratio.%: zero denominator

Кстати, если вам интересно, почему Integer и Double являются рассматриваемыми типами, когда ваша реальная операция не ссылается на них, взгляните на то, как Haskell обрабатывает типы по умолчанию (особенно числовые типы) в отчет.

Короткая версия заключается в том, что если у вас есть неоднозначный тип из класса Num, Haskell сначала попробует Integer, а затем Double для этого типа. Вы можете изменить это с помощью инструкции default (Type1, Type2...) или отключить с помощью инструкции default () на уровне модуля.

person Tikhon Jelvis    schedule 19.02.2012
comment
Где я могу узнать об утверждении default? Я не видел его раньше. - person amindfv; 20.02.2012
comment
Раздел отчета, на который я ссылался, охватывает его ближе к концу. Я думаю, что это также упоминается в мягком введении в Haskell. Тем не менее, я не уверен, что есть что-то большее, чем я рассказал здесь. (Если вы не включите некоторые расширения, это касается только числовых типов и ведет себя так, как я объяснил.) - person Tikhon Jelvis; 20.02.2012
comment
Я только что ввел его в GHCI. Какая у вас версия GHC? Кроме того, что произойдет, если вы просто попробуете 1/0? - person Tikhon Jelvis; 20.02.2012
comment
@TikhonJelvis: Спасибо за ваш ответ, он действительно полный. У меня есть последнее сомнение: упомянутый вами стандарт IEEE не кажется лучшим способом справиться с делением на ноль, он как бы предполагает их как ограничения для знаменателя, который идет к 0, а не к реальным делениям. Не лучше ли просто генерировать исключение, как это делает div? - person Riccardo T.; 20.02.2012
comment
@Riccardo Haskell не указывает, что должно произойти, когда вы делите на 0 числа с плавающей запятой. Как правило, он просто выполняет деление с плавающей запятой, поэтому то, что происходит, зависит от режима, в котором находится FPU. Это похоже на C. - person augustss; 20.02.2012

Надеюсь, это поможет:

Prelude> 1/0
Infinity
Prelude> -1/0
-Infinity
Prelude> 0/0
NaN
person 9000    schedule 19.02.2012
comment
Спасибо... да, это просто похоже на установленный таким образом предел. - person Riccardo T.; 20.02.2012

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

Пример:

Prelude> 10 ** 10 ** 10
Infinity

... определенно не оправдано математически!

person amindfv    schedule 20.02.2012
comment
Вы еще не встречали твердолобого финиста! :) - person Ingo; 20.02.2012
comment
@Ingo Hard core как в ноль, один, бесконечность? - person Daniel Fischer; 20.02.2012
comment
@ Даниэль Я имею в виду людей, которые утверждают, что число вроде 10 ^ 100 не имеет смысла, поскольку во вселенной не так много объектов. Но, возможно, они предпочтут 10^10^10 = NaN - person Ingo; 20.02.2012
comment
@Ingo Вы всегда можете запросить количество комбинаций объектов во вселенной, и вдруг вам пригодится гораздо больше чисел, поскольку вы должны использовать факториал :) - person godfryd; 20.02.2012
comment
@Piotr - я думаю, что лучше отвергнуть их аргументы по другим причинам. Пока (10^100)! действительно может быть огромным, но все же конечным, и поэтому они чувствуют себя оправданными в своей глупости. - person Ingo; 20.02.2012

Дробный не равен типу Float (или Double).

Доля 1/n, где n стремится к 0, поэтому lim(n→0) 1/n = +∞, lim(n→0) -1/n = -∞, и это имеет смысл.

person David Unric    schedule 19.02.2012
comment
Я думаю, что ограничение Fractional по умолчанию получает значение Double, которое является типом с плавающей запятой. Прочитайте раздел отчета, на который я ссылаюсь. - person Tikhon Jelvis; 20.02.2012
comment
Правильно, @TikhonJelvis, если нет объявления по умолчанию, говорящего об обратном, неоднозначный тип с ограничением Fractional по умолчанию имеет значение Double. - person Daniel Fischer; 20.02.2012