PHP: Числа с плавающей точкой
В математике есть разные типы чисел. Например:
- Натуральные: целые положительные числа: 1, 2, 3 и т.д.
- Рациональные: дробные числа, которые можно представить в виде деления, например: 0.5, 1.75, 3.14.
С точки зрения математики все просто. Но с точки зрения компьютера между этими типами чисел лежит настоящая пропасть. Попробуйте мысленно решить: сколько будет 0.2 + 0.1? Очевидно, 0.3. PHP при выводе покажет то же самое:
<?php
print_r(0.1 + 0.2); // => 0.3Кажется, все в порядке. Но проведем проверку: вычтем из этой суммы 0.3. Математически должен получиться ровно ноль. А вот что скажет PHP:
<?php
print_r(0.1 + 0.2 - 0.3); // => 5.5511151231258E-17Вместо нуля мы получаем крошечный «хвостик» — число в экспоненциальной записи, равное примерно 0.000000000000000055. Дело в том, что внутри PHP хранит не ровно 0.3, а приближение 0.30000000000000004. При выводе число округляется до 14 значащих цифр, поэтому погрешность не видна. Но в самих вычислениях она никуда не девается.
Ожидание: 0.1 + 0.2 → 0.3
Реальность: 0.1 + 0.2 → 0.30000000000000004
└── погрешность хранения
Проверка: 0.1 + 0.2 - 0.3 → 5.5511151231258E-17 (а не 0)Почему так происходит?
Такое поведение характерно для Python, JavaScript и большинства других языков программирования.
Причина в устройстве компьютера. Компьютер работает с ограниченной памятью, тогда как рациональные числа бесконечно точны. Между 0.1 и 0.2 можно поместить бесконечно много других чисел. Но компьютер не может хранить бесконечность. Он приближает число, стараясь уместить его в доступное количество бит.
Такие приближенные значения называются числами с плавающей точкой (floating point numbers). Хранение и вычисления с ними подчиняются строгим правилам, описанным в специальном стандарте IEEE 754, на который ориентируются большинство языков программирования.
Когда появляются такие числа
Числа с плавающей точкой появляются в программах чаще, чем может показаться. Вот основные случаи:
- Когда вы явно пишете дробное число, например 0.1, 2.5, 3.14.
- Когда выполняете деление с дробным результатом:
<?php
print_r(1 / 2); // => 0.5
print_r(2 / 3); // => 0.66666666666667Даже если результат кажется «красивым», внутри он все равно представлен в виде приближенного значения. Некоторые дроби, такие как 1 / 3, вообще не могут быть точно представлены в двоичной системе, поэтому их точность всегда ограничена.
Где это критично и как с этим работают
Обычно небольшая погрешность не мешает. Но в финансовых расчетах, научных и инженерных задачах, а также при точном сравнении результатов она может стать проблемой. Например, ошибка в доли копейки способна дать неправильную итоговую сумму, а длинная цепочка вычислений может постепенно накапливать неточность.
В реальных программах с этим работают по-разному. Деньги часто хранят в минимальных единицах, например в копейках, то есть используют целые числа вместо дробных. В других случаях результат округляют до нужного количества знаков, сравнивают числа с допустимой погрешностью или используют специальные типы данных и библиотеки для точных вычислений.
Что нужно запомнить
Операции с числами с плавающей точкой не всегда точны, и это нормально. Такое поведение характерно для большинства языков программирования и объясняется устройством компьютерной памяти. PHP при выводе округляет числа, поэтому погрешность не всегда видна на экране — но в вычислениях она присутствует. Точность можно контролировать, например с помощью округления или сравнения чисел с заданной погрешностью. А при работе с деньгами, точными измерениями или научными расчетами лучше использовать специальные типы данных, которые обеспечивают контроль над точностью.
Задание
Вы оплачиваете два товара в интернет-магазине: первый стоит 0.1 рубля, второй — 0.2 рубля. Кассовая программа должна проверить точность расчетов: вычесть из итоговой суммы ожидаемые 0.3 рубля.
Рассчитайте и выведите эту разницу. Ожидаете увидеть ноль — но PHP выдаст кое-что интересное.
Полезное
PHP: Числа с плавающей точкой
В математике есть разные типы чисел. Например:
- Натуральные: целые положительные числа: 1, 2, 3 и т.д.
- Рациональные: дробные числа, которые можно представить в виде деления, например: 0.5, 1.75, 3.14.
С точки зрения математики все просто. Но с точки зрения компьютера между этими типами чисел лежит настоящая пропасть. Попробуйте мысленно решить: сколько будет 0.2 + 0.1? Очевидно, 0.3. PHP при выводе покажет то же самое:
<?php
print_r(0.1 + 0.2); // => 0.3Кажется, все в порядке. Но проведем проверку: вычтем из этой суммы 0.3. Математически должен получиться ровно ноль. А вот что скажет PHP:
<?php
print_r(0.1 + 0.2 - 0.3); // => 5.5511151231258E-17Вместо нуля мы получаем крошечный «хвостик» — число в экспоненциальной записи, равное примерно 0.000000000000000055. Дело в том, что внутри PHP хранит не ровно 0.3, а приближение 0.30000000000000004. При выводе число округляется до 14 значащих цифр, поэтому погрешность не видна. Но в самих вычислениях она никуда не девается.
Ожидание: 0.1 + 0.2 → 0.3
Реальность: 0.1 + 0.2 → 0.30000000000000004
└── погрешность хранения
Проверка: 0.1 + 0.2 - 0.3 → 5.5511151231258E-17 (а не 0)Почему так происходит?
Такое поведение характерно для Python, JavaScript и большинства других языков программирования.
Причина в устройстве компьютера. Компьютер работает с ограниченной памятью, тогда как рациональные числа бесконечно точны. Между 0.1 и 0.2 можно поместить бесконечно много других чисел. Но компьютер не может хранить бесконечность. Он приближает число, стараясь уместить его в доступное количество бит.
Такие приближенные значения называются числами с плавающей точкой (floating point numbers). Хранение и вычисления с ними подчиняются строгим правилам, описанным в специальном стандарте IEEE 754, на который ориентируются большинство языков программирования.
Когда появляются такие числа
Числа с плавающей точкой появляются в программах чаще, чем может показаться. Вот основные случаи:
- Когда вы явно пишете дробное число, например 0.1, 2.5, 3.14.
- Когда выполняете деление с дробным результатом:
<?php
print_r(1 / 2); // => 0.5
print_r(2 / 3); // => 0.66666666666667Даже если результат кажется «красивым», внутри он все равно представлен в виде приближенного значения. Некоторые дроби, такие как 1 / 3, вообще не могут быть точно представлены в двоичной системе, поэтому их точность всегда ограничена.
Где это критично и как с этим работают
Обычно небольшая погрешность не мешает. Но в финансовых расчетах, научных и инженерных задачах, а также при точном сравнении результатов она может стать проблемой. Например, ошибка в доли копейки способна дать неправильную итоговую сумму, а длинная цепочка вычислений может постепенно накапливать неточность.
В реальных программах с этим работают по-разному. Деньги часто хранят в минимальных единицах, например в копейках, то есть используют целые числа вместо дробных. В других случаях результат округляют до нужного количества знаков, сравнивают числа с допустимой погрешностью или используют специальные типы данных и библиотеки для точных вычислений.
Что нужно запомнить
Операции с числами с плавающей точкой не всегда точны, и это нормально. Такое поведение характерно для большинства языков программирования и объясняется устройством компьютерной памяти. PHP при выводе округляет числа, поэтому погрешность не всегда видна на экране — но в вычислениях она присутствует. Точность можно контролировать, например с помощью округления или сравнения чисел с заданной погрешностью. А при работе с деньгами, точными измерениями или научными расчетами лучше использовать специальные типы данных, которые обеспечивают контроль над точностью.
Задание
Вы оплачиваете два товара в интернет-магазине: первый стоит 0.1 рубля, второй — 0.2 рубля. Кассовая программа должна проверить точность расчетов: вычесть из итоговой суммы ожидаемые 0.3 рубля.
Рассчитайте и выведите эту разницу. Ожидаете увидеть ноль — но PHP выдаст кое-что интересное.
Полезное
Ваше упражнение проверяется по этим тестам
<?php
namespace HexletBasics\Arithmetics\FloatingPointNumbers;
use HexletBasics\Exercise\TestCase;
class SolutionTest extends TestCase
{
public function test()
{
$expected = '5.5511151231258E-17';
$this->assertOutput($expected);
}
}Решение учителя откроется через:
20:00
