JavaRush /Java блог /Java Developer /BigInteger и BigDecimal
Автор
Pavlo Plynko
Java-разработчик в CodeGym

BigInteger и BigDecimal

Статья из группы Java Developer
В языке Java приложение состоит из классов, а классы — из методов и переменных. Переменные, в свою очередь, делятся на примитивные и ссылочные. BigInteger и BigDecimal - 1 В Java есть 8 видов переменных и, если не считать boolean и char, они делятся на такие типы:
  • целые: byte, short, int и long;
  • с плавающей точкой (также называют вещественными числами): float и double.
Внутри этих небольших групп различия существуют только в диапазоне вмещаемых значений (и соответственно, разнится занимаемое место такой переменной). Самый большой из целочисленных типов — long, с диапазоном от -9223372036854775808 до 9223372036854775807. Из чисел с плавающей точкой — double, с диапазоном от 1.7e-308 до 1.7e+308. Подробнее о вещественных числах можно почитать в этой статье. Но что если нам понадобится сохранять числа, которые превышают допустимый диапазон? В таком случае нам пригодятся BigInteger и BigDecimal.

BigInteger в Java

Класс Java BigInteger используется как аналог целочисленных значений произвольной длины, у которого нет ограничения в 64 битов, как например у long. При этом он потомок класса Number, как и стандартные обертки для числовых простых типов — Integer, Long, Byte, Double и так далее — поэтому имеет реализации методов, приводящих к простым типам:

BigInteger value = new BigInteger("32145");

int intValue = value.intValue();//32145

long longValue = value.longValue();//32145

double doubleValue = value.doubleValue();//32145.0
Тут же мы видим создание такого объекта BigInteger с передачей в конструктор нашего значения, но в формате строки. Стоит отметить, что конструктор у него не один, а на все случаи жизни. Если простые типы не вмещают полный объем данных из BigInteger, данные будут урезаны до диапазона этого примитивного типа. Но при этом есть аналоги этих методов (intValueExact(), longValueExact() и т. д.), с той только разницей, что если простой тип, в который идет преобразование, не вмещает диапазон данных, выбрасывается ArithmeticException.

Константы BigInteger

Для внутреннего пользования, у класса есть константы:

BigInteger.ZERO
BigInteger.ONE
BigInteger.TEN
Это константные объекты BigInteger со значениями, соответственно, 0, 1 и 10.

Методы BigInteger

Одна из главных особенностей данного класса — он полон методов, реализующих стандартные арифметические операции на Java. Например:
  • операции суммирования:

    
    BigInteger firstValue = new BigInteger("37995");
    BigInteger secondValue = new BigInteger("35466");
    BigInteger resultValue =  firstValue.add(secondValue);//73461
    
  • операции умножения:

    
    BigInteger firstValue = new BigInteger("37995");
    BigInteger secondValue = new BigInteger("35466");
    BigInteger resultValue =  firstValue.multiply(secondValue);//1347530670
    
  • операции нахождения остатка при делении одного числа на другое:

    
    BigInteger firstValue = new BigInteger("37995");
    BigInteger secondValue = new BigInteger("35466");
    BigInteger resultValue =  firstValue.remainder(secondValue);//2529
    
  • получение абсолютного значения числа (то есть по модулю, без знака):

    
    BigInteger firstValue = new BigInteger("-37995");
    BigInteger resultValue =  firstValue.abs();//37995
    
Также присутствуют методы для более сложных (специфических) операций:
  • операции с вычислением mod:

    
    BigInteger firstValue = new BigInteger("-34");
    BigInteger secondValue = new BigInteger("5");
    BigInteger resultValue = firstValue.mod(secondValue); //1
    
Существуют несколько разных вариаций данной функции:
  • получение рандомного числа с заданием количества битов, которое будет использовать полученное значение:

    
    BigInteger firstValue = BigInteger.probablePrime(8, new Random());//211
    BigInteger secondValue = BigInteger.probablePrime(16, new Random());//42571
    
  • операции побитовых сдвигов(this >> n)

    Сдвиг влево:

    
    BigInteger firstValue = new BigInteger("5");
    BigInteger firstResultValue = firstValue.shiftLeft(3);//40
    

    Сдвиг вправо:

    
    BigInteger secondValue = new BigInteger("34");
    BigInteger secondResultValue = secondValue.shiftRight(2); //8
    
