Python: При поиске продукта большого массива, как лучше всего уменьшить ошибку с плавающей запятой?

1

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

import numpy as np
randoms = np.random.uniform(0.5, 1.61, 10000)
print(randoms[0:10])

array([ 1.01422339,  0.65581167,  0.8154046 ,  1.49519379,  0.96114304,
    1.20167417,  0.93667198,  0.66899907,  1.26731008,  1.59689486])

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

product_1 = 1
for i in randoms:
    product_1 = product_1 * i
print(product_1)

64355009.758539267

Следующий метод заключается в использовании numpy встроенного prod функции, однако это возвращает ту же самую величину, как указано выше, что свидетельствует о том, что это, как prod на самом деле это вычисление:

product_2 = np.prod(randoms)
print(product_2)

64355009.758539267

print(product_1 == product_2)

True

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

product_3 = np.exp(np.sum(np.log(randoms)))
print(product_3)

64355009.758538999

print(product_3 == product_1)

False

Я знаю, что в этом примере я не теряю такой высокой точности, но для того, что мне действительно нужно делать, ошибки компаундирования в конечном итоге вызывают проблемы, достаточно того, что я рассматриваю возможность использования пакета, который может выполнять вычисление символьной/произвольной точности, Итак, какой метод лучше всего здесь? Есть ли другие способы, которые я не рассматривал?

  • 0
    Один из других способов вы не рассматривали это NumPy «S cumprod который выступает за совокупный продукт. Это то, что вы делаете в основном. Просто возьмите последний элемент [-1] как product_1 = np.cumprod(randoms)[-1] . Вы можете сравнить свой ответ
  • 1
    Посмотрите на десятичные и дробные библиотеки Python
Показать ещё 12 комментариев
Теги:
numpy
floating-accuracy
precision

1 ответ

1
Лучший ответ

Я попробовал несколько экспериментов. Код ниже, но сначала некоторые комментарии.

Можно точно вычислить результат, переведя значения в точные рациональные числа, точно вычислив продукт, а затем выполнив окончательное преобразование в поплавок. Это можно сделать с помощью модуля fractions входящего в состав Python, но в конечном итоге он будет очень медленным. Я использовал модуль gmpy2 для более быстрой рациональной арифметики.

Существуют некоторые тонкости с форматированием двоичных значений с плавающей запятой для отображения. Последние версии Python возвращают кратчайшую десятичную строку, которая приведет к исходному значению. numpy floats имеют другое форматирование. А также тип gmpy2.mpfr. И Decimal явно использовал другое правило форматирования. Поэтому я всегда конвертирую результат, вычисляемый в плавающий Python.

В дополнение к определяемой пользователем десятичной точности Decimal типа, я также использовал gmpy2.mpfr поскольку он поддерживает определяемую пользователем двоичную точность.

Программа выводит несколько значений:

  • Последовательное умножение с использованием 53-битной точности (64-разрядный формат IEEE).
  • Точное значение с использованием рациональной арифметики.
  • Использование Decimal с 28 десятичными цифрами точности.
  • Использование Decimal с заданной пользователем точностью.
  • Использование mpfr с заданной пользователем точностью.
  • Использование метода рекурсивного умножения для минимизации числа умножений.

Вот код. Вы можете изменить точность Decimal и mpfr и проверить точность.

import numpy as np
from gmpy2 import mpq, mpfr, get_context, round2
from decimal import Decimal, getcontext

randoms = np.random.uniform(0.5, 1.61, 10000)

# Sequential multiplication using 53-bit binary precision.

product_1 = 1
for i in randoms:
    product_1 = product_1 * i
print("53-bit binary:     ", float(product_1))

# Exact value by converting all floats to fractions and then a final
# conversion to float. Uses gmpy2 for speed.

product_2 = 1
for i in randoms:
    product_2 = product_2 * mpq(i)
print("exact using mpq:   ", float(mpfr(product_2, precision=53)))

# Decimal math with 28 decimal digits (~93 bits of precision.)

product_3 = 1
for i in randoms:
    product_3 = product_3 * Decimal(i)
print("Decimal(prec=28):  ", float(product_3))

# Choose your own decimal precision.

getcontext().prec=18
product_4 = 1
for i in randoms:
    product_4 = product_4 * Decimal(i)
print("Decimal(prec=%s):   %s" % (getcontext().prec, float(product_4)))

# Choose your own binary precision.

get_context().precision = 60
product_5 = 1
for i in randoms:
    product_5 = product_5 * mpfr(i)
print("mpfr(precision=%s): %s" % (get_context().precision, float(product_5)))

# Recursively multiply pairs of numbers together.

def rmult(d):
    if len(d) == 1:
        return d[0]
    # If the length is odd, extend with 1.
    if len(d) & 1:
        d.append(1)
    temp = []
    for i in range(len(d)//2):
        temp.append(d[2*i] * d[2*i+1])
    return rmult(temp)

print("recursive 53-bit:  ", float(rmult(list(randoms))))

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

Насколько важно, чтобы результат был на 100% точным?

Ещё вопросы

Сообщество Overcoder
Наверх
Меню