JavaRush/Курси/Java Syntax Zero/Особливості роботи з дійсними числами

Особливості роботи з дійсними числами

Відкрита

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 — підлога

4
Задача
Java Syntax Zero,  4 рівень,  10 лекція
Недоступна
Швидкість вітру
Швидкість вітру
4
Задача
Java Syntax Zero,  4 рівень,  10 лекція
Недоступна
Склянка наполовину порожня чи наполовину повна?
Склянка наполовину порожня чи наполовину повна?

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: якщо написати його перед ім’ям функції, всі операції з дійсними числами в цій функції будуть виконуватися однаково погано на всіх пристроях!
Коментарі (22)
  • популярні
  • нові
  • старі
Щоб залишити коментар, потрібно ввійти в систему
Konstantin
Рівень 6
2 травня 2025, 14:30
Цікаве зауваження якщо змінна int pisimist = (int) Math.floor(glass); розташована після змінної int optimist = (int) Math.ceil(glass); то третій пункт умови не пройде валідацію !!! Серйозно !!! Яка в даному випадку різниця яка змінна стоїть спочатку а яка після ? Тут прямо нереально суровий валідатор. Пів години довбався не міг зрозуміти в чому проблема. Чисто випадково вирішив поміняти їх місцями. Це просто капец !!! ------------------------- double glass = 0.5; int optimist = (int) Math.ceil(glass); int pisimist = (int) Math.floor(glass); Scanner scan = new Scanner(System.in); boolean bol = scan.nextBoolean(); int result = bol? optimist : pisimist; System.out.println(result);
IronMan57
Рівень 28
19 листопада 2024, 00:49
Уточнення: в лекції сказано, що метод round классу Math повертає значення типу long (довге ціле число), але немає інформації про те, що методи ceil та floor того ж классу повертають значення типу double (числа з плаваючою крапкою), які математично рівні цілим числам. Тобто Math.ceil(4.5) поверне значення 5.0, а не 5, а Math.floor(4.5) поверне значення 4.0, а не 4. В окремих випадках це може бути важливим.
Денис
Рівень 28
20 жовтня 2024, 12:08
Експоненційний запис - представлення дійсних чисел у вигляді мантиси і порядку. Тобто не мантиса і степінь, а саме порядок. 10 в степені. До речі. Якщо хтось каже, щось типу "Я заробляю на порядок більше грошей", то це в 10 разів більше. . На 2 порядки - в 100 разів. А на півтора порядки, це приблизно в 31.62 рази більше. )))
hidden #3500645
Рівень 30
20 жовтня 2024, 15:20
цікава інформація 👍
Pavlo
Рівень 8
11 травня 2024, 15:44
Якби я не знав, що java script був створений за подобою java, то подумав би, що java це гірша копія java script))))
Alex
Рівень 2
6 серпня 2024, 01:47
javascript не був створений за подобою java, в них абсолютно нічого спільного немає, javascript назвали саме так виключно через високу популярність java на той час, в js повинна була бути взагалі інша назва, здається ecma script, чи щось таке
Maxim
Рівень 6
7 червня 2023, 15:36
double glass = 0.5;
        //напишіть тут ваш код
        Scanner scanner = new Scanner(System.in);
        boolean num = scanner.nextBoolean();
        int result = num ? (int)Math.ceil(glass) : (int) Math.floor(glass);
        System.out.println(result);
// очень удобный и понятный код с тернарным оператором используйте :)
Maxim
Рівень 6
7 червня 2023, 15:01
без Math.round
        Scanner scanner = new Scanner(System.in);
        double s = scanner.nextInt();
        double a = (s * 3.6);
        System.out.println(a);
с Math.round
        Scanner scanner = new Scanner(System.in);
        double s = scanner.nextInt();
        int a = (int) Math.round (s * 3.6);
        System.out.println(a);
не вижу смысла использовать в этой ситуации Math.round
Oleh
Рівень 7
31 березня 2023, 17:59
Scanner abc = new Scanner(System.in);
        boolean a = abc.nextBoolean();
          if (a = false)
            System.out.println((int) Math.floor(glass));
                else if (a = true)
                    System.out.println((int) Math.ceil(glass));
Написав код за пів хвилини, дуже простий і поставлену задачу виконував, але рішення не приймало як вірне, перепробував гору іншого коду, але всерівно повернувся до цього варіанту, знадобився день щоб замінити - "if (a = false) на if (a == false) та else if (a = true) на else if (a == true)" і вуаля.
Yaroslav Tkachyk
Рівень 23
Expert
6 грудня 2022, 07:29
В 9 лекції описано: Зміна_цілого_типу = (int) (дійсне число) В даній лекції (там де Math.floor): int x = (int) 4.9 Запитання: Дійсне число потрібно брати в () чи ні?
theylovevalera Backend Developer в шукаю роботу
12 березня 2023, 20:45
Ні,не потрібно)
15 вересня 2022, 01:43
Дуже дивна ситуація, спочатку мій розв'язок не зараховувався, хоча все виводилося і відповідало вимогам. Пробувала міняти код (дивилася як інші розв'язували), взагалі компілятор не працював, і показувало помилку. Вернула назад свій розв'язок - зарахувало задачу і вимоги пройшли 😶 Це дуже збиває і дратує((( public class Solution { public static void main(String[] args) { double glass = 0.5; Scanner console = new Scanner (System.in); boolean x = console.nextBoolean(); if (x){ int y = (int) Math.ceil (glass); System.out.println (y); } else { int z = (int) Math.floor (glass); System.out.println (z); } } }
27 квітня 2023, 23:00
не понял как это правильно работает 😟
Karibrut
Рівень 2
29 липня 2022, 10:36
У кожної задачі є умови які чітко прописані. Наприклад у задачі з boolean можна не використовувати Math.floor(), натомість просто прописавши (int) glass, але тоді не буде виконано умова вирішення задачі. Якщо іти по лекції то задача з boolean досить проста. public class Solution { public static void main(String[] args) { double glass = 0.5; Scanner scan = new Scanner(System.in); boolean answer = scan.nextBoolean(); if(answer){ int liter = (int) Math.ceil(glass); System.out.print(liter); } else if(!answer){ int liter = (int) Math.floor(glass); System.out.print(liter); } } }
Jane Fox
Рівень 26
6 вересня 2022, 13:41
System.out.print можна було використати і один раз, в кінці, менше непотрібної інформації)