Конечно же, полный перечень методов лучше посмотреть в документации. BigInteger и BigDecimal - 2

BigDecimal в Java

Когда нам нужно вещественное число произвольной длины, используется класс Java — BigDecimal. Как правило его применяют для работы с финансами вместо double, так как он дает больше возможности настройки. Как и BigInteger, BigDecimal является потомком класса Number и имеет методы, возвращающие значение объекта в виде определенного примитивного типа:

BigDecimal value = new BigDecimal(35563.3);

long longValue = value.longValue();//35563

double doubleValue = value.doubleValue();//35563.3
Как мы можем видеть при приведении к long, остаётся только целая часть, а знаки после запятой отбрасываются.

Конструкторы BigDecimal

С конструкторами BigDecimal мы ознакомимся подробнее, так как у класса есть их гораздо более широкий выбор. Есть конструкторы, позволяющие задать значение объекта разными способами (передачей int, long, double, String и даже BigInteger), а есть такие, которые позволяю. задавать настройки создаваемого объекта (способы округления, количество знаков после запятой):

BigDecimal firstValue = new BigDecimal("455656.545");//455656.545
Тут всё понятно, мы непосредственно задали значение и количество знаков после запятой, которые хотим видеть.

BigDecimal secondValue = new BigDecimal(3445.54);//3445.5399999999999636202119290828704833984375
Результаты этого конструктора могут быть весьма непредсказуемыми, ведь мы задаем double, который по своей природе весьма неоднозначный тип. Поэтому обычно рекомендуется использовать в конструкторе String.

BigDecimal thirdValue = new BigDecimal(3445.554645675444, MathContext.DECIMAL32);//3445.555
Мы задаем double, но при этом и задаём параметр, описывающий правило округления (который содержит количество знаков после запятой и алгоритм для округления).

char[] arr = new String("455656.545").toCharArray();

BigDecimal fourthValue = new BigDecimal(arr, 2, 6);//5656.5
Задаем массив знаков, с которого элемента берём значения для объекта и сколько этих элементов берём.

BigDecimal fifthValue = new BigDecimal(new BigInteger("44554"), 3);//44.554
Берем уже существующий объект BigInteger, задаём какое количество знаков после запятой.

Методы BigDecimal

Класс BigDecimal также содержит в себе методы для разнообразных арифметических операций, но методов работы с битами, как у BigInteger, у него нет. Но тем не менее, главная фишка BigDecimal — гибкость в работе с числами с плавающей запятой. Давайте рассмотрим некоторые методы, которые дают нам возможность властвовать над вещественными числами:
  • получаем точность (количество чисел):

    
    BigDecimal value = new BigDecimal("454334.34334");
    int result = value.precision();//11
    
  • задаем количество знаков после запятой и правило округления:

    
    BigDecimal firstValue = new BigDecimal(3445.544445);
    
    BigDecimal secondValue = firstValue.setScale(3,BigDecimal.ROUND_CEILING);//3445.545
    

    Немного ниже мы рассмотрим подробнее константы для задания правил округления.

  • делим BigDecimal на другой BigDecimal, при этом указывая необходимое количество знаков после запятой и правило округления:

    
    BigDecimal firstValue = new BigDecimal("455656.545");
    
    BigDecimal secondValue = new BigDecimal(3445.544445);
    
    BigDecimal result = firstValue.divide(secondValue, 2,RoundingMode.DOWN);//132.24
    
  • перемещение запятой вправо/влево на определенное количество знаков:

    
    BigDecimal value = new BigDecimal("455656.545");
    BigDecimal firstResult = value.movePointRight (2);//45565654.5
    BigDecimal secondResult = value.movePointLeft (2);//4556.56545
    
  • обрезаем конечные нули:

    
    BigDecimal value = new BigDecimal("45056.5000");
    BigDecimal result = value.stripTrailingZeros();//45056.5
    

    Если же у нас в вещественной части все нули и в целой тоже есть (или у нас и вовсе нет знаков после запятой), тогда:

    
    BigDecimal value = new BigDecimal("450000.000");
    BigDecimal result = value.stripTrailingZeros();//4.5E+5
    

Правила округления BigDecimal

