Вопросы десятичного разряда с поплавками и decimal.Decimal

Кажется, я теряю много точности с поплавками.

Например, мне нужно решить матрицу:

4.0x -2.0y 1.0z =11.0
1.0x +5.0y -3.0z =-6.0
2.0x +2.0y +5.0z =7.0

Это код, который я использую для импорта матрицы из текстового файла:

f = open('gauss.dat')
lines =  f.readlines()
f.close()

j=0
for line in lines:
    bits = string.split(line, ',')
    s=[]
    for i in range(len(bits)):
        if (i!= len(bits)-1):
            s.append(float(bits[i]))
            #print s[i]
    b.append(s)
    y.append(float(bits[len(bits)-1]))

Мне нужно решить с помощью gauss-seidel, поэтому мне нужно переставить уравнения для x, y и z:

x=(11+2y-1z)/4
y=(-6-x+3z)/5
z=(7-2x-2y)/7

Вот код, который я использую для переупорядочения уравнений. b - матрица коэффициентов, а y - вектор ответа:

def equations(b,y):
    i=0
    eqn=[]
    row=[]
    while(i<len(b)):
        j=0
        row=[]
        while(j<len(b)):
            if(i==j):
                row.append(y[i]/b[i][i])
            else:
                row.append(-b[i][j]/b[i][i])
            j=j+1
        eqn.append(row)
        i=i+1
    return eqn

Однако ответы, которые я возвращаю, не точны для десятичной точки.

Например, при перестановке второго уравнения сверху, я должен получить:

y=-1.2-.2x+.6z

Я получаю:

y=-1.2-0.20000000000000001x+0.59999999999999998z

Это может показаться не большой проблемой, но когда вы поднимаете номер до очень высокой мощности, ошибка довольно велика. Есть ли способ обойти это? Я попробовал класс Decimal, но он не работает с полномочиями (т.е. Decimal(x)**2).

Любые идеи?

Ответ 1

Я недостаточно разбираюсь в классе Decimal, чтобы помочь вам, но ваша проблема связана с тем, что десятичные дроби часто не могут быть точными, представленными в двоичном формате, поэтому то, что вы видите, является ближайшим возможным приближением; нет способа избежать этой проблемы, не используя специальный класс (например, Decimal, возможно).

EDIT: Как насчет десятичного класса не работает должным образом для вас? Пока я начинаю с строкой, а не с плавающей точкой, полномочия, похоже, работают нормально.

>>> import decimal
>>> print(decimal.Decimal("1.2") ** 2)
1.44

Документация модуля объясняет необходимость и использование decimal.Decimal довольно четко, вы должны проверить это, если вы еще этого не сделали.

Ответ 2

Плавающая точка IEEE является двоичной, а не десятичной. Не существует бинарной фракции фиксированной длины, которая составляет ровно 0,1 или любое ее кратное. Это повторяющаяся фракция, как 1/3 в десятичной форме.

Пожалуйста, прочитайте Что должен знать каждый компьютерный ученый о арифметике с плавающей точкой

Другими параметрами, кроме десятичного класса, являются

  • используя Common Lisp или Python 2.6 или другой язык с точными рациональными

  • преобразование двойников для закрытия рациональных вычислений с использованием, например, frap

Ответ 3

Во-первых, ваш вход можно упростить. Вам не нужно читать и разбирать файл. Вы можете просто объявить свои объекты в нотации Python. Eval файл.

b = [
    [4.0, -2.0,  1.0],
    [1.0, +5.0, -3.0],
    [2.0, +2.0, +5.0],
]
y = [ 11.0, -6.0, 7.0 ]

Во-вторых, y = -1.2-0.20000000000000001x + 0,59999999999999998z не является чем-то необычным. Точного представления в двоичной нотации нет 0,2 или 0,6. Следовательно, отображаемые значения являются десятичными приближениями исходных точных представлений. Это верно только для всех типов процессоров с плавающей запятой.

Вы можете попробовать модуль Python 2.6 fractions. Там может использоваться более старый rational пакет.

Да, повышение числа с плавающей запятой до значений увеличивает ошибки. Следовательно, вы должны быть уверены, что не используете правые позиции числа с плавающей запятой, так как эти биты в основном представляют собой шум.

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

>>> a
0.20000000000000001
>>> "%.4f" % (a,)
'0.2000'

Ответ 4

Я бы предостерег от десятичного модуля для таких задач. Его цель заключается в том, чтобы больше иметь дело с реальными десятичными числами (например, с учетом практики ведения бухгалтерского учета в человеке), с конечной точностью, а не с точной математикой точности. Есть числа, которые точно не представляются в десятичной форме, так же, как и в двоичном выражении, а выполнение арифметики в десятичной системе также намного медленнее, чем альтернативы.

Вместо этого, если вы хотите получить точные результаты, вы должны использовать рациональную арифметику. Они будут представлять числа в виде пары числитель/деноминатор, поэтому могут точно представлять все рациональные числа. Если вы используете только умножение и деление (а не операции, такие как квадратные корни, которые могут привести к иррациональным числам), вы никогда не потеряете точность.

Как уже упоминалось, python 2.6 будет иметь встроенный рациональный тип, хотя обратите внимание, что это не очень эффективная реализация - для скорости вы лучше используете библиотеки, такие как gmpy. Просто замените свои вызовы на float() на gmpy.mpq(), и ваш код теперь должен давать точные результаты (хотя вы можете отформатировать результаты как плавающие для отображения).

Здесь немного приведенная версия вашего кода для загрузки матрицы, которая вместо этого использует gmpy rationals:

def read_matrix(f):
    b,y = [], []
    for line in f:
        bits = line.split(",")
        b.append( map(gmpy.mpq, bits[:-1]) )
        y.append(gmpy.mpq(bits[-1]))
    return b,y

Ответ 5

Это не ответ на ваш вопрос, но связанный:

#!/usr/bin/env python
from numpy import abs, dot, loadtxt, max
from numpy.linalg import solve

data = loadtxt('gauss.dat', delimiter=',')
a, b = data[:,:-1], data[:,-1:]
x = solve(a, b) # here you may use any method you like instead of `solve`
print(x)
print(max(abs((dot(a, x) - b) / b))) # check solution

Пример:

$ cat gauss.dat
4.0, 2.0, 1.0, 11.0
1.0, 5.0, 3.0, 6.0 
2.0, 2.0, 5.0, 7.0

$ python loadtxt_example.py
[[ 2.4]
 [ 0.6]
 [ 0.2]]
0.0

Ответ 7

Просто предложение (я не знаю, с какими ограничениями вы работаете):

Почему бы не использовать прямое исключение Гаусса, а не итерацию Гаусса-Зейделя? Если вы выберете коэффициент с наибольшим значением в качестве поворота для каждого шага исключения, вы минимизируете ошибки округления FP.

Это может на самом деле то, что numpy.linalg.solve, упомянутое J.F.Sebastian(!!), делает.

Ответ 8

вместо десятичного, вы можете посмотреть mpmath.