Як виникають помилки в обчисленнях з рухомою комою?

Як виникають помилки в обчисленнях з рухомою комою?
Переклад 7 хв. читання

Ви коли-небудь працювали з такими числами, як 1/3, де результат дорівнює 0,33333... і так до нескінченності? Як люди, ми природно округлюємо такі числа, але чи замислювалися ви коли-небудь, як з ними працюють комп'ютери?

У цій статті ви дізнаєтесь, як комп'ютери працюють з неперервними значеннями, включаючи поняття похибки точності. Ми розглянемо проблему арифметики з рухомою комою - універсальну проблему, яка впливає на багато мов програмування. Ми зосередимося на тому, як JavaScript підходить до розв'язання цієї проблеми.

Крім того, ви дізнаєтеся, як за лаштунками працюють двійкові операції, поріг, при якому JavaScript усікає числа на основі стандарту IEEE 754, а також познайомитеся з BigInt як рішенням для точної обробки великих чисел без втрати точності.

Для початку розглянемо приклад. Чи можете ви вгадати результат цієї операції?

console.log(0.1 + 0.2);

Ви можете подумати, що відповідь дорівнює 0,3, так? Але ні, фактичний результат такий:

Output: 0.30000000000000004

Вам, мабуть, цікаво, чому так відбувається. Чому так багато зайвих нулів і чому число закінчується на 4?

Відповідь проста: числа 0.1 і 0.2 не можуть бути точно представлені в JavaScript.

Звучить просто, чи не так? Але пояснення трохи складніше.

Як ви думаєте, що це - баг чи особливість?

Ну, це не баг. Насправді це фундаментальна проблема з тим, як комп'ютери працюють з числами, особливо з числами з рухомою комою.

Чому так відбувається?

Давайте розберемося з цим за допомогою елементарної математики.

Коли ви додаєте 0.1 і 0.2, це має дорівнювати 0.3. Однак у комп'ютерних системах десяткові числа, такі як 0.1 і 0.2, представлені приблизно, оскільки вони не можуть зберігатися точно у двійковій формі. В результаті, додавання 0.1 і 0.2 може дати щось на кшталт 0.30000000000000004 замість 0.3.

Ця проблема виникає через те, що багато десяткових дробів не мають точного представлення у двійковій системі числення, наприклад 1/3 (0.3333...) не може бути точно записане в десятковій формі і натомість повторюється нескінченно. Комп'ютери змушені апроксимувати такі числа, як 0.1 і 0.2, що може призвести до крихітних помилок округлення в обчисленнях.

Це призводить до того, що ми називаємо проблемою арифметики з рухомою комою.

Проблема арифметики з рухомою комою

Простіше кажучи, числа з рухомою комою - це числа, які неможливо записати точно, тому ми їх наближено обчислюємо. У комп'ютері таке наближення може призвести до невеликих похибок, які ми називаємо проблемою арифметики з рухомою комою.

Пояснення з використанням двійкового коду

Тепер, коли ми розглянули просте пояснення, давайте розберемося з двійковим кодом. За лаштунками JavaScript обробляє все в двійковому форматі.

Двійкова система числення - це система числення, яка використовує лише дві цифри: 0 і 1.

Чому 0.1 і 0.2 не можуть бути представлені саме в двійковій системі числення?

Основна проблема полягає в тому, що не всі десяткові числа можна ідеально представити у вигляді двійкового дробу.

Візьмемо для прикладу 0.1:

Коли ви спробуєте представити 0.1 у двійковому вигляді, ви побачите, що воно не може бути виражене у вигляді скінченного двійкового дробу. Замість цього він стає повторюваним дробом, подібно до того, як 1/3 в десятковій системі числення перетворюється на 0,333..., повторюючись до нескінченності.

У двійковій системі числення - 0.1 стає:

0.0001100110011001100110011001100110011... (повторюється нескінченно)

