Числа в Java
Работа с числовыми типами в Java — int, long, float, double — точность, переполнение и класс Number.
Java различает целые и вещественные числа, и в каждом семействе есть несколько размеров. Выбор правильного типа важен как для корректности (точность, диапазон), так и для производительности. Эта глава — сжатый обзор числовых типов, их литералов, преобразований и подводных камней, которые поджидают начинающих. Предполагается, что вы уже знаете, как объявить переменную, и знакомы с полным набором примитивных типов данных.
Числовые примитивы
| Тип | Биты | Диапазон | Пример литерала |
|---|---|---|---|
byte | 8 | -128 до 127 | byte b = 10; |
short | 16 | -32 768 до 32 767 | short s = 30_000; |
int | 32 | -2³¹ до 2³¹-1 (≈ ±2,1 млрд) | int i = 1_000_000; |
long | 64 | -2⁶³ до 2⁶³-1 | long l = 9_000L; |
float | 32 | IEEE 754 одинарной точности (~7 цифр) | float f = 3.14f; |
double | 64 | IEEE 754 двойной точности (~16 цифр) | double d = 3.14; |
В повседневном Java:
- Используйте
intдля счётчиков, индексов и большинства целых значений. - Используйте
long, когда значение может превысить ~2 миллиарда (размеры файлов, миллисекунды с начала эпохи, генераторы идентификаторов). - Используйте
doubleдля вычислений с плавающей точкой. - Используйте
floatтолько при нехватке памяти или когда этого требует API.
Целочисленные литералы
Обычные цифры — это int:
int answer = 42;
int million = 1_000_000; // underscores are ignoredСуффикс L (предпочтительно в верхнем регистре) для long:
long big = 9_000_000_000L;Префикс 0x для шестнадцатеричного, 0b для двоичного, 0 для восьмеричного:
int hex = 0xFF; // 255
int bin = 0b1010; // 10
int oct = 0755; // 493 — be careful, easy to write by accident(Восьмеричные литералы — частый источник ошибок: 0123 — это не 123, а 83. Избегайте ведущих нулей, если вы действительно не имеете в виду восьмеричное число.)
Литералы с плавающей точкой
Обычные десятичные числа — это double:
double pi = 3.14159;
double e = 2.71828;Суффикс f для float, d для явного double (необязательно):
float pif = 3.14f;
double pid = 3.14d;Научная нотация тоже работает:
double avogadro = 6.022e23;
double tiny = 1.0e-9;Преобразование числовых типов
Java выполняет расширяющее преобразование автоматически, когда меньший тип переходит в больший (данные не теряются):
int x = 7;
double promoted = x; // 7.0 — int widens to double automaticallyОбратное направление — сужающее: значение может не поместиться, поэтому Java требует явного приведения и молча отбрасывает то, что не умещается. Усечение, а не округление:
double d = 9.99;
int i = (int) d; // 9 — the fraction is dropped, never rounded
long big = 9_000_000_000L;
int truncated = (int) big; // 410065408 — high bits lost, value is garbageПоэтому сужайте тип только тогда, когда уверены, что значение поместится. Чтобы округлить double до ближайшего целого вместо усечения, используйте Math.round (см. Java Math).
Преобразования также происходят внутри выражений. Целочисленное деление отбрасывает остаток, что удивляет начинающих:
System.out.println(7 / 2); // 3 — both operands are int, so int division
System.out.println(7 / 2.0); // 3.5 — one double operand promotes the whole expressionПолные правила явного приведения типов см. в Java Type Casting.
Переполнение
Целочисленная арифметика молча оборачивается по границам типа. Исключение не выбрасывается:
int max = Integer.MAX_VALUE;
System.out.println(max + 1); // -2147483648 (Integer.MIN_VALUE)Для арифметики с проверкой переполнения используйте семейство Math.exact:
Math.addExact(Integer.MAX_VALUE, 1); // throws ArithmeticException
Math.multiplyExact(100_000, 100_000); // throws tooПолезно, когда корректность важнее производительности — финансовые расчёты, размеры выделяемой памяти и т. д.
Точность чисел с плавающей точкой
double и float используют формат IEEE 754 с двоичным представлением — они не могут точно представить большинство десятичных дробей:
System.out.println(0.1 + 0.2); // 0.30000000000000004Никогда не сравнивайте числа с плавающей точкой с помощью ==. Сравнивайте с допуском:
double diff = Math.abs(a - b);
if (diff < 1e-9) { /* close enough */ }Для денежных сумм или в любом месте, где нужна точная десятичная арифметика, используйте BigDecimal:
import java.math.BigDecimal;
BigDecimal sum = new BigDecimal("0.1").add(new BigDecimal("0.2"));
System.out.println(sum); // 0.3Всегда передавайте строки в new BigDecimal(...). Конструктор с double заново внесёт двоичную неточность.
Специальные значения с плавающей точкой
У double есть три значения, которые не являются обычными числами:
double posInf = Double.POSITIVE_INFINITY;
double negInf = Double.NEGATIVE_INFINITY;
double nan = Double.NaN;
System.out.println(1.0 / 0); // Infinity
System.out.println(-1.0 / 0); // -Infinity
System.out.println(0.0 / 0); // NaN
System.out.println(nan == nan); // false — NaN compares unequal to everything
System.out.println(Double.isNaN(nan)); // trueИспользуйте Double.isNaN(x) для проверки на NaN; == не сработает.
Классы-обёртки
Для каждого примитива есть соответствующая обёртка: Byte, Short, Integer, Long, Float, Double. Обёртки расширяют абстрактный класс Number. Они нужны, когда:
- Используются коллекции, хранящие объекты:
List<Integer>,Map<String, Double>. - Выполняется разбор строк:
Integer.parseInt("42"),Double.parseDouble("3.14"). - Выполняется преобразование в другой числовой тип через
.intValue(),.doubleValue()и т. д.
Java автоматически выполняет автоупаковку между примитивом и обёрткой:
List<Integer> ids = new ArrayList<>();
ids.add(42); // autobox: int → Integer
int first = ids.get(0); // unbox: Integer → intBigInteger и BigDecimal
Когда числа могут быть больше long или необходима десятичная точность:
import java.math.BigInteger;
BigInteger huge = new BigInteger("123456789012345678901234567890");
huge.multiply(huge);
BigDecimal price = new BigDecimal("19.95");
BigDecimal tax = price.multiply(new BigDecimal("0.07"));Они медленнее примитивов, но в случаях, когда они нужны, это оправданный компромисс.
Демонстрация
Что дальше
- Java Math —
Math.abs,Math.round,Math.powи остальные инструменты числовых вычислений. - Java Operators — как арифметические, сравнительные и побитовые операторы работают с этими типами.
- Java Booleans — двузначный тип Java, намного проще в объяснении.