Многоточное рациональное сравнение Python: фракция, mpq и mpfr

#python #fractions #mpfr #gmpy

#python #дроби #mpfr #gmpy

Вопрос:

Я понимаю, что вычисление с плавающей запятой не является точным из-за его природы. Я пытаюсь найти лучшую библиотеку / способ сделать многоточное сравнение рационов. Я сравниваю фракцию, mpq и mpfr. Последние два взяты из библиотеки gmpy2. Первый из них — из пакета фракций. Я использую python3.3

Это сценарий, который я использовал для сравнения. Не очень хорошо написано, очень просто.

 from fractions import Fraction
from gmpy2 import mpq, mpfr
import time

# This script compares gmpy2 library and Fraction library

total_pass_mpq = 0
total_pass_mpfr = 0
total_pass_frc = 0

a = mpq("-3.232429")
a_ = Fraction("-3.232429")
a__ = mpfr("-3.232429")
if str(float(a)) == "-3.232429":
    total_pass_mpq  =1
if str(float(a_)) == "-3.232429":
    total_pass_frc  = 1
if str(float(a__)) == "-3.232429":
    total_pass_mpfr  = 1

b = mpq("604.08")
c = mpq("1.979")
b_ = Fraction("604.08")
c_ = Fraction("1.979")
b__ = mpfr("604.08")
c__ = mpfr("1.979")
if str(float(b*c)) == "1195.47432":
    total_pass_mpq  = 1
if str(float(b_*c_)) == "1195.47432":
    total_pass_frc  = 1
if str(float(b__*c__)) == "1195.47432":
    total_pass_mpfr  = 1

d = mpq(604.08)
e = mpq(1.979)
d_ = Fraction(604.08)
e_ = Fraction(1.979)
d__ = mpfr(604.08)
e__ = mpfr(1.979)
if str(float(d*e)) == "1195.47432":
    total_pass_mpq  = 1
if str(float(d_*e_)) == "1195.47432":
    total_pass_frc  = 1
if str(float(d__*e__)) == "1195.47432":
    total_pass_mpfr  = 1

f = mpq(-3.232429)
f_ = Fraction(-3.232429)
f__ = mpfr(-3.232429)
if str(float(f)) == "-3.232429":
    total_pass_mpq  =1
if str(float(f_)) == "-3.232429":
    total_pass_frc  = 1
if str(float(f__)) == "-3.232429":
    total_pass_mpfr  =1

g = mpq(503.79)
g_ = Fraction(503.79)
g__ = mpfr(503.79)
h = mpq(0.07)
h_ = Fraction(0.07)
h__ = mpfr(0.07)
if str(float(g*(1 h))) == "539.0553":
    total_pass_mpq  = 1
if str(float(g_*(1 h_))) == "539.0553":
    total_pass_frc  = 1
if str(float(g__*(1 h__))) == "539.0553":
    total_pass_mpfr  = 1

print("Total passed mpq: "   str(total_pass_mpq))
print("Total passed Fraction: "   str(total_pass_frc))
print("Total passed mpfr: "   str(total_pass_mpfr))

start_mpq = time.time()
for i in range(0, 50000):
    y = mpq(0.32329)
    z = mpq(-1)
    yz = y*z
end_mpq = time.time()
print("Time for executing mpq: "   str(end_mpq - start_mpq))

start_frc = time.time()
for j in range(0, 50000):
    y = Fraction(0.32329)
    z = Fraction(-1)
    yz_ = y*z
end_frc = time.time()
print("Time for executing frc: "   str(end_frc - start_frc))

start_frc_2 = time.time()
for j_ in range(0, 50000):
    y = Fraction(0.32329)
    z = Fraction(-1)
    yz_2 = y*z
end_frc_2 = time.time()
print("Time for executing frc str: "   str(end_frc_2 - start_frc_2))

start_mpfr = time.time()
for k in range(0, 50000):
    y = mpfr(0.32329)
    z = mpfr(-1)
    yz__ = y*z
end_mpfr = time.time()
print("Time for executing mpfr: "   str(end_mpfr - start_mpfr))

start_mpfr_2 = time.time()
for k_ in range(0, 50000):
    y = mpfr("0.32329")
    z = mpfr("-1")
    yz__2 = y*z
end_mpfr_2 = time.time()
print("Time for executing mpfr str: "   str(end_mpfr_2 - start_mpfr_2))
  

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

 Total passed mpq: 3
Total passed Fraction: 5
Total passed mpfr: 4
Time for executing mpq: 0.04700875282287598
Time for executing frc: 2.1327619552612305
Time for executing frc str: 2.0934295654296875
Time for executing mpfr: 0.05441713333129883
Time for executing mpfr str: 0.12844634056091309
  

Итак, в основном я получил результат, что фракция является наиболее точной, но она очень медленная. По этому вопросу я хотел спросить,

  1. есть ли какой-либо другой случай, который, по вашему мнению, я также должен попробовать?
  2. любая другая библиотека?
  3. Если важна скорость, есть ли способ повысить точность с помощью библиотеки gmpy2?

