JavaRush /Java блог /Random UA /BigDecimal у Java

BigDecimal у Java

Стаття з групи Random UA
Вітання! На сьогоднішній лекції ми поговоримо про великі числа. Ні, про ДІЙСНО ВЕЛИКИХ. Раніше ми неодноразово зустрічали таблицю діапазонів значень для примітивних типів даних. Виглядає вона так:
Примітивний тип Розмір у пам'яті Діапазон значень
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, а якось треба пояснити компілятору, яке саме число ми хочемо отримати :) Просто передати в конструктор число 111111111111111111111111111111111111111111111111111111111111111111111111111111111 не вийде: 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. Це той результат, на який ми розраховували! :) На цьому наше сьогоднішнє заняття закінчено. Саме час повернутися до завдань! :)
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