Java: Числа с плавающей точкой
В математике есть разные виды чисел. Например:
- натуральные, это целые числа от 1 и больше
- рациональные, это числа с точкой, например 0.5, 1.75, 2.25
С точки зрения математики тут все понятно. А вот с точки зрения компьютера между этими видами чисел лежит пропасть. Попробуйте в уме сложить 0.2 и 0.1. Кажется, что выйдет 0.3. Вот что на это скажет Java:
System.out.println(0.2 + 0.1); // 0.30000000000000004Вместо привычных 0.3 получается 0.30000000000000004.
Ожидание: 0.1 + 0.2 → 0.3
Реальность: 0.1 + 0.2 → 0.30000000000000004
└── погрешность храненияТот же результат выдадут JavaScript, C++ и почти все остальные языки.
Почему так происходит
Причина в устройстве компьютера. Память конечна, а рациональных чисел бесконечно много. Между 0.1 и 0.2 помещается бесконечно много других чисел, и сохранить их все компьютер не может. Поэтому он приближает число, стараясь уместить его в доступные биты.
С целыми числами этой беды нет, их ограничивают сверху. У самого большого целого числа в Java даже есть имя:
System.out.println(Integer.MAX_VALUE); // 2147483647За записью Integer.MAX_VALUE прячется крайнее значение, дальше которого обычное целое число в Java не уходит.
С рациональными числами такой фокус не проходит, ведь они не выстроены в ровную цепочку. Приближенные значения называют числами с плавающей точкой (floating point numbers). Хранение и вычисления с ними подчиняются строгому стандарту IEEE 754, на который опирается большинство языков.
Когда появляются такие числа
Числа с плавающей точкой встречаются чаще, чем кажется. Вот два основных случая.
В первом случае вы сами пишете число с точкой, например 0.1, 2.5 или 3.14.
Во втором случае в делении участвует дробное число:
System.out.println(1.0 / 2); // 0.5
System.out.println(2.0 / 3); // 0.6666666666666666Тут стоит вспомнить про целочисленное деление. Когда оба операнда целые, Java делит нацело, и 1 / 2 даст 0. Достаточно сделать хотя бы один операнд дробным, и результат становится числом с плавающей точкой. Даже когда результат выглядит красиво, внутри он все равно хранится приближенно. Некоторые дроби, например 1.0 / 3, в двоичной системе вообще не записать точно.
Где это критично
Обычно маленькая погрешность не мешает. Но в финансовых расчетах, инженерных задачах и при точном сравнении чисел она превращается в проблему. Ошибка в доли копейки портит итоговую сумму, а длинная цепочка вычислений постепенно накапливает неточность.
В реальных программах с этим борются по-разному. Деньги часто хранят в минимальных единицах, например в копейках, то есть в целых числах. Результат округляют до нужного количества знаков. Числа сравнивают с небольшим допуском. Для точных вычислений берут специальные типы данных.
Что нужно запомнить
Операции с числами с плавающей точкой не всегда точны, и это нормально. Так ведет себя большинство языков программирования, и причина в устройстве памяти. Точность можно контролировать округлением или сравнением с допуском. А для денег и научных расчетов лучше сразу брать специальные типы данных.
Задание
Вычислите и выведите на экран произведение двух чисел: 0.39 и 0.22
Полезное
Java: Числа с плавающей точкой
В математике есть разные виды чисел. Например:
- натуральные, это целые числа от 1 и больше
- рациональные, это числа с точкой, например 0.5, 1.75, 2.25
С точки зрения математики тут все понятно. А вот с точки зрения компьютера между этими видами чисел лежит пропасть. Попробуйте в уме сложить 0.2 и 0.1. Кажется, что выйдет 0.3. Вот что на это скажет Java:
System.out.println(0.2 + 0.1); // 0.30000000000000004Вместо привычных 0.3 получается 0.30000000000000004.
Ожидание: 0.1 + 0.2 → 0.3
Реальность: 0.1 + 0.2 → 0.30000000000000004
└── погрешность храненияТот же результат выдадут JavaScript, C++ и почти все остальные языки.
Почему так происходит
Причина в устройстве компьютера. Память конечна, а рациональных чисел бесконечно много. Между 0.1 и 0.2 помещается бесконечно много других чисел, и сохранить их все компьютер не может. Поэтому он приближает число, стараясь уместить его в доступные биты.
С целыми числами этой беды нет, их ограничивают сверху. У самого большого целого числа в Java даже есть имя:
System.out.println(Integer.MAX_VALUE); // 2147483647За записью Integer.MAX_VALUE прячется крайнее значение, дальше которого обычное целое число в Java не уходит.
С рациональными числами такой фокус не проходит, ведь они не выстроены в ровную цепочку. Приближенные значения называют числами с плавающей точкой (floating point numbers). Хранение и вычисления с ними подчиняются строгому стандарту IEEE 754, на который опирается большинство языков.
Когда появляются такие числа
Числа с плавающей точкой встречаются чаще, чем кажется. Вот два основных случая.
В первом случае вы сами пишете число с точкой, например 0.1, 2.5 или 3.14.
Во втором случае в делении участвует дробное число:
System.out.println(1.0 / 2); // 0.5
System.out.println(2.0 / 3); // 0.6666666666666666Тут стоит вспомнить про целочисленное деление. Когда оба операнда целые, Java делит нацело, и 1 / 2 даст 0. Достаточно сделать хотя бы один операнд дробным, и результат становится числом с плавающей точкой. Даже когда результат выглядит красиво, внутри он все равно хранится приближенно. Некоторые дроби, например 1.0 / 3, в двоичной системе вообще не записать точно.
Где это критично
Обычно маленькая погрешность не мешает. Но в финансовых расчетах, инженерных задачах и при точном сравнении чисел она превращается в проблему. Ошибка в доли копейки портит итоговую сумму, а длинная цепочка вычислений постепенно накапливает неточность.
В реальных программах с этим борются по-разному. Деньги часто хранят в минимальных единицах, например в копейках, то есть в целых числах. Результат округляют до нужного количества знаков. Числа сравнивают с небольшим допуском. Для точных вычислений берут специальные типы данных.
Что нужно запомнить
Операции с числами с плавающей точкой не всегда точны, и это нормально. Так ведет себя большинство языков программирования, и причина в устройстве памяти. Точность можно контролировать округлением или сравнением с допуском. А для денег и научных расчетов лучше сразу брать специальные типы данных.
Задание
Вычислите и выведите на экран произведение двух чисел: 0.39 и 0.22
Полезное
Ваше упражнение проверяется по этим тестам
import static org.assertj.core.api.Assertions.assertThat;
import java.io.ByteArrayOutputStream;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.PrintStream;
class AppTest {
public static void main(String[] args) {
final var expected = "0.0858";
ByteArrayOutputStream out = new ByteArrayOutputStream();
System.setOut(new PrintStream(out));
App.main(null);
final var actual = out.toString().trim();
System.setOut(new PrintStream(new FileOutputStream(FileDescriptor.out)));
System.out.println(actual);
assertThat(actual).isEqualTo(expected);
}
}Решение учителя откроется через:
20:00
