Как получить деление Python на -0.0 и 0.0, чтобы получить соответственно -Inf и Inf?

У меня есть ситуация, когда разумно иметь деление на 0,0 или на -0,0, где я бы ожидал увидеть + Inf и -Inf, соответственно, как результаты. Кажется, что Python любит бросать

ZeroDivisionError: float division by zero

в любом случае. Очевидно, я решил, что могу просто обернуть это тестом на 0.0. Однако я не могу найти способ отличить от +0.0 до -0.0. (FYI вы можете легко получить -0.0, набрав его или с помощью общих вычислений, таких как -1.0 * 0.0).

IEEE обрабатывает все это очень хорошо, но Python, похоже, прилагает все усилия, чтобы скрыть хорошо продуманное поведение IEEE. Фактически, тот факт, что 0.0 == -0.0 на самом деле является функцией IEEE, поэтому поведение Python серьезно нарушает ситуацию. Он отлично работает в C, Java, Tcl и даже JavaScript.

Предложения?

Ответ 1

from math import copysign

def divide(numerator, denominator):
    if denominator == 0.0:
        return copysign(float('inf'), denominator)
    return numerator / denominator

>>> divide(1, -0.0)
-inf
>>> divide(1, 0)
inf

Ответ 2

Я полностью согласен с @Mark Ransom, за исключением того, что вместо этого я использовал бы try:

def f(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        return copysign(float('inf'), denominator)

Причина, по которой я рекомендую это, заключается в том, что если вы выполняете эту функцию много раз, вам не нужно тратить время на каждую проверку итерации, если значение равно нулю, прежде чем пытаться делить.

ИЗМЕНИТЬ

Я сравнивал скорость try по сравнению с функцией if:

def g(a, b):
    if b == 0:
        return copysign(float('inf'), b)
    else:
        return a / b

Вот тесты:

s = time.time()
[f(10, x) for x in xrange(-1000000, 1000000, 1)]
print 'try:', time.time()-s
s = time.time()
[g(10, x) for x in xrange(-1000000, 1000000, 1)]
print 'if:', time.time()-s

Вот результат:

try: 0.573683023453
if: 0.610251903534

Это означает, что метод try работает быстрее, по крайней мере, на моей машине.

Ответ 3

Библиотека gmpy2 предоставляет плавающий тип произвольной точности, а также позволяет управлять поведением IEEE-754.

>>> import gmpy2
>>> from gmpy2 import mpfr
>>> mpfr(1)/mpfr(0)
mpfr('inf')
>>> mpfr(1)/mpfr(-0)
mpfr('inf')
>>> mpfr(1)/mpfr("-0")
mpfr('-inf')
>>> gmpy2.get_context().trap_divzero=True
>>> mpfr(1)/mpfr(0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
gmpy2.DivisionByZeroError: 'mpfr' division by zero in division

Отказ от ответственности: я поддерживаю gmpy2.

Ответ 4

Вот решение, которое правильно обрабатывает все крайние случаи, по крайней мере, насколько я знаю:

def divide(a: float, b: float) -> float:
    try:
        return a/b
    except:
        return a*math.copysign(math.inf, b)

assert divide( 1,  1) ==  1
assert divide( 1, -1) == -1
assert divide(-1,  1) == -1
assert divide(-1, -1) ==  1
assert divide( 1,  0.0) >  1e300
assert divide( 1, -0.0) < -1e300
assert divide(-1,  0.0) < -1e300
assert divide(-1, -0.0) >  1e300
assert math.isnan(divide( 0.0,  0.0))
assert math.isnan(divide( 0.0, -0.0))
assert math.isnan(divide(-0.0,  0.0))
assert math.isnan(divide(-0.0, -0.0))

В случае, когда b - ноль, он в основном разделяет деление a/b на a * (1/b) и реализует 1/b через copysign(). Умножение не выбрасывает, когда его аргументы равны 0*inf, вместо этого оно правильно возвращает NAN.