Страницы

Поиск по вопросам

воскресенье, 26 мая 2019 г.

Почему при дробных значениях алгоритм не работает?

Задача следующая:
Ввести целое число a и вывести сумму квадратов всех чисел от 1 до a с шагом 0.1. (1^2+1.1^2+1.2^2+...+a^2)
Алгоритм решения такой:
result := 0 while (a >= 1) do begin result := result + a * a; a := a - 0.1; end;
В конце работы программы результат должен быть:
При a = 1.1 result = 2.21 При a = 3 result = 91.7
Переменная a, в любом случае, при завершении программы должна быть равна 0.9
Проблема в следующем. При a = 1.1 все верно, но начиная с 1.2 и далее программа выдает в результате на 1 меньше, чем нужно: a = 3; result = 90.7, при этом, в конце выполнения программы, a = 0.9(9)
Если модифицировать алгоритм: переменную a после ввода умножать на 10, а затем в цикле в квадрат возводить не ее, а выражение a / 10, то все начинает работать правильно (второй строкой в цикле здесь будет: a := a - 1).
В чем может быть проблема?


Ответ

Проблема в том, что числа представлены в двоичном виде. Десятичное значение 0,1 не может быть представлено в двоичном виде конечной дробью, оно равно 0,0001100110011...2 или 0,0(0011)2 в стандартной записи рациональных дробей.
У чисел с плавающей точкой с двойной точностью хранится 53 двоичных знака после запятой, бесконечная часть дроби отбрасывается. В результате вместо точного значения 0,1 мы имеем число 0,1000000000000000055511151231257827021181583404541015625. Если вы выведете его на печать с не очень большой точностью, вы получите 0,1, но это только результат округления. При округлении до 17-го знака получается 0,100000000000000001, и последний знак участвует в вычислениях. Поэтому, два раза вычитая из 1,2 значение 0,1, вы получаете число меньшее 1.
Простого решения подобных проблем на все случаи жизни не существует. Простейшим в вашем случае будет эмуляция чисел с фиксированной запятой, например так, как вы и сделали.
Можно упростить вычисления, посчитав суммы квадратов целых чисел, и разделив результат на 100:
a2 + (a + 0.1)2 + (a + 0.2)2 + ... =
(10a)2/100 + (10a + 1)2/100 + (10a + 2)2/100 + ...
Можно использовать библиотеки, которые обеспечивают работу с рациональными числами, с числами в десятичной системе счисления (как тип decimal в C#), или с числами большой точности.

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

Отправить комментарий