Оскільки комп'ютери мають обмежену пам'ять, вони не можуть точно зберігати цю нескінченну послідовність. Замість цього вони змушені обрізати число в певному місці, що вносить невелику похибку округлення. Ось чому 0.1 у двійковому коді є лише наближенням до реального значення.

Як і 0.1, 0.2 не може бути точно представлено в двійковому коді. Воно стає:

0.00110011001100110011001100110011... (повторюється нескінченно)

Знову ж таки, комп'ютер відсікає (відрізає частину числа, щоб вкластися в обмеження або видаляє зайві цифри) цю нескінченну двійкову послідовність, що призводить до невеликої помилки в представленні.

Отже, що відбувається, коли ми додаємо 0.1 + 0.2?

Коли ви додаєте 0.1 + 0.2 в JavaScript, бінарні наближення для 0.1 і 0.2 додаються разом. Але оскільки обидва значення є лише наближеннями, результат також є наближенням.

Замість того, щоб отримати точно 0.3, ви отримаєте щось близьке до цього:

console.log(0.1 + 0.2); // Output: 0.30000000000000004

Ця незначна помилка виникає через те, що ні 0.1, ні 0.2 не можуть бути точно представлені в двійковій системі числення, тому кінцевий результат має невелику похибку округлення.

Як JavaScript скорочує число?

Тепер виникає питання: звідки JavaScript знає, коли потрібно усікати значення?

(Усічення означає відсікання або скорочення числа шляхом видалення зайвих цифр після певної точки. )

Для цього існує максимальна і мінімальна межа.

Для вирішення цієї проблеми в комп'ютерному світі існує стандарт, який визначає, як зберігаються та обчислюються числа з рухомою комою.

Стандарт IEEE 754

JavaScript використовує стандарт IEEE 754 для обробки арифметики з рухомою комою.

Стандарт визначає безпечні межі цілих чисел для типу Number в JavaScript без втрати точності:

  • Максимальне допустиме ціле число: 2^53 - 1 або 9007199254740991
  • Мінімальне допустиме ціле число: -(2^53 - 1) або -9007199254740991

Поза цими межами JavaScript не може точно представляти цілі числа через те, що так працює арифметика з рухомою комою.

З цієї причини JavaScript надає дві константи для представлення цих обмежень:

  • Number.MAX_SAFE_INTEGER
  • Number.MIN_SAFE_INTEGER

Що робити, якщо треба більше число?

Якщо вам потрібно працювати з числами, більшими за Максимально допустиме безпечне ціле число (наприклад, з тими, що використовуються у криптографії або фінансах), JavaScript має рішення: BigInt.

BigInt - це вбудований об'єкт, який дозволяє працювати з цілими числами за межею безпечного цілого. Він дозволяє представляти числа, більші за 9007199254740991, тому вам не доведеться хвилюватися про похибки точності!

Щоб використовувати BigInt, просто додайте n в кінець цілого літералу:

const bigNumber = 1234567890123456789012345678901234567890n;

Крім того, ви можете скористатися конструктором BigInt:

const bigNumber = BigInt("1234567890123456789012345678901234567890");

Операції з BigInt

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

Наприклад, це не спрацює:

let result = bigNumber + 5; // Error: cannot mix BigInt and other types

Спочатку потрібно перетворити число у тип BigInt:

let result = bigNumber + BigInt(5); // Now it works!

Де можна використовувати BigInt?

BigInt особливо корисний в областях, що вимагають точності, таких як:

  • Криптографічні алгоритми
  • Обробка великих масивів даних
  • Фінансові розрахунки, що вимагають точності

Підсумок

Безпечна межа цілих чисел у JavaScript забезпечує точне представлення чисел між -(2^53 - 1) та 2^53 - 1.

Помилки точності виникають через арифметику з рухомою комою при обробці певних чисел (наприклад, 0.1 + 0.2).

Якщо вам потрібні числа більші за безпечну межу, BigInt - ваш друг. Але пам'ятайте, що змішування типів BigInt і Number вимагає явного перетворення.

Джерело: What is a Floating-Point Arithmetic Problem?
Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід