Обработка очень очень маленьких чисел в Python - PullRequest
6 голосов
/ 10 ноября 2019

Мне нужно умножить на 1e6 числа порядка 0.01. Ожидаемый результат порядка 1e-100000000. Очевидно, что типичная арифметика с плавающей точкой не может справиться с этим.

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

>>> Decimal('.01')**Decimal('1e5') # Seems to handle this
Decimal('1E-200000')
>>> Decimal('.01')**Decimal('1e5')*Decimal('1E200000') # Yeah! It works!
Decimal('1')
>>> Decimal('.01')**Decimal('1e6') # This result is strange...
Decimal('0E-1000026')
>>> Decimal('.01')**Decimal('1e6')*Decimal('0E1000026') # Wrong result
Decimal('0')

Кто-нибудь знает какое-либо решение этой проблемы?

Ответы [ 2 ]

3 голосов
/ 10 ноября 2019

Ваш результат неверен, потому что десятичная дробь тоже имеет точность (десятичная дробь - это математика с фиксированной точкой), поэтому вы также столкнетесь с проблемой недостаточного значения:

Decimal('.01')**Decimal('1e6')

Десятичная дробь ('0E-1000026')

Но:

getcontext().prec = 1000000000   # sets precision to 1000000000
Decimal('.01')**Decimal('1e6')

Десятичное число ('1E-2000000')

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

Decimal('.01')**Decimal('1e6')

можно преобразовать в

Decimal('1e-2') ** Decimal('1e6')

и позже в

1 ** ((-2) ** 1e6) = 1 ** (-2000000)

Десятичная документация модуля

2 голосов
/ 10 ноября 2019

Почему бы не использовать логарифмы?

Вы хотите вычислить:

RESULT  = x1 * x2 * x3 * x4 ... * xn

Представьте это как:

ln(RESULT) = ln(x1) + ln(x2) + ln(x3) + ln(x4) ... + ln(xn)

Очень маленькие положительные числа хорошо сохраняются в числах с плавающей запятой, есливы сохраняете их натуральный логарифм:

ln(0.000001) ≈ -13.81551

Вместо того, чтобы хранить сами числа, сохраняйте журнал значений.

Предположим, вы добавили ln(0.0000011) к себе 10^6 раз. Вы получаете примерно -13815510.558. На float теряется меньше точности, чем на 0.000001^(10^6)

Какое бы число вы ни получили в итоге, вы знаете, что ваш результат - это просто число e, возведенное в эту степень. Например, RESULT = e^-13815510.558

Вы можете использовать следующий код:

import math

class TinyNum:
    def __init__(self, other=None, *, pow=None):
        """
        x = TinyNum(0.0000912922)
        x = TinyNum("0.12345")     # strings are okay too
        x = TinyNum(pow = -110)    # e^-110
        y = TinyNum(x)             # copy constructor
        """
        if other:
            if isinstance(other, type(self)):
                self._power = other._power
            else:
                self._power = math.log(float(str(other)))
        else: # other == None
            self._power = float(str(pow))

    def __str__(self):
        return "e^"+str(self._power)

    def __mul__(lhs, rhs):
        rhs = type(lhs)(rhs)
        return type(lhs)(pow=lhs._power + rhs._power)

    def __rmul__(rhs, lhs):
        lhs = type(rhs)(lhs)
        return type(rhs)(pow=lhs._power + rhs._power)

    def __imul__(total, margin):
        total._power = total._power + type(total)(margin)._power


lyst = [
    0.00841369,
    0.004766949,
    0.003188046,
    0.002140916,
    0.004780032
]

sneaky_lyst = map(TinyNum, lyst)

print(math.prod(sneaky_lyst))

Сообщение, напечатанное на консоли:

e^-27.36212057035477
...