Автор
Александр Мяделец
Руководитель команды разработчиков в CodeGym

BigDecimal в Java

Статья из группы Java Developer
участников
Привет! На сегодняшней лекции мы поговорим о больших числах. Нет, о ДЕЙСТВИТЕЛЬНО БОЛЬШИХ. Ранее мы не раз встречали таблицу диапазонов значений для примитивных типов данных. Выглядит она так:
Примитивный тип Размер в памяти Диапазон значений
byte 8 бит от -128 до 127
short 16 бит до -32768 до 32767
char 16 бит от 0 до 65536
int 32 бита от -2147483648 до 2147483647
long 64 бита от -9223372036854775808 до 9223372036854775807
float 32 бита от (2 в степени -149) до ((2-2 в степени -23)*2 в степени 127)
double 64 бита от (-2 в степени 63) до ((2 в степени 63) - 1)
boolean 8 (при использовании в массивах), 32 (при использовании не в массивах) true или false
Если мы говорим о целых числах, наиболее вместительным типом данных является long, а если речь идет о числах с плавающей точкой — double. Но что если нужное нам число настолько велико, что не влезает даже в long? Диапазон возможных значений Long довольно велик, но все-таки ограничен определенным размером — 64 бита. Что нам придумать, если наше Очень Большое Число весит 100 бит? К счастью, ничего придумывать не нужно. В Java для таких случаев были созданы два специальных класса — BigInteger (для целых чисел) и BigDecimal (для чисел с плавающей точкой). В чем же заключается их особенность? Прежде всего в том, у них теоретически нет максимального размера. Теоретически, потому что не бывает компьютеров с бесконечным размером памяти. И если ты создаешь в программе число размером больше, чем размер памяти компьютера, конечно, программа работать не будет. Но такого рода случаи маловероятны. Поэтому можно сказать, что размер чисел BigInteger и BigDecimal практически ничем не ограничен. Для чего используются эти классы? Прежде всего, для вычислений с крайне высокими требованиями к точности. Есть, к примеру, программы, в которых от точности вычислений может зависеть человеческая жизнь (ПО для самолетов и ракет или для медицинского оборудования). Поэтому, если даже 150-й разряд после запятой играет важную роль, 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);

   }
}
Вывод в консоль:

1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
Наше число не изменилось, как и следовало ожидать. Чтобы операция сложения прошла успешно, необходимо создать новый объект и присвоить ему результат сложения.
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);

   }
}
Вывод в консоль:

1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111144444444
Вот, теперь все работает как надо :) Кстати, обратил внимание, как необычно выглядит операция сложения?
BigInteger result = integer.add(BigInteger.valueOf(33333333));
Это еще один важный момент. Классы больших чисел не используют в своей работе операторы +-*/, а предоставляют вместо этого набор методов. Давай ознакомимся с основными из них (полный перечень методов ты, как и всегда, можешь найти в документации Oracle: здесь и здесь).
  1. методы для осуществления арифметических операций: add(), subtract(), multiply(), divide(). Используются для операций сложения, вычитания, умножения и деления соответственно.

  2. 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
    
  3. 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
    

Управление округлением BigDecimal

Эта тема вынесена в отдельный раздел, поскольку округление больших чисел и его настройка — вещь не такая уж простая. Ты можешь установить количество цифр после запятой для числа BigDecimal при помощи метода setScale(). К примеру, мы хотим установить для числа 111.5555555555 точность в три цифры после запятой. Однако мы не сможем передать число 3 в качестве аргумента в метод setScale() и таким образом решить нашу задачу. Как было сказано выше, BigDecimal — это числа для вычислений с повышенной точностью. В текущем виде наше число имеет 10 цифр после запятой. Мы же хотим отбросить 7 из них и оставить только 3. Поэтому кроме числа 3 мы должны передать в качестве параметра режим округления (rounding mode). Всего у BigDecimal существует 8 режимов округления. Немало! Но если тебе в программе понадобится действительно тонкая настройка точности вычислений, у тебя будет для этого все необходимое. Итак, вот какие 8 режимов округления есть в BigDecimal:
  1. ROUND_CEILING — округление в большую сторону

    111.5555555555 -> setScale(3, ROUND_CEILING) -> 111.556
  2. ROUND_DOWN — отбрасывание разряда

    111.5555555555 -> setScale(3, ROUND_DOWN) -> 111.555
  3. ROUND_FLOOR — округление в меньшую сторону

    111.5555555555 -> setScale(3, ROUND_FLOOR) -> 111.555

  4. ROUND_HALF_UP — округление в большую сторону, если число после запятой >= .5

    0.55 -> setScale(1, ROUND_HALF_UP) -> 0.6
    0.54 -> setScale(1, ROUND_HALF_UP) -> 0.5
  5. ROUND_HALF_DOWN — округление в большую сторону, если число после запятой > .5

    0.55 -> setScale(1, ROUND_HALF_DOWN) -> 0.5
    0.56 -> setScale(1, ROUND_HALF_DOWN) -> 0.6
  6. ROUND_HALF_EVEN — округление будет зависеть от цифры слева от запятой. Если цифра слева будет четной, то округление будет произведено вниз, в меньшую сторону. Если цифра слева от запятой нечетная, то округление будет произведено вверх.

    2.5 -> setScale(0, ROUND_HALF_EVEN) -> 2

    Цифра слева от запятой - 2 - четная. Округление происходит вниз. Поскольку нам требуется 0 знаков после запятой, результатом будет 2.

    3.5 -> setScale(0, ROUND_HALF_EVEN) -> 4

    Цифра слева от запятой - 3 - нечетная. Округление происходит вверх. Поскольку нам требуется 0 знаков после запятой, результатом будет 4.

  7. ROUND_UNNECCESSARY — используется в тех случаях, когда в какой-то метод нужно передать режим округления, но число в округлении не нуждается. Если попробовать произвести округление числа при выставленном режиме ROUND_UNNECCESSARY — выброшено исключение ArithmeticException.

    3.51 -> setScale(1, ROUND_UNNECCESSARY) -> ArithmeticException
  8. ROUND_UP — округление в большую сторону.

    111.5551 -> setScale(3, ROUND_UP) -> 111.556

