Как избежать ошибок с плавающей запятой?

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

def sqrt(num):
    root = 0.0
    while root * root < num:
        root += 0.01
    return root

Используя это, получим следующие результаты:

>>> sqrt(4)
2.0000000000000013
>>> sqrt(9)
3.00999999999998

Я понимаю, что могу просто использовать round(), но я хочу, чтобы сделать это очень точным. Я хочу уметь вычислять до 6 или 7 цифр. Это невозможно, если я закругляюсь. Я хочу понять, как правильно обрабатывать вычисления с плавающей запятой в Python.

Ответ 1

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

После того, как вы прочтете это, вы поймете, что вы не добавляете одну сотую в свой код. Это именно то, что вы добавляете:

>>> from decimal import Decimal
>>> Decimal(.01)
Decimal('0.01000000000000000020816681711721685132943093776702880859375')

Эта строка показывает точное десятичное значение бинарного плавающего ( "двойной точности" в С) приближения к точной десятичной величине 0,01. То, что вы действительно добавляете, немного больше, чем 1/100.

Управление числовыми ошибками с плавающей запятой - это поле под названием "численный анализ" и является очень большой и сложной темой. До тех пор, пока вы напуганы тем фактом, что поплавки просто приближаются к десятичным значениям, используйте модуль decimal. Это уберет для вас мир "мелких проблем". Например, учитывая эту небольшую модификацию вашей функции:

from decimal import Decimal as D

def sqrt(num):
    root = D(0)
    while root * root < num:
        root += D("0.01")
    return root

то

>>> sqrt(4)
Decimal('2.00')
>>> sqrt(9)
Decimal('3.00')

Это не совсем точно, но может быть менее неожиданным в простых примерах, потому что теперь он добавляет ровно одну сотую. ​​

Альтернативой является придерживаться float и добавлять что-то точно представляемое как двоичный float: значения формы I/2**J. Например, вместо добавления 0,01 добавить 0,125 (1/8) или 0,0625 (1/16).

Затем найдите "метод Ньютона" для вычисления квадратных корней; -)