1. Округлення дійсних чисел

Ми вже раніше обговорювали, що коли змінній типу int присвоюється дійсне число, воно завжди округлюється до меншого цілого — дробова частина просто відкидається.

Однак легко уявити ситуацію, коли дробове число потрібно округлити просто до найближчого або навіть до більшого цілого. Що ж робити в такій ситуації?

Для цього та багатьох інших подібних випадків у мові Java є клас Math, який містить методи round(), ceil() і floor().


Метод Math.round()

Метод Math.round() округляє число до найближчого цілого:

long x = Math.round(дійсне_число)

Але, як то кажуть, є нюанс: результатом використання цього методу буде цілочисловий тип long (а не int). Адже дійсні числа можуть бути дуже великими, тому розробники Java вирішили використовувати найдовший цілочисловий тип, який є в Java — long.

Ось чому для присвоєння результату змінній типу int програміст повинен явно вказати компілятору, що він згоден із можливою втратою даних (якщо раптом число не вміститься в тип int).

int x = (int) Math.round(дійсне_число)

Приклади:

Команда Результат
int x = (int) Math.round(4.1);
4
int x = (int) Math.round(4.5);
5
int x = (int) Math.round(4.9);
5

Метод Math.ceil()

Метод Math.ceil() округлює число вгору (до більшого цілого). Приклади:

Команда Результат
int x = (int) Math.ceil(4.1);
5
int x = (int) Math.ceil(4.5);
5
int x = (int) Math.ceil(4.9);
5

Метод Math.floor()

Метод Math.floor() округлює число вниз (до меншого цілого). Приклади:

Команда Результат
int x = (int) Math.floor(4.1);
4
int x = (int) Math.floor(4.5);
4
int x = (int) Math.floor(4.9);
4

Натомість для округлення числа до меншого цілого простіше використовувати звичайний оператор приведення типу — (int):

Команда Результат
int x = (int) 4.9
4

Якщо вам складно запам'ятати ці команди, у пригоді стане невеличкий урок англійської:

  • Math — математика
  • Round — круглий/округлювати
  • Ceiling — стеля
  • Floor — підлога


2. Будова чисел із рухомою крапкою

Тип double може зберігати значення від -1.7*10308 до +1.7*10308. Такий широчезний діапазон значень (порівняно з типом int) пояснюється тим, що будова типу double (і типу float) суттєво відрізняється від будови цілих типів. Кожна змінна типу double містить два числа: перше має назву «мантиса», а друге — «степінь».

Припустімо, ми маємо число 123456789 і зберегли його в змінній типу double. Це число буде перетворено у форму 1.23456789*108, і в змінній типу double зберігатимуться два числа — 23456789 і 8. Червоним виділено «значущу частину числа» (манти́су), синім — степінь.

Завдяки такому підходу можна зберігати і дуже великі, і дуже малі числа. Однак оскільки розмір числа обмежений 8 байтами (64 бітами) і частина бітів використовується для зберігання степеня (а також знака числа та знака степеня), максимальна довжина мантиси становить 15 цифр.

Це дуже спрощений опис будови дійсних чисел. Детальнішій опис див. тут.


3. Втрата точності під час операцій із дійсними числами

Виконуючи різні дії з дійсними числами, слід мати на увазі, що дійсні числа не є точними. Завжди є похибки округлення, похибки перетворення чисел з десяткової системи у двійкову, а найчастішою проблемою є втрата точності під час додавання/віднімання чисел надто різних розмірностей.

Для новачків у програмуванні остання ситуація є найменш очікуваною.

Якщо від числа 109 відняти 1/109, ми знову ж таки отримаємо 109.

Віднімання чисел надто різних розмірностей Пояснення
 1000000000.000000000;
-         0.000000001;
 1000000000.000000000;
Друге число надто мале, і його значуща частина ігнорується (виділено сірим). Помаранчевим виділено 15 значущих цифр.

Ну що сказати? Програмування — це не математика.


4. Небезпека порівняння дійсних чисел

Ще одна небезпека чатує на програмістів під час порівняння дійсних чисел. Оскільки під час операцій із цими числами можуть накопичуватися похибки округлення, то можливі такі ситуації, коли дійсні числа мали б бути рівними між собою, однак вони не рівні. І навпаки: числа мають бути нерівними, а вони рівні.

Приклад:

Команда Пояснення
double a = 1000000000.0;
double b = 0.000000001;
double c = a - b;
Змінна a матиме значення 1000000000.0
Змінна c матиме значення 1000000000.0
(число у змінній b надто мале)

У наведеному вище прикладі а і с мають бути нерівними, однак вони рівні.

Або розглянемо інший приклад:

Команда Пояснення
double a = 1.00000000000000001;
double b = 1.00000000000000002;
Змінна a матиме значення 1.0
Змінна b матиме значення 1.0

5. Цікавий факт про слово strictfp

У Java є спеціальне ключове слово strictfp (strict floating point), якого немає в інших мовах програмування. А знаєте, навіщо воно потрібне? Воно погіршує точність операцій із дійсними числами. Послухайте історію про те, як воно виникло.

Створювачі Java:
Ми прагнемо, щоб Java була суперпопулярною і щоб програми на Java виконувалися на якомога більшій кількості пристроїв. Тому ми прописали в специфікації Java-машини, що на пристроях усіх типів усі програми мають виконуватись однаково!
Створювачі процесора Intel:
Хлопці, ми поліпшили наші процесори, і тепер усі дійсні числа в процесорі будуть представлені не вісьмома, а десятьма байтами. Більше байтів — більше значущих цифр. А що це означає? Правильно: тепер ваші наукові розрахунки стануть ще точнішими!
Вчені й усі, хто займається надточними розрахунками:
Круто! Молодці. Чудова новина.
Створювачі Java:
Ні-ні-ні, хлопці. Адже ми сказали: всі Java-програми мають виконуватись однаково на всіх пристроях. Примусово вимикаємо можливість використання 10-байтових дійсних чисел у процесорах Intel.
От тепер усе знову супер! Не дякуйте.
Вчені й усі, хто займається надточними розрахунками:
Та чи ви там зовсім береги поплутали? Нумо мерщій поверніть усе як було!
Створювачі Java:
Хлопці, це ж заради вашої користі! Лише уявіть: усі Java-програми виконуються однаково на всіх пристроях. Це ж круто!
Вчені й усі, хто займається надточними розрахунками:
Ні. Зовсім не круто. Швиденько поверніть усе назад! Або ми вашу Java знаєте куди вам запхнемо?
Створювачі Java:
Гм. Чом же ви одразу не сказали? Авжеж, повернемо.
Повернули можливість користуватися всіма фічами крутих процесорів.
До речі, ми спеціально додали в мову слово strictfp: якщо написати його перед ім’ям функції, всі операції з дійсними числами в цій функції будуть виконуватися однаково погано на всіх пристроях!