Для работы с очень большими числами или для вычислений, требующих высокой точности, в Java используются классы
BigInteger и
BigDecimal. Они незаменимы в финансовых и научных приложениях, где ошибки округления недопустимы, поскольку позволяют работать с числами, которые не помещаются в
long или
double, и обеспечивают полный контроль над точностью. Работают они не как примитивы: число передают строкой, объекты неизменяемы, а вместо операторов
+ - * / используются методы вроде
add() и
multiply().
Кратко
BigInteger и BigDecimal из пакета java.math хранят числа любой длины: практическое ограничение только объем памяти.- Число задают строкой в конструкторе, иначе Java попробует уместить литерал в примитивный тип и не сможет.
- Объекты обоих классов неизменяемы:
add() не меняет число, а возвращает новое, и результат надо куда-то присвоить. - Операторы
+ - * / с ними не работают, вместо них методы add(), subtract(), multiply(), divide(). - У
BigDecimal есть масштаб (scale), и equals() его учитывает: 1.5 и 1.50 окажутся неравны. Сравнивать по значению нужно через compareTo(). - Количество знаков после запятой задает
setScale() вместе с режимом округления из перечисления RoundingMode: режимов восемь.
Почему примитивных типов не хватает
Теперь, когда мы знаем, что это за инструменты, давайте разберемся, почему их создание было необходимо. Все начинается с ограничений, которые есть у стандартных примитивных типов данных в Java. На сегодняшней лекции мы поговорим о больших числах. Нет, о
ДЕЙСТВИТЕЛЬНО БОЛЬШИХ.
Ранее мы не раз встречали таблицу диапазонов значений для примитивных типов данных.
Выглядит она так:
| Примитивный тип |
Размер в памяти |
Диапазон значений |
| byte |
8 бит |
от -128 до 127 |
| short |
16 бит |
от -32768 до 32767 |
| char |
16 бит |
от 0 до 65535 |
| int |
32 бита |
от -2147483648 до 2147483647 |
| long |
64 бита |
от -9223372036854775808
до 9223372036854775807
|
| float |
32 бита |
от (2 в степени -149)
до ((2-2 в степени -23)*2 в степени 127)
|
| double |
64 бита |
от (2 в степени -1074)
до ((2-2 в степени -52)*2 в степени 1023)
|
| boolean |
8 (при использовании в массивах),
32 (при использовании не в массивах)
|
true или false |
Если мы говорим о целых числах, наиболее вместительным типом данных является
long, а если речь идет о числах с плавающей точкой, это
double.
Но что если нужное нам число настолько велико, что не влезает даже в
long?
Диапазон возможных значений Long довольно велик, но все-таки ограничен определенным размером в 64 бита.
Что нам придумать, если наше Очень Большое Число весит 100 бит?
Что такое BigInteger и BigDecimal
К счастью, ничего придумывать не нужно. В Java для таких случаев были созданы два специальных класса:
BigInteger (для целых чисел) и
BigDecimal (для дробных, с точно заданным числом знаков после запятой).
В чем же заключается их особенность?
Прежде всего в том, у них теоретически нет максимального размера. Теоретически, потому что не бывает компьютеров с бесконечным размером памяти. И если ты создаешь в программе число размером больше, чем размер памяти компьютера, конечно, программа работать не будет. Но такого рода случаи маловероятны.
Поэтому можно сказать, что размер чисел
BigInteger и
BigDecimal практически ничем не ограничен.
Для чего используются эти классы?
Прежде всего, для вычислений с крайне высокими требованиями к точности. Есть, к примеру, программы, в которых от точности вычислений может зависеть человеческая жизнь (ПО для самолетов и ракет или для медицинского оборудования).
Поэтому, если даже 150-й разряд после запятой играет важную роль,
BigDecimal это лучший выбор.
Кроме того, довольно часто эти объекты применяются в мире финансов, где точность вычислений вплоть до самых мелких значений тоже крайне важна.
Как создать объект BigInteger или BigDecimal
Как работать с объектами
BigInteger и
BigDecimal и что важно о них помнить? Объекты этих классов создаются вот так:
public class Main {
public static void main(String[] args) {
BigInteger integer = new BigInteger("11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111");
System.out.println(integer);
BigDecimal decimal = new BigDecimal("123.444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444");
System.out.println(decimal);
}
}
Передача строки в качестве параметра это только один из возможных конструкторов.
Здесь мы используем строки, потому что наши числа превышают максимальные значения
long и
double, а как-то ведь надо объяснить компилятору, какое именно число мы хотим получить :)
Просто передать в конструктор число
11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
не выйдет: Java попытается «вместить» переданное число в один из примитивных типов данных, но ни в один из них оно не влезет.
Поэтому использование строки для передачи нужного числа это хороший вариант. Оба класса умеют автоматически извлекать из переданных строк числовые значения.
Большие числа неизменяемы
Еще один важный момент, который необходимо помнить при работе с классами больших чисел:
их объекты являются неизменяемыми (Immutable).
С принципом неизменяемости ты уже хорошо знаком на примере класса
String и
классов-оберток для примитивов (Integer, Long и других).
import java.math.BigInteger;
public class Main {
public static void main(String[] args) {
BigInteger integer = new BigInteger("11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111");
System.out.println(integer);
integer.add(BigInteger.valueOf(33333333));
System.out.println(integer);
}
}
Вывод в консоль:
11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
Наше число не изменилось, как и следовало ожидать. Чтобы операция сложения прошла успешно, необходимо создать новый объект и присвоить ему результат сложения.
import java.math.BigInteger;
public class Main {
public static void main(String[] args) {
BigInteger integer = new BigInteger("11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111");
System.out.println(integer);
BigInteger result = integer.add(BigInteger.valueOf(33333333));
System.out.println(result);
}
}
Вывод в консоль:
11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111144444444
Вот, теперь все работает как надо :)
Кстати, обратил внимание, как необычно выглядит операция сложения?
BigInteger result = integer.add(BigInteger.valueOf(33333333));
Методы вместо операторов
Это еще один важный момент.
Классы больших чисел не используют в своей работе операторы +-*/, а предоставляют вместо этого набор методов.
Давай ознакомимся с основными из них (полный перечень методов ты, как и всегда, можешь найти в документации Oracle:
здесь и
здесь).
методы для осуществления арифметических операций: add(), subtract(), multiply(), divide(). Используются для операций сложения, вычитания, умножения и деления соответственно.
doubleValue(), intValue(), floatValue(), longValue() и т.д. используются для преобразования большого числа к примитивному типу Java. Будь осторожен при их использовании и не забывай про разницу во вместимости!
import java.math.BigInteger;
public class Main {
public static void main(String[] args) {
BigInteger integer = new BigInteger("11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111");
long result = integer.longValue();
System.out.println(result);
}
}
Вывод в консоль:
8198552921648689607
min() и max() позволяют найти минимальное и максимальное значение из двух переданных больших чисел.
Обрати внимание: методы не являются статическими!
import java.math.BigInteger;
public class Main {
public static void main(String[] args) {
BigInteger integer = new BigInteger("11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111");
BigInteger integer2 = new BigInteger("222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222");
System.out.println(integer.max(integer2));
}
}
Вывод в консоль:
222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222
Если свести различия между примитивами и классами больших чисел в одну таблицу, получится так.
| |
long и double |
BigInteger и BigDecimal |
| Размер числа |
фиксированный: 64 бита у long и double |
ограничен только объемом памяти |
| Как записать очень большое число |
не получится, литерал не влезет в тип |
строкой в конструкторе: new BigDecimal("123.444...") |
| Арифметика |
операторы + - * / |
методы add(), subtract(), multiply(), divide() |
| Изменяемость |
значение переменной меняется на месте |
объекты неизменяемы, метод возвращает новый объект |
| Сравнение |
== для примитивов |
compareTo() по значению, а equals() учитывает еще и масштаб |
| Где применяют |
обычные вычисления, где хватает точности |
финансы, научные расчеты, все, где ошибка округления недопустима |
Управление округлением BigDecimal
Эта тема вынесена в отдельный раздел, поскольку
округление больших чисел и его настройка это вещь не такая уж простая.
Ты можешь установить количество цифр после запятой для числа
BigDecimal при помощи метода
setScale().
К примеру, мы хотим установить для числа
111.5555555555 точность в три цифры после запятой.
Одного числа 3 тут мало: перегрузка с единственным аргументом существует, но когда округление действительно требуется, она бросает
ArithmeticException.
Как было сказано выше,
BigDecimal это числа для вычислений с повышенной точностью.
В текущем виде наше число имеет 10 цифр после запятой. Мы же хотим отбросить 7 из них и оставить только 3. Поэтому кроме числа 3 мы должны передать в качестве параметра режим округления (
rounding mode).
Всего у BigDecimal существует 8 режимов округления. Немало! Но если тебе в программе понадобится действительно тонкая настройка точности вычислений, у тебя будет для этого все необходимое. Задаются они значениями перечисления
RoundingMode. Раньше на их месте были целочисленные константы вида
BigDecimal.ROUND_HALF_UP, но с Java 9 они объявлены устаревшими, так что в новом коде используйте перечисление. Итак, вот какие 8 режимов округления есть у
BigDecimal:
| Режим |
Что делает |
Пример |
RoundingMode.CEILING |
округление к плюс бесконечности; на положительных числах это в большую сторону |
111.5555555555 -> setScale(3) -> 111.556
-111.5555555555 -> setScale(3) -> -111.555 |
RoundingMode.DOWN |
округление к нулю: лишние разряды просто отбрасываются |
111.5555555555 -> setScale(3) -> 111.555
-111.5555555555 -> setScale(3) -> -111.555 |
RoundingMode.FLOOR |
округление к минус бесконечности; на положительных числах это в меньшую сторону |
111.5555555555 -> setScale(3) -> 111.555
-111.5555555555 -> setScale(3) -> -111.556 |
RoundingMode.HALF_UP |
к ближайшему значению, а если отбрасывается ровно половина, то вверх |
0.55 -> setScale(1) -> 0.6
0.54 -> setScale(1) -> 0.5 |
RoundingMode.HALF_DOWN |
к ближайшему значению, а если отбрасывается ровно половина, то вниз |
0.55 -> setScale(1) -> 0.5
0.56 -> setScale(1) -> 0.6 |
RoundingMode.HALF_EVEN |
к ближайшему значению, а если отбрасывается ровно половина, то к четной цифре в позиции округления |
2.5 -> setScale(0) -> 2
3.5 -> setScale(0) -> 4 |
RoundingMode.UNNECESSARY |
утверждение, что округление не требуется. Если оно все же понадобится, будет выброшено исключение ArithmeticException |
3.51 -> setScale(1) -> ArithmeticException |
RoundingMode.UP |
округление от нуля: по модулю всегда в большую сторону |
111.5551 -> setScale(3) -> 111.556
-111.5551 -> setScale(3) -> -111.556 |
Обрати внимание на две пары.
CEILING и
UP на положительных числах ведут себя одинаково, а на отрицательных расходятся: первый тянет к плюс бесконечности, второй уводит от нуля. Точно так же различаются
FLOOR и
DOWN.
И еще про
HALF_EVEN: от
HALF_UP и
HALF_DOWN он отличается только тогда, когда отбрасывается ровно половина. В примере с
2.5 соседи это 2 и 3, четный из них 2, поэтому результатом будет 2. В примере с
3.5 соседи 3 и 4, четный это 4, поэтому результатом будет 4.
Сравнение больших чисел
Этот вопрос тоже важен.
Ты уже помнишь, что для сравнения объектов в Java используется метод
equals(). Он либо предоставляется самим языком (в случае встроенных в Java классов), либо переопределяется программистом.
Но в случае с объектами классов
BigDecimal использовать метод
equals() для сравнения не рекомендуется.
Причина этого в том, что метод
BigDecimal.equals() двух чисел возвращает
true только в случае, если
2 числа имеют одинаковое значение и масштаб (scale):
Давай сравним поведение методов
equals() у
Double и
BigDecimal:
import java.math.BigDecimal;
public class Main {
public static void main(String[] args) {
Double a = 1.5;
Double b = 1.50;
System.out.println(a.equals(b));
BigDecimal x = new BigDecimal("1.5");
BigDecimal y = new BigDecimal("1.50");
System.out.println(x.equals(y));
}
}
Вывод в консоль:
true
false
Как видишь, числа 1.5 и 1.50 в случае с
BigDecimal оказались неравны! Это произошло именно из-за специфики работы метода
equals(), в классе
BigDecimal.
Для более корректного сравнения двух
BigDecimal лучше использовать
метод compareTo():
import java.math.BigDecimal;
public class Main {
public static void main(String[] args) {
BigDecimal x = new BigDecimal("1.5");
BigDecimal y = new BigDecimal("1.50");
System.out.println(x.compareTo(y));
}
}
Вывод в консоль:
0
Метод
compareTo() вернул 0, что означает равенство 1.5 и 1.50.
Это тот результат, на который мы и рассчитывали! :)
На этом наше сегодняшнее занятие окончено. Самое время вернуться к задачам! :)
Вопросы и ответы
Чем BigDecimal отличается от double?
double занимает фиксированные 64 бита и хранит число приближенно, поэтому на дробях вроде 0.1 накапливается погрешность.
BigDecimal устроен иначе: он хранит целое значение и масштаб, то есть количество знаков после запятой. Длина ограничена только памятью, а точность задается явно. Платой становятся скорость и отсутствие операторов.
Почему число для BigDecimal передают строкой?
Потому что литерал сначала обрабатывает компилятор Java, и он обязан уместить его в один из примитивных типов. Число из полутора сотен цифр не влезет ни в
long, ни в
double, так что до конструктора оно просто не дойдет. Строка этой проверки избегает, а разбирают ее уже сами классы больших чисел.
Почему после вызова add() число не изменилось?
Потому что объекты
BigInteger и
BigDecimal неизменяемы, как
String и классы-обертки. Метод
add() ничего не меняет в исходном объекте, он возвращает новый. Результат нужно присвоить:
BigInteger result = integer.add(...).
Почему 1.5 и 1.50 в BigDecimal не равны?
Потому что
equals() у
BigDecimal сравнивает не только значение, но и масштаб. У
1.5 масштаб равен единице, у
1.50 двум, поэтому объекты считаются разными. Если нужно сравнить именно числа, берите
compareTo(): для этой пары он вернет 0.
Как задать количество знаков после запятой у BigDecimal?
Методом
setScale(). Если округление действительно нужно, одного числа мало: вторым аргументом передают режим округления, например
setScale(3, RoundingMode.HALF_UP). Метод не меняет исходный объект, а возвращает новый с нужным масштабом.
Почему константы BigDecimal.ROUND_* больше не используют?
Они объявлены устаревшими начиная с Java 9. В документации Oracle у каждой из них написано «Deprecated. Use RoundingMode.X instead», и вместе с ними устарел метод
setScale(int, int). Вместо целочисленных констант теперь используется перечисление
RoundingMode с теми же восемью значениями: код читается лучше, а компилятор проверяет тип.
Читайте также