Как я могу проверить ошибку машинной точности при изменении значения переменной `c`?

Я следую Обзору вычислительной физики Ландау и решаю эту задачу (часть b):

введите здесь описание изображения

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

  1. При тестировании на наличие ошибок вычитания это так же просто, как вычитание результата 1 из результата 2? Суть в том, чтобы увидеть, как машина искажает вычитание из-за округления, поэтому я никак не могу получить точный ответ от машины, верно?

  2. Что касается моего решения этой проблемы, я обнаружил, что использование c=10^-1 приводит к 1.38777878078145e-17 для метода 1 и 1.11022302462516e-16 для метода 2. Когда я увеличиваю его до c=10^-16 (без получения в результате inf), я получаю -1.10223024625156e-17 для метода 1 и -0.0992800745259007 для метода 2. Это говорит мне о том, что для метода 2 произошло очень большое изменение значения, когда я модифицировал c. Но я не думаю, что улавливаю суть того, что автор хочет, чтобы я понял здесь.

Если вы хотите увидеть код, вот он. В противном случае вы можете увидеть пример вывода ниже:

import Foundation

var a: Double = 1.0
var b: Double = 1.0
var c: Double = pow(10,-16)
var x: Double = 0.0

//The following variables are just used to run the calculations within 
//the functions, i.e. they can be reused within each function definition
var x1: Double = 0.0
var x2: Double = 0.0

func x_func(a_var: Double, b_var: Double, c_var: Double) -> Double {

    x1 = 0.0
    x2 = 0.0
    x = 0.0

    x1 = pow(b,2.0)-4.0*a*c
    x2 = -b + x1.squareRoot()
    x = x2/(2.0*a)  

    return x

}
func x_func2(a_var: Double, b_var: Double, c_var: Double) -> Double {

    x1 = 0.0
    x2 = 0.0
    x = 0.0

    x1 = pow(b,2.0)-4.0*a*c
    x2 = -b - x1.squareRoot()
    x = x2/(2.0*a)  

    return x

}
func xp_func(a_var: Double, b_var: Double, c_var: Double) -> Double {

    x1 = 0.0
    x2 = 0.0
    x = 0.0

    x1 = pow(b,2.0)-4.0*a*c
    x2 = x1.squareRoot() + b
    x = -2.0*c/x2

    return x

}
func xp_func2(a_var: Double, b_var: Double, c_var: Double) -> Double {

    x1 = 0.0
    x2 = 0.0
    x = 0.0

    x1 = pow(b,2.0)-4.0*a*c
    x2 = b - x1.squareRoot()
    x = -2.0*c/x2

    return x

}

print("Method 1: Positive")
print(x_func(a_var: a, b_var: b, c_var: c))
print(" \n")



print("Method 1: Negative")
print(x_func2(a_var: a, b_var: b, c_var: c))
print(" \n")



print("Method 2: Positive")
print(xp_func(a_var: a, b_var: b, c_var: c))
print(" \n")



print("Method 2: Negative")
print(xp_func2(a_var: a, b_var: b, c_var: c))

print(" \n")

print("Subtractive cancellation error for Method 1:")
print(" \n")
print("Method 1 (positive) minus Method 2 (positive)",x_func(a_var: a, b_var: b, c_var: c) - xp_func(a_var: a, b_var: b, c_var: c))

print(" \n")
print("Subtractive cancellation error for Method 2:")
print(" \n")
print("Method 1 (negative) minus Method 2 (negative)",x_func2(a_var: a, b_var: b, c_var: c) - xp_func2(a_var: a, b_var: b, c_var: c))

Пример вывода:

Method 1: Positive
-1.11022302462516e-16

Method 1: Negative
-1.0

Method 2: Positive
-1e-16

Method 2: Negative
-0.900719925474099

Subtractive cancellation error for Method 1:

Method 1 (positive) minus Method 2 (positive) -1.10223024625156e-17

Subtractive cancellation error for Method 2:

Method 1 (negative) minus Method 2 (negative) -0.0992800745259007

person whatwhatwhat    schedule 03.02.2017    source источник
comment
Это вопрос о программировании на Swift или о понимании числовой математики?   -  person Martin R    schedule 03.02.2017
comment
Вы также можете прочитать этот комментарий .com/questions/42027328/ еще раз к предыдущему вопросу и узнайте о локальных переменных (вместо глобального определения x1, x2).   -  person Martin R    schedule 03.02.2017
comment
Я отметил swift, потому что это язык, который я использую, но я сосредоточен на том, чтобы понять, как возникают ошибки в вычислениях.   -  person whatwhatwhat    schedule 03.02.2017


Ответы (1)


Сначала давайте упростим ваш код. У вас есть два метода вычисления решений квадратного уравнения a x^2 + b x + c == 0:

func qsolve1(a: Double, b: Double, c: Double) -> (Double, Double) {
    let x1 = (-b - (b*b - 4*a*c).squareRoot())/(2*a)
    let x2 = (-b + (b*b - 4*a*c).squareRoot())/(2*a)
    return (x1, x2)
}

func qsolve2(a: Double, b: Double, c: Double) -> (Double, Double) {
    let x1 = -2*c/(b - (b*b - 4*a*c).squareRoot())
    let x2 = -2*c/(b + (b*b - 4*a*c).squareRoot())
    return (x1, x2)
}

(Здесь мы рассматриваем только случаи a != 0, c != 0 и b^2 - 4ac >= 0.)

Оба метода возвращали бы одинаковые результаты, если бы вычисления были выполнены точно, но из-за ошибок округления с плавающей запятой они различаются.

Применительно к многочлену

x^2 + x + 1.0E-10

вы получаете результаты:

print(qsolve1(a: 1.0, b: 1.0, c: 1.0E-10))
// (-0.99999999989999999, -1.000000082740371e-10)

print(qsolve2(a: 1.0, b: 1.0, c: 1.0E-10))
// (-0.99999991725963588, -1.0000000001000001e-10)

Если мы сравним эти цифры с результатами PARI/GP

? solve(x=-1.5, -0.5, x^2+x+1.0e-10)
%2 = -0.99999999989999999998999999999800000000
? solve(x=-0.5, +0.5, x^2+x+1.0e-10)
%3 = -1.0000000001000000000200000000050000000 E-10

тогда мы видим, что qsolve1() точнее вычисляет решение около -1.0, а qsolve2() точнее вычисляет решение около 0.0.

Почему это происходит? Давайте посмотрим на промежуточные результаты:

print("b=", b.debugDescription, " sqrt(b^2-4ac)=", (b*b - 4*a*c).squareRoot().debugDescription)
// b= 1.0  sqrt(b^2-4ac)= 0.99999999979999998

Двоичное число с плавающей запятой имеет ограниченную точность, около 16 десятичных цифр для чисел двойной точности IEEE 754.

При вычислении x1 из qsolve1() или x2 из qsolve2() эти два числа вычитаются, и разница составляет приблизительно

0.00000000020000002

в котором всего 8 значащих цифр, а не 16 больше. Это «вычитающая отмена», и это происходит, когда два числа примерно одинакового значения вычитаются друг из друга.

Поэтому численно лучше складывать числа. Является ли x1 или x2 более точным, зависит от знака b. В любом случае другое решение можно вычислить из отношения x1 * x2 = c без потери точности.

Это дает следующий метод:

func qsolve(a: Double, b: Double, c: Double) -> (Double, Double) {
    let x1: Double
    if b >= 0 {
        x1 = (-b - (b*b - 4*a*c).squareRoot())/(2*a)
    } else {
        x1 = (-b + (b*b - 4*a*c).squareRoot())/(2*a)
    }
    let x2 = c/x1
    return (x1, x2)
}

и для нашего теста мы получаем

print(qsolve(a: 1.0, b: 1.0, c: 1.0E-10))
// (-0.99999999989999999, -1.0000000001000001e-10)
person Martin R    schedule 03.02.2017
comment
@whatwhatwhat: (бесплатная) система компьютерной алгебры. Я использовал его только для сравнения. - person Martin R; 03.02.2017
comment
Итак, как мне определить здесь ошибку вычислений (согласно вопросу в книге)? Могу ли я сказать, что это просто b*b - 4*a*c? Потому что это значение меняется, когда я меняю значение c. - person whatwhatwhat; 03.02.2017
comment
@whatwhatwhat: Ошибка вычисления — это разница между вычисленным и точным решением. - person Martin R; 03.02.2017
comment
Ах хорошо. В этом случае я изучу эту программу PARI/GP, чтобы получить более точные значения. Вы случайно не знаете, как сделать так, чтобы Swift показывал точную машину epsilon? Я слышал, что для этого есть функция, но я не могу найти ее в документах. - person whatwhatwhat; 03.02.2017
comment
@whatwhatwhat: print(Double.ulpOfOne) в Swift 3. - person Martin R; 03.02.2017
comment
Подождите, но при вычислениях у меня получается 17 знаков после запятой. Однако, когда я использую print(Double.ulpOfOne), он возвращает 2.22044604925031e-16, который имеет только 14 цифр после запятой. Как может заданная точность машины быть ниже той, что она фактически возвращает в расчете? - person whatwhatwhat; 03.02.2017
comment
@whatwhatwhat: ulpOfOne — это разница между 1.0 и следующим большим номером машины. То, как это число напечатано (и сколько цифр) — это другое дело. - person Martin R; 03.02.2017