Комментарии:

1. mpq и Fraction должна быть равной (фактически бесконечной) точностью, поскольку они оба хранят произвольную точность int s в качестве числителя и знаменателя. Я подозреваю, что ваши тесты плохо разработаны (слишком полагаются на представление с плавающей запятой), если они утверждают, что у них нет точности соответствия. mpq в основном должна быть более быстрая версия Fraction , вот и все. Однако в обоих случаях инициализация из a float вызывает проблемы; float у s есть проблемы с представлением, которые разные типы рациональных чисел могут преобразовывать по-разному.

2. Например, @ShadowRanger float(mpq("-3.232429")) дает мне -3.2324289999999998 , пока float(Fraction("-3.232429")) дает мне -3.232429 . Как вы думаете, это ожидаемо?

3. Вы предполагаете, что целью библиотек является обработка математики с плавающей запятой. Это не так. Как только вы покидаете область рациональных чисел, библиотеки выходят за рамки предполагаемого варианта использования. Я не знаю точно, где возникает «ошибка», но обратное преобразование из mpq в float может отличаться из-за слишком большой точности в GMP (где ошибка с плавающей запятой зависит от точности; может помочь преобразовать себя mympq.numerator / mympq.denominator , чтобы это сделал Python), или слишком мало. Если вы конвертируете в и из float , вы неправильно выполняете математику рациональных чисел.

4. @ShadowRanger Как насчет этого: >>> from gmpy2 import sub >>> sub(mpq("-3.232429"), mpq(-3.232429)) >>> mpq(7697,35184372088832000000) почему оно не равно нулю? Разве это не неточно?

5. Это верно и для Fraction too, потому что, как я уже сказал, ВЫ НЕ МОЖЕТЕ ИСПОЛЬЗОВАТЬ float С БИБЛИОТЕКАМИ РАЦИОНАЛЬНЫХ ЧИСЕЛ! ЭТО НЕ ТО, ДЛЯ ЧЕГО ОНИ ПРЕДНАЗНАЧЕНЫ! Один из них становится дробью -3232429/1000000 , другой становится -7278783019950793/2251799813685248 , потому что это фактическое соотношение, представленное float значением -3.232429 . Строка является точной, и оба Fraction и mpq использовать его, чтобы получить точное представление «разделить на степень 10». A float будет преобразовываться менее интуитивно, потому что вы даете им число, которое на самом деле не является рациональным, как вы думаете.

Ответ №1:

float(mpq) вызывает библиотечную функцию GMP mpq_get_q . Я проверил источник GMP и mpq_get_d округлил промежуточный результат до 0. Он не вычисляет правильно округленный результат. (В этом случае правильное округление подразумевает округление до ближайшего с привязкой к четному.) Так что иногда это будет отличаться от float(Fraction) .

Библиотека GMP не оптимизирована для вычислений с плавающей запятой. Чтобы получить правильно округленные значения с плавающей запятой, вы должны использовать библиотеку MFPR (она же mpfr type in gmpy2 ).

Самый точный способ преобразовать an mpq в a float — сначала преобразовать его в an mpfr . Чтобы избежать двойного округления, вы должны преобразовать из mpq в mpfr с точностью ровно 53 бита. Итак float(mpfr(mpq, 53)) . (Точность по умолчанию в настоящее время составляет 53 бита, но это может измениться в будущем. Рекомендуется указать желаемую точность или обеспечить, чтобы точность контекста по умолчанию была установлена на 53.) Это изменение делает mpq и Fraction возвращает те же результаты, что и в вашем примере.

Есть еще один mpfr результат, который отличается. Это просто связано с тем фактом, что промежуточные mpfr вычисления округляются до текущей точности (в данном случае 53 бита).

Обновление, чтобы ответить на вопрос @mattsun.

Почему mpfr("503.79")*(mpfr("1") mpfr("0.07")) не равно «539.0553»?

Как float тип Python, так и mpfr тип gmpy2 используют двоичное, или radix-2, представление. Обычно мы используем десятичное или десятичное представление, когда работаем с числами. Точно так же, как 1/3 cannon может быть точно представлено в десятичной арифметике, большинство десятичных чисел не могут быть точно представлены в двоичном представлении. Вычисления выполняются со значениями, которые близки, но не точно равны заданным значениям. Ошибки могут накапливаться, и результат будет немного отличаться от вашего ожидаемого значения.

Есть два варианта:

1) Отформатируйте строку в желаемый десятичный формат.

2) Используйте decimal библиотеку.

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

Комментарии:

1. Спасибо за ответ. Я не уверен, что правильно понял это. Учитывая это выражение mpfr("503.79")*(mpfr("1") mpfr("0.07")) , оно дает мне результат mpfr('539.0553000000001') . Тем не менее, я хотел получить значение «539.0553». Как?

2. Независимо от приведенного выше примера, то, что вы сказали, преобразование mpq в mpfr (53 бит) имеет смысл и полезно.