От «почему 0.1 + 0.2 ≠ 0.3» до реальных катастроф в IT
ОП.10 Численные методы · Специальность 09.02.07 «Информационные системы и программирование»
Лекция 1 · 2 академических часа (90 минут)
Добро пожаловать на первую лекцию курса. Сегодня мы заложим фундамент всего курса — разберёмся, почему компьютеры ошибаются, откуда берутся эти ошибки и как будущему программисту научиться с ними жить.
На первый взгляд кажется, что погрешности — это «скучная математика», которая нужна только учёным. Но на деле это одна из самых частых причин багов в реальном программном обеспечении.
0.1 + 0.2 ≠ 0.3 в вашем коде.Программист обязан понимать, как компьютер хранит числа и где могут накапливаться ошибки. Иначе ваш код однажды может «промахнуться на 600 метров» — в финансах, медицине, транспорте или играх.
Что мы разберём за одну пару:
Во время войны в Персидском заливе американская зенитная ракетная система Patriot (ПРО — противоракетная оборона) не смогла перехватить иракскую ракету «Скад». В результате погибло 28 американских солдат.
0.1.0.1 — это бесконечная периодическая дробь.Вывод: крошечная ошибка округления → гибель людей. Это не теория, это реальность.
Прежде чем говорить о погрешностях, нужно договориться о терминах. В численных методах мы всегда имеем дело с двумя величинами.
Точное значение величины, которое мы хотели бы узнать. В большинстве реальных задач оно нам неизвестно.
Пример: истинное расстояние до Луны, истинное значение числа π.
То значение, которое мы получили в результате измерения, вычисления или которое хранится в памяти компьютера.
Пример: 3.14159265358979 — приближение π.
Компьютер не может хранить бесконечное число знаков. Поэтому любое число в памяти — это приближение.
Абсолютная погрешность (Δ) — это модуль разности между истинным и приближённым значениями:
Δ = |X − x|
Она показывает, на сколько единиц мы ошиблись. Измеряется в тех же единицах, что и сама величина: метры, секунды, рубли.
Измерили длину стола линейкой: получили 120 см. Истинная длина — 120.3 см.
Но если мы измеряем расстояние до Луны (384 000 км) с ошибкой в 1 км — это тоже абсолютная погрешность 1 км. Звучит страшнее, но по сути это гораздо более точное измерение.
Чтобы понять, насколько плохо мы ошиблись, одной абсолютной погрешности недостаточно. Нужно сравнить её с самой величиной.
Относительная погрешность (δ) — отношение абсолютной погрешности к модулю приближённого значения. Обычно выражается в процентах:
δ = (Δ / |x|) × 100%
Запомните: в программировании, когда вы видите «точность вычислений 10⁻⁹» — это почти всегда относительная погрешность. Именно её задают в алгоритмах поиска корней, интегрирования и решения дифференциальных уравнений.
| Измерение | Абсолютная погрешность | Относительная погрешность |
|---|---|---|
| Длина стола (1 м) | 1 см | 1% |
| Расстояние до Луны (384 000 км) | 1 км | 0.00026% |
Абсолютные погрешности близки (1 см и 1 км), но относительные отличаются в тысячи раз.
Именно поэтому в IT и науке чаще используют относительную погрешность — она масштабируется и позволяет сравнивать точность измерений разного порядка.
Любое численное решение в компьютере — это цепочка преобразований. На каждом этапе в него закрадывается ошибка. Все погрешности делятся на три большие группы.
Погрешность исходных данных. Возникает ещё до написания кода.
Погрешность метода. Замена точного процесса приближённым.
Погрешность округления. «Родная» погрешность программиста.
Разберём каждую подробнее.
Она возникает ещё до того, как вы написали хоть строчку кода.
Такую погрешность нельзя устранить в рамках численного метода. Можно только уменьшить, используя более точные исходные данные.
Возникает из-за того, что мы заменяем точный математический процесс приближённым.
Методическую погрешность можно уменьшить, выбрав более точный метод или увеличив число шагов. Но полностью устранить — нельзя.
Это «родная» погрешность программиста. Она возникает из-за того, что память ЭВМ (электронно-вычислительной машины) конечна: компьютер не может хранить бесконечное число знаков.
Каждое действие (сложение, умножение) может вносить микроскопическую ошибку округления.
Именно эта погрешность привела к катастрофе с Patriot. Именно её мы будем разбирать подробнее всего.
Чтобы понять, откуда берутся ошибки округления, нужно заглянуть «под капот» компьютера — к стандарту IEEE 754.
Все современные компьютеры хранят вещественные (дробные) числа по стандарту IEEE 754 (Institute of Electrical and Electronics Engineers — Институт инженеров по электротехнике и электронике, стандарт 754).
Любое вещественное число в IEEE 754 хранится в виде трёх частей:
число = (−1)^знак × 1.мантисса × 2^порядок
| Параметр | float (одинарная точность) | double (двойная точность) |
|---|---|---|
| Размер в памяти | 32 бита (4 байта) | 64 бита (8 байт) |
| Бит на мантиссу | 23 бита | 52 бита |
| Примерный диапазон | ±3.4 × 10³⁸ | ±1.7 × 10³⁰⁸ |
| Точность (знаков) | ≈ 7 десятичных | ≈ 15–16 десятичных |
| Где используется | Игры, графика, C float |
Python, Java double |
В Python все «обычные» дробные числа — это double (64 бита).
В десятичной системе дробь 1/3 = 0.33333… — бесконечная. В двоичной системе роль «проблемных» дробей играют числа, знаменатель которых не является степенью двойки.
Дробь 0.1 = 1/10 — именно такая.
В двоичном виде 0.1 выглядит так:
0.00011001100110011001100110011…
Это бесконечная периодическая дробь. Компьютер вынужден её обрезать до 52 знаков мантиссы (для double). И вот это обрезание и есть источник всех бед.
Машинный эпсилон (ε) — это минимальное число, которое при прибавлении к единице даёт результат, строго больший 1. Проще говоря, это «предел чувствительности» компьютера к изменениям.
float (32 бита): ε ≈ 1.19 × 10⁻⁷double (64 бита): ε ≈ 2.22 × 10⁻¹⁶Что это значит на практике? Если вы прибавите к числу 1.0 что-то меньшее, чем эпсилон, компьютер «не заметит» этого прибавления.
Машинный эпсилон — это характеристика не задачи, а самого компьютера. Его нельзя улучшить, можно только перейти на более ёмкий тип данных.
Есть операция, которая особенно опасна для точности: вычитание двух близких чисел.
Представьте, что у вас есть два числа:
Разность: x − y = 0.0000001. Но в этом результате все старшие значащие цифры сократились, и на свет «выплыли» младшие разряды, которые на самом деле были зашумлены округлением.
Относительная погрешность результата может оказаться огромной, даже если исходные числа были точными. Это явление называется катастрофической потерей значимости.
Откроем Python и посмотрим, как теория превращается в баги.
a = 0.1
b = 0.2
print(a + b) # Вывод: 0.30000000000000004
print(a + b == 0.3) # Вывод: False!
Что произошло? Числа 0.1 и 0.2 не могут быть точно представлены в двоичной памяти. При их сложении ошибки округления накапливаются.
Это значит, что условие if a + b == 0.3: никогда не сработает. Это одна из самых частых причин багов в финансовых приложениях, играх и научных расчётах.
Вместо точного сравнения нужно сравнивать числа в пределах заданной точности:
import math
a = 0.1
b = 0.2
epsilon = 1e-9 # наша допустимая погрешность
if math.isclose(a + b, 0.3, abs_tol=epsilon):
print("Числа равны в пределах заданной точности!")
else:
print("Числа различаются")
Функция math.isclose() — это стандартный способ сравнивать числа с плавающей точкой в Python. Она учитывает и абсолютную, и относительную погрешность.
s = 0.0
for i in range(1000):
s += 0.1
print(s) # 99.99999999999861 (а не 100.0!)
print(s == 100.0) # False
Мы тысячу раз прибавили 0.1 и ожидали получить 100. Но из-за накопления ошибок округления результат «уплыл».
Это классический пример того, как маленькая погрешность превращается в большую. В реальных программах такие циклы могут выполняться миллионы раз — и ошибка становится критичной.
Запомните золотое правило программиста: никогда не сравнивайте числа типа float и double через оператор ==.
if price == 100.0: # ❌ Никогда не сработает
print("Ровно 100")
epsilon = 1e-9
if abs(price - 100.0) < epsilon: # ✅ Работает всегда
print("Практически 100")
Если вы работаете с финансами, не используйте float и double вообще. В Python есть специальный тип Decimal, который хранит числа как строки десятичных цифр:
from decimal import Decimal
a = Decimal('0.1')
b = Decimal('0.2')
print(a + b) # 0.3 — ровно!
print(a + b == Decimal('0.3')) # True
Именно Decimal используется в банковских системах, бухгалтерии и везде, где важна копейка.
Когда мы выполняем действия над приближёнными числами, погрешности тоже складываются, умножаются и делятся.
В результате сохраняют столько десятичных знаков после запятой, сколько их в числе с наименьшим количеством таких знаков.
Пример: 12.34 + 5.6789 = 18.02 (а не 18.0189).
В результате сохраняют столько значащих цифр, сколько их в числе с наименьшим количеством значащих цифр.
Пример: 12.3 × 4.567 = 56.2 (3 значащих цифры).
На следующей паре мы проведём Практическую работу №1 по теме «Вычисление погрешностей результатов арифметических действий над приближёнными числами» (4 академических часа).
float и Decimal.К практической работе нужно подготовиться: повторить правила действий с приближёнными числами и установить Python на домашний компьютер.
float и double?0.1 + 0.2 не равно 0.3?После изучения этой темы и выполнения практической работы вы должны:
Понимать: почему компьютеры ошибаются и как эти ошибки влияют на реальные программы.
0.1 к 0 тысячу раз и сравнить результат с 100.0. Вывести в консоль разницу.Сегодня мы сделали самый важный шаг в курсе численных методов — поняли, что любое вычисление в компьютере по определению приближённое.
float 32 бита, double 64 бита).float и double через ==.Decimal.Численное решение в ЭВМ = Модель + Метод + Округление.
Программист обязан контролировать все три компонента.
Именно этим мы и будем заниматься весь семестр.
Спасибо за внимание!
До встречи на практической работе №1!
💡 Подсказка: используйте клавиши ← → на клавиатуре для переключения слайдов