Для задания правил округления, внутри BigDecimal мы можем увидеть специальные константы, описывающие алгоритмы округления: ROUND_UP — округления от нуля, округление в сторону вещественной части:

BigDecimal firstValue = new BigDecimal("2.578");
BigDecimal firstResult =  firstValue.setScale(1, BigDecimal.ROUND_UP );//2.6
BigDecimal secondValue = new BigDecimal("-2.578");
BigDecimal secondResult = secondValue.setScale(1, BigDecimal.ROUND_UP );//-2.5
ROUND_DOWN — округления до нуля, то есть усечение вещественной части:

BigDecimal firstValue = new BigDecimal("2.578");
BigDecimal firstResult =  firstValue.setScale(1, BigDecimal.ROUND_DOWN  );//2.5
BigDecimal secondValue = new BigDecimal("-2.578");
BigDecimal secondResult = secondValue.setScale(1, BigDecimal.ROUND_DOWN  );//-2.6
ROUND_CEILING — округления до положительной бесконечности. То есть, если число у нас положительное, то -> ROUND_UP, если отрицательное, то -> ROUND_DOWN

BigDecimal firstValue = new BigDecimal("2.578");
BigDecimal firstResult =  firstValue.setScale(1, BigDecimal.ROUND_CEILING);//2.6
BigDecimal secondValue = new BigDecimal("-2.578");
BigDecimal secondResult = secondValue.setScale(1, BigDecimal.ROUND_CEILING);//-2.5
ROUND_FLOOR — округления до отрицательной бесконечности, то есть если число у нас положительное, то -> ROUND_DOWN, если отрицательное, то -> ROUND_UP

BigDecimal firstValue = new BigDecimal("2.578");
BigDecimal firstResult =  firstValue.setScale(1, BigDecimal.ROUND_FLOOR);//2.5
BigDecimal secondValue = new BigDecimal("-2.578");
BigDecimal secondResult = secondValue.setScale(1, BigDecimal.ROUND_FLOOR);//-2.6
Для рассматриваемого значения это ближайшее число с урезанным знаком после запятой будем рассматривать как ближайшего соседа рассматриваемого числа. Например, 2.43 будет ближе к 2.4, чем к 2.5, но 2.48 будет уже ближе к 2.5. ROUND_HALF_DOWN — округления до «ближайшего соседа». Если оба соседа равноудалены от конкретного значения, тогда производится округление к нулю. Равноудалены — это, например, когда округляемое число — 5, и оно находится на одинаковом расстоянии от 0 и 10):

BigDecimal firstValue = new BigDecimal("2.58");
BigDecimal firstResult =  firstValue.setScale(1, BigDecimal.ROUND_HALF_DOWN );//2.6
BigDecimal secondValue = new BigDecimal("2.55");
BigDecimal secondResult = secondValue.setScale(1, BigDecimal.ROUND_HALF_DOWN );//2.5
ROUND_HALF_UP — режим для округления в сторону «ближайшего соседа». Если оба соседа равноудалены, округление выполняется к большему числу (это то самое округление, которому нас обучали в школе):

BigDecimal firstValue = new BigDecimal("2.53");
BigDecimal firstResult =  firstValue.setScale(1, BigDecimal.ROUND_HALF_UP  );//2.5
BigDecimal secondValue = new BigDecimal("2.55");
BigDecimal secondResult = secondValue.setScale(1, BigDecimal.ROUND_HALF_UP  );//2.6
ROUND_HALF_EVEN — округления до «ближайшего соседа», если оба соседа не равноудалены. В этом случае если перед округляемым числом стоит нечетное, выполняется округление в большую сторону, если чётное — в меньшую:

BigDecimal firstValue = new BigDecimal("2222.2225");
BigDecimal secondValue = firstValue.setScale(3,BigDecimal.ROUND_HALF_EVEN );//2222.222
Такой результат мы получаем, потому что при округлении 5 смотрит на предыдущее число 2, и видя, что оно чётное, округление идет в меньшую сторону. Но если:

BigDecimal firstValue = new BigDecimal("2222.22255");
BigDecimal secondValue = firstValue.setScale(3,BigDecimal.ROUND_HALF_EVEN );//2222.223
То округление идёт в большую сторону, так как последняя 5 смотрит на предыдущее значение и видит нечётное число. Как следствие, число округляется в большую сторону до 6, после чего следующая 6 тоже идёт на округление. Но шестерка уже не смотрит на число слева, так как число явно ближе в большую сторону, и в итоге последняя 2 увеличивается на 1. ROUND_UNNECESSARY — используется для проверки, что число в округлении не нуждается. То есть, мы проверяем, что число имеет нужное нам количество знаков после запятой:

BigDecimal firstValue = new BigDecimal("2.55");
BigDecimal firstResult =  firstValue.setScale(2, BigDecimal.ROUND_UNNECESSARY);//2.55
Тут всё хорошо, значение имеет два знака и мы проверяем, что после запятой только два знака. Но если:

BigDecimal secondValue = new BigDecimal("2.55");
BigDecimal secondResult = secondValue.setScale(1, BigDecimal.ROUND_UNNECESSARY);
То мы получим — ArithmeticException, так как проверяемое значение превышает заданное количество знаков после запятой. Но если мы будем проверять два знака после запятой, а по факту там есть один, то исключение не выпадет, а недостающие знаки просто дополняются нулями:

BigDecimal thirdValue = new BigDecimal("2.5");
BigDecimal thirdResult = thirdValue.setScale(3, BigDecimal.ROUND_UNNECESSARY   );//2.500
Еще хотелось бы отметить что у BigDecimal есть константы, аналогичные константам BigInteger ZERO, ONE и TEN. Вот ссылка на документацию. И напоследок: как вы, наверное, заметили, выполняя операции с объектами BigInteger и BigDecimal, мы не изменяем старые, а всегда получаем новые. Это нам говорит о том, что они — immutable, то есть неизменные после их создания, как и String. Иными словами, все их методы не могут изменить внутреннее состояние объекта, максимум — вернуть новый объект с параметрами, заданными используемой нами функцией.
Комментарии (15)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Anonymous #2447435 Уровень 32
21 ноября 2022
Объясните пожалуйста, почему, когда набираю Ваши примеры в IDEA, то setScale и все аргументы в скобках: BigDecimal.ROUND_UP и все другие из лекции зачёрктнутые? Ошибок нет, на экран всё выводится, но в коде эти строки зачёркиваются и пишется ошибка, например: "ROUND_NP is deprecated". Это из-за того что setScale имеет параметры int int?
Руслан Уровень 22
3 августа 2022
Имеет смысл полностью отказаться от примитов в пользу BigInteger?)
Vlad Уровень 28
8 июня 2022
опечатка: " а есть такие, которые позволяю(пропущена т) задавать настройки создаваемого объекта (способы округления, количество знаков после запятой):"
Anastasia A. Уровень 24
29 апреля 2022
В ROUND_UP и ROUND_DOWN ошибки. Оно либо от нуля, либо к нулю, независимо от знака числа. Даже проверила в Idea
Серега Батенин Уровень 34
20 марта 2022
А мне вот интересно почему когда мы рассматривали конкретные пример округления BigDecimal с вариантами ROUND_UP ROUND_DOWN, то значение у отрицательных чисел были одни. А когда начали рассматривать ROUND_CEILING и ROUND_FLOOR, то в таком случае значения у отрицательных чисел почему то поменялись, хотя написано что к ним применяются вышеупомянутые методы UP и DOWN
12 января 2022
добрый день кто то может пояснить логику: BigInteger - который на вход принимает только строчные описания чисел ну вот как так то!!!
vzubkeviсh Уровень 26
28 января 2021

char[] arr = new String("455656.545").toCharArray();

BigDecimal fourthValue = new BigDecimal(arr, 2, 6);
//5656.5
Задаем массив знаков, с которого элемента берём значения для объекта и сколько этих элементов берём. Берем же 6 элементов, почему в результате вышло 5?
Rufat Khafizov Уровень 23
6 января 2021
метод BigInteger probablePrime (int bitLength, Random rnd) Возвращает положительное значение BigInteger, которое, вероятно, является простым, с указанным значением bitLength. Вероятность того, что BigInteger, возвращаемый этим методом, является составным, не превышает 2 в степени -100 . (мануал) Так что это не совсем рандом, в полном смысле этого слова.
Иван Уровень 41
7 августа 2020
Возможно, я тупой, но примеры с созданием BigDecimal() с различными параметрами, передаваемыми в конструктор - абсолютно непонятные.