Привет! На сегодняшней лекции мы поговорим о больших числах. Нет, о ДЕЙСТВИТЕЛЬНО БОЛЬШИХ. Ранее мы не раз встречали таблицу диапазонов значений для примитивных типов данных. Выглядит она так:
Примитивный тип Размер в памяти Диапазон значений
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);

   }
}
Вывод в консоль: 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: здесь и здесь).
  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. Это тот результат, на который мы и рассчитывали! :) На этом наше сегодняшнее занятие окончено. Самое время вернуться к задачам! :)