Точность с плавающей точкой в сборке MIPS
Я написал два файла кода в сборке MIPS для выражения ниже:
R(n) = (от i до n) SUM { (i+2)/(i+1- 1/i) - i/(i+ 1/i) }
Один код вычисляет все выражение R(n) как суммирование и выдает результат.
Второй код сначала вычисляет первое слагаемое, т.е. (i + 2) / (i + 1- 1 / i) в цикле, а затем вычисляет второе слагаемое, т.е. i / (i + 1 / i) в другом цикле. Затем он просто вычитает две суммы.
Ниже приведены результаты для двух программ для разных значений n:
Программа 1:
N Result
-----------
10 5.07170725
100 7.41927338
1000 9.72636795
10000 12.02908134
100000 14.33149338
1000000 16.63462067
Программа 2:
N Result
---------
10 5.07170773
100 7.41923523
1000 9.72259521
10000 12.31250000
100000 8.61718750
1000000 6.50000000
Программа 1 дает более точные результаты (по сравнению с результатами Wolfram Alpha для R(n)). Почему программа 2 дает странные результаты при больших значениях n? Мой вопрос связан с точностью с плавающей запятой здесь.
Примечание: я использую числа одинарной точности.
1 ответ
Скажем, у вас есть un=an-bn и вы хотите сумму (un)
lim an -> 1, когда n -> бесконечность, поэтому сумма P членов стремится к P + cte_a, то же самое для bn, сумма стремится к P + cte_b
Когда вы различаете два (P + cte_a) - (P + cte_b), вы должны математически получить сумму (un).
Но с плавающей точкой это не то, что происходит, потому что (P + cte_a) округляется до ближайшего числа с плавающей точкой. И чем больше P, тем меньше float (P + cte_a) -float (P) будет близко к cte_a...
Чтобы убедить себя, попробуйте оценить эти операции:
10.0f+0.1f-10.0f
100.0f+0.1f-100.0f
...
1.0e7f+0.1f-1.0e7
lim un -> 1 / n, когда n -> бесконечность, поэтому программа 1 работает немного лучше...