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

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

Java Syntax Zero
Рівень 4 , Лекція 10
Відкрита

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: якщо написати його перед ім’ям функції, всі операції з дійсними числами в цій функції будуть виконуватися однаково погано на всіх пристроях!
Коментарі (22)
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ
Konstantin Рівень 6
2 травня 2025
Цікаве зауваження якщо змінна 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
Уточнення: в лекції сказано, що метод round классу Math повертає значення типу long (довге ціле число), але немає інформації про те, що методи ceil та floor того ж классу повертають значення типу double (числа з плаваючою крапкою), які математично рівні цілим числам. Тобто Math.ceil(4.5) поверне значення 5.0, а не 5, а Math.floor(4.5) поверне значення 4.0, а не 4. В окремих випадках це може бути важливим.
Денис Рівень 28
20 жовтня 2024
Експоненційний запис - представлення дійсних чисел у вигляді мантиси і порядку. Тобто не мантиса і степінь, а саме порядок. 10 в степені. До речі. Якщо хтось каже, щось типу "Я заробляю на порядок більше грошей", то це в 10 разів більше. . На 2 порядки - в 100 разів. А на півтора порядки, це приблизно в 31.62 рази більше. )))
hidden #3500645 Рівень 30
20 жовтня 2024
цікава інформація 👍
Pavlo Рівень 8
11 травня 2024
Якби я не знав, що java script був створений за подобою java, то подумав би, що java це гірша копія java script))))
Alex Рівень 2
6 серпня 2024
javascript не був створений за подобою java, в них абсолютно нічого спільного немає, javascript назвали саме так виключно через високу популярність java на той час, в js повинна була бути взагалі інша назва, здається ecma script, чи щось таке
Maxim Рівень 6
7 червня 2023

        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

без 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

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
В 9 лекції описано: Зміна_цілого_типу = (int) (дійсне число) В даній лекції (там де Math.floor): int x = (int) 4.9 Запитання: Дійсне число потрібно брати в () чи ні?
theylovevalera Рівень 51
12 березня 2023
Ні,не потрібно)
15 вересня 2022
Дуже дивна ситуація, спочатку мій розв'язок не зараховувався, хоча все виводилося і відповідало вимогам. Пробувала міняти код (дивилася як інші розв'язували), взагалі компілятор не працював, і показувало помилку. Вернула назад свій розв'язок - зарахувало задачу і вимоги пройшли 😶 Це дуже збиває і дратує((( 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); } } }
Сергей Брага Рівень 6
27 квітня 2023
не понял как это правильно работает 😟
Karibrut Рівень 1
29 липня 2022
У кожної задачі є умови які чітко прописані. Наприклад у задачі з 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
System.out.print можна було використати і один раз, в кінці, менше непотрібної інформації)