Сравнение больших чисел

Этот вопрос тоже важен. Ты уже помнишь, что для сравнения объектов в 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. Это тот результат, на который мы и рассчитывали! :) На этом наше сегодняшнее занятие окончено. Самое время вернуться к задачам! :)
Комментарии (34)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
Андрей
Уровень 43
20 марта, 09:51
Кто-нибудь может объяснить это: boolean 8 (при использовании в массивах), 32 (при использовании не в массивах)
Igor Petrashevsky
Уровень 47
2 августа 2022, 21:18
где-то внутри кавычек снует запах php
Богдан
Уровень 18
8 июля 2022, 17:31
ROUND_CEILING — округление в большую сторону;
ROUND_UP — округление в большую сторону.
Чем отличаются эти 2 режима округления? Почему их 2?
Evgeny Siganov QA Automation Engineer в Айтеко
24 августа 2022, 08:06
ROUND_CEILING - округление в большую сторону при равной неопределённости, ну т.е. 1.5 так же близко к 1 как и к 2 но будет при этой настройке 2 ROUND_UP - округление будет всегда в большую сторону даже не смотря на логику перевеса, т.е. несмотря на то что 1.4 ближе к 1 при данной настройке будет выбрано 2
Anonymous #2934468
Уровень 30
6 апреля 2022, 16:13
Статья подустарела, на сегодняшний день методы округления устаревшие (depricated). Необходимо пользоваться классом RoundingMode
Roma S
Уровень 29
20 января 2022, 17:42
Я правильно понимаю, если мне нужно иметь именно точные числа, их лучше хранить в типе BigDecimal ? Т.е. в принципе не ипользовать тип double там где нужны точные числа. А сразу всё переводить в BigDecimal и не будет мне горя?
Jerry Backend Developer
29 января 2022, 12:10
В реальной работе на проекте все финансовые филды храним только в BigDecimal.
Алексей
Уровень 35
25 октября 2021, 13:04
Метод compareTo() вернул 0, что означает равенство 1.5 и 1.50. Это довольно неожиданно
LuneFox Java Developer в BIFIT Expert
21 ноября 2021, 17:00
А какой результат ты ожидал?
Roma S
Уровень 29
20 января 2022, 17:44
мы ведь не String сравниваем
Anonymous #2524703
Уровень 28
23 октября 2021, 08:33
Программы от которых может зависить человеческая жизнь - ПО для ракет. Ну да, неправильно сделаем такую, и наша ракет не дай бог промажет по Вашингтону.
Уpовень 302
30 ноября 2021, 11:16
😂
hidden #2641196
Уровень 51
16 августа 2021, 11:11
Нельзя так просто взять и вычислить абсолютное значение
MermaidMan
Уровень 41
1 февраля 2021, 17:28
Профессор, вы уверены, что диапазоны типов float и double именно такие? Специально посмотрел в других источниках, и, как ни странно, информация разнится, но, такой записи нигде не встречал. Например, на оракле у double верхний диапазон (2-2^52)·2^1023, что намного больше указанных вами ((2 в степени 63) - 1). Этот диапазон, кажется для long?
ANDREY TYUNIKOV
Уровень 41
5 марта 2021, 10:38
Так там вроде про Double а не про double )) А примитивы вот по этой ссылке описаны примитивы
Максим
Уровень 17
14 января 2021, 19:09
Что имеется ввиду под уточнением "Обрати внимание: методы не являются статическими!"? Что это значит?
Игорь HDL developer в Y
20 января 2021, 10:07
Значит это то, что для использования этих методов нужно создать экземпляр(объект) класса BigInteger. Далее, для созданного объекта вызывается необходимый метод. Если бы эти методы были Статическими, то для их вызова не требовалось бы создание объекта, а вызывали мы их через имя класса вот так:
BigInteger.max(number1, number2);