Питання Математика з плаваючою точкою зламана?


Розглянемо наступний код:

0.1 + 0.2 == 0.3  ->  false
0.1 + 0.2         ->  0.30000000000000004

Чому ці неточності відбуваються?


2268
2018-02-25 21:39


походження


Звичайні змінні з плаваючою комою мають таку поведінку. Це викликано тим, як вони зберігаються в апаратному забезпеченні. Для отримання додаткової інформації відвідайте Стаття вікіпедії про номери з плаваючою точкою. - Ben S
JavaScript обробляє десяткові значення як числа з плаваючою точкою, що означає, що такі операції, як додавання, можуть бути пов'язані з помилкою округлення. Ви можете поглянути на цю статтю: Що повинен знати кожен вчений-комп'ютер про арифметику з плаваючою точкою - matt b
Для інформації всі типи чисел в javascript - це дублювання IEEE-754. - Gary Willoughby
@Gary Правда, хоча гарантовано, що ви маєте ідеальну цілість точності для цілих чисел до 15 цифр, див hunlock.com/blogs/The_Complete_Javascript_Number_Reference - Ender
Оскільки JavaScript використовує стандарт IEEE 754 для Math, він використовує 64-розрядний плаваючі числа. Це призводить до помилок точності при виконанні розрахунків з десятковим коефіцієнтом з плаваючою комою (короткостроковими), коротше кажучи, через роботу комп'ютерів База 2 в той час як десяткове число База 10. - Pardeep Jain


Відповіді:


Двійковий плаваюча точка математика схожа на це. На більшості мов програмування він базується на Стандарт IEEE 754. JavaScript використовує 64-бітове представлення з плаваючою комою, яке є таким самим, як Java double. Суть проблеми полягає в тому, що цифри представлені в цьому форматі в цілому чисельністю за силою двох; раціональні числа (такі як 0.1, який 1/10), чия знаменник не є силою двох, точно не може бути представлена.

Для 0.1 в стандарті binary64 формат, представлення може бути записано точно як

Навпаки, раціональне число 0.1, який 1/10, можна записати точно так, як

  • 0.1 в десятковій, або
  • 0x1.99999999999999...p-4 в аналозі позначення C99 hexfloat, де ... являє собою нескінченну послідовність 9-х років.

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

Досить комплексне ставлення до питань арифметики з плаваючою точкою Що повинен знати кожен вчений-комп'ютер про арифметику з плаваючою точкою. Для більш легкого для дайджесту пояснення див плаваюча точка-gui.de.


1723
2018-04-18 11:52



'Деяка помилка постійна', також відома як значення Epsilon. - Gary Willoughby
Я думаю, що "деяка постійна помилки" є більш правильною, ніж "The Epsilon", тому що немає "The Epsilon", який може бути використаний у всіх випадках. Різні епсілони потрібно використовувати в різних ситуаціях. І машина епсілон майже ніколи не є гарною постійною у використанні. - Rotsor
Це не цілком Щоправда, всі математичні схеми з плаваючою точкою базуються на стандарті IEEE [754]. Існують ще деякі системи, які використовують, наприклад, старий IBM шістнадцятковий FP, і є ще відеокарти, які не підтримують арифметику IEEE-754. Проте це справедливо до розумного наближення. - Stephen Canon
Крей збив з IEEE-754 відповідність для швидкості. Java також послабила свою прихильність як оптимізацію. - Art Taylor
Я думаю, ви повинні додати щось до цієї відповіді про те, як обчислення на гроші завжди, завжди повинні бути зроблені з фіксованою точкою арифметики на цілі числа, тому що гроші квантовано. (Було б сенс робити внутрішні розрахунки бухгалтерського обліку в крихітних фракціях центів або будь-якій вашій найменшій валютній одиниці - це часто допомагає, наприклад, зменшуючи помилку округлення при перетворенні $ 29,99 на місяць до добової ставки, - але це слід все-таки буде фіксована арифметика.) - zwol


Перспектива дизайнера апаратного забезпечення

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

1. Огляд

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

2. Стандарти

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

У стандарті IEEE-754 розробникам апаратних засобів допускається будь-яке значення помилки / епсілон, якщо воно становить менше половини одного блоку на останньому місці, а результат має бути лише менше половини одного блоку в останньому місце для однієї операції. Це пояснює, чому при повторних операціях помилки складаються. Для подвійної точності IEEE-754 це 54 біт, оскільки 53 біти використовуються для позначення числової частини (нормалізованої), також називається mantissa, числа з плаваючою комою (наприклад, 5.3 в 5.3e5). У наступних розділах докладніше розглянуто причини апаратної помилки при різних операціях з плаваючою комою.

3. Причина округлення помилки в відділенні

Основною причиною помилки в поділі з плаваючою комою є алгоритми розподілу, що використовуються для обчислення фактору. Більшість комп'ютерних систем розраховують розподіл, використовуючи множення на зворотний, в основному в Z=X/Y, Z = X * (1/Y). Розподіл обчислюється ітераційно, тобто кожен цикл обчислює деякі біти фактору, доки не буде досягнуто бажаної точності, що для IEEE-754 - це щось з похибкою, меншою за одиницю, на останньому місці. Таблиця взаємних символів Y (1 / Y) відома як таблиця вибору факторів (QST) у повільному розподілі, а розмір у бітах таблиці вибору фактору - це, як правило, ширина radix або кількість бітів коефіцієнт, який обчислюється в кожній ітерації, плюс кілька бітів захисту. Для стандарту IEEE-754, подвійної точності (64-розрядна), це буде розмір радіометра роздільника плюс кілька захисних бітів k, де k>=2. Так, наприклад, типова таблиця вибору співвідношення для дільника, який обчислює 2 біти фактору за один раз (radix 4), буде 2+2= 4 біти (плюс кілька необов'язкових бітів).

3.1 Порушення округлення підрозділу: наближення взаємної форми

Які рекомбінанти знаходяться в таблиці факторів вибору залежать від метод поділу: повільний поділ, такий як відділ СТО, або швидке поділ, подібне поділу Гольдшмідта; кожен запис модифікується відповідно до алгоритму розподілу в спробі отримати найменшу можливу похибку. У будь-якому випадку, однак, всі зворотні речі наближення фактичного зворотного і вводять деякий елемент помилки. Як повільне розподіл, так і методи швидкого розподілу обчислюють фактор ітераційно, тобто кожну стадію обчислюють певну кількість бітів фактора, потім результат вираховується з дивіденду, а дільник повторює етапи, доки помилка не буде меншою, ніж одна половина одиниця на останньому місці. Методи повільного розподілу обчислюють фіксовану кількість цифр фактору на кожному кроці та зазвичай є менш дорогими для побудови, а методи швидкого розподілу обчислюють змінну кількість цифр на кожному кроці і зазвичай коштують дорожче. Найбільш важливою частиною методів розподілу є те, що більшість з них залежить від повторного множення на a наближення взаємно, тому вони схильні до помилок.

4. Округлення помилок в інших операціях: урізання

Іншою причиною помилок округлення у всіх операціях є різні режими урізання остаточної відповіді, яку дозволяє IEEE-754. Там вкорочений, круглий в бік нуля округлений до найближчого (за умовчанням), внизу та округлення. Всі методи вводять елемент помилки менш ніж однієї одиниці на останньому місці для однієї операції. З часом і повторюваними операціями обрізання також сумарно додає до результуючої помилки. Ця помилка урізання особливо проблематична при пастці, яка передбачає певну форму повторного множення.

5. Повторні операції

Оскільки апаратне забезпечення, яке виконує обчислення з плаваючою точкою, потрібно лише отримати результат з помилкою, меншою за одну половину одного блоку на останньому місці для однієї операції, помилка буде зростати через повторні операції, якщо вона не буде переглянута. Це пов'язано з тим, що в обчисленнях, що вимагають обмеженої помилки, математики використовують такі методи, як використання округленої до найближчої навіть цифра на останньому місці від IEEE-754, оскільки з часом помилки частіше скасовують один одного, і Інтервальна арифметика в поєднанні з варіаціями IEEE 754 режими округлення передбачити помилки округлення та виправити їх. Через низьку відносну похибку в порівнянні з іншими режимами округлення, округлення до найближчої рівної цифри (на останньому місці), є стандартним способом округлення IEEE-754.

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

6. Резюме

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


490
2018-02-25 21:43



(3) неправильно. Помилка округлення в підрозділі не менше один одиниця на останньому місці, але не більше половина одиниця на останньому місці. - gnasher729
@ gnasher729 Хороший вихід. Більшість базових операцій також мають похибку EN менше, ніж 1/2 одного блоку на останньому місці за допомогою стандартного режиму округлення IEEE. Відредагував пояснення, а також зазначив, що помилка може бути більшою, ніж 1/2 з однієї краплини, але менше, ніж 1 кв.м., якщо користувач перевизначає режим округлення за замовчуванням (це особливо актуально для вбудованих систем). - KernelPanik
(1) Плаваюча точка номери не помиляюся Кожне значення з плаваючою комою - це саме те, що воно є. Більшість (але не всі) плаваючою крапкою операції дати неточні результати. Наприклад, значення бінарної функції з плаваючою комою відсутнє, рівночасно рівне 1.0 / 10.0. Деякі операції (наприклад, 1,0 + 1,0) робити дати точні результати, з іншого боку. - james large
"Основною причиною помилки в поділі з плаваючою комою є алгоритми розподілу, що використовуються для обчислення фактору" дуже що ввести в оману. Для відповідного підрозділу IEEE-754 тільки причиною помилки в поділі з плаваючою комою є неможливість точного відображення результату у форматі результатів; однаковий результат обчислюється незалежно від використовуваного алгоритму. - Stephen Canon
@ Матт Вибачте за пізню відповідь. Це в основному через проблеми ресурсу / час та компроміси. Існує спосіб зробити довге поділ / більш "нормальний" поділ, його називають SRT Division з radix two. Однак це неодноразово зміщує і віднімає дільник від дивідендів і займає багато циклів, оскільки він лише обчислює один біт фактору за тактовий цикл. Ми використовуємо таблиці взаємних значень, щоб ми могли обчислити більше бітів фактору за цикл і зробити ефективні компроміси ефективності / швидкості. - KernelPanik


Коли ви перетворюєте .1 або 1/10 на базу 2 (бінарний), ви отримуєте повторюваний шаблон після десяткової крапки, просто намагаючись позначити 1/3 в базі 10. Значення не точне, і тому ви не можете це зробити Точна математика з нею, використовуючи звичайні методи з плаваючою комою.


357
2017-11-20 02:39



Відмінна та коротка відповідь. Схема повторення виглядає як 0.00011001100110011001100110011001100110011001100110011 ... - Konstantin Chernov
Це не пояснює, чому не використовується кращий алгоритм, який не переносить в двійкові файли. - Dmitri Zaitsev
Оскільки продуктивність. Використання двійкового файлу в декілька тисяч разів швидше, тому що він є рідним для машини. - Joel Coehoorn
Є методи, які дають точні десяткові значення. BCD (Бінарне кодоване десяткове число) або різні інші форми десяткового числа. Однак вони обидва повільні (набагато LOT) і займає більше місця, ніж використання бінарних з плаваючою точкою. (наприклад, упакований BCD зберігає 2 десяткових цифри в байті. Це 100 можливих значень в байті, які можуть фактично зберігати 256 можливих значень або 100/256, що витрачає близько 60% можливих значень байта.) - Duncan C
@Jacksonkr ви все ще думаєте в базі-10. Комп'ютери базу-2. - Joel Coehoorn


Більшість відповідей тут стосуються цього питання в дуже сухих технічних умовах. Я хочу розглянути це в термінах, які можуть зрозуміти звичайні люди.

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

Це різак для піци має дуже чудові рухи, і якщо ви починаєте з цілої піци, тоді вдвічі зменште його і продовжуйте зменшувати вдвічі частку найменшого шматка кожного разу, ви можете вдвічі зменшити 53 рази перш ніж скибочка занадто мала для навіть своїх високоточних здібностей. У такому разі ви вже не зможете вдвічі зменшити цей тонкий фрагмент, але потрібно включити або виключити його як є.

Тепер, як би ви скріпили всі шматочки таким чином, щоб скласти до однієї десятої (0,1) або одну п'яту (0,2) піци? Дійсно подумайте про це, і спробуйте це зробити. Ви навіть можете спробувати використати справжню піцу, якщо у вас є ручна різальна піца для міфічної точності. :-)


Найбільш досвідчені програмісти, звичайно, знають справжню відповідь, а саме: неможливо скласти частину точно десята чи п'ята частина піци, використовуючи ці скибочки, незалежно від того, наскільки тонко їх виріжете. Ви можете зробити досить гарне наближення, і якщо ви наблизите наближення 0,1 до апроксимації 0,2, ви отримаєте досить гарне наближення 0,3, але це все одно саме це, наближення.

Для цифр подвійної точності (точна точність, яка дозволяє вам вдвічі зменшити вашу піцу в 53 рази), числа, що одразу ж менше і більше 0,1, - 0,09999999999999999167332731531132594682276248931884765625 та 0,1000000000000000055511151231257827021181583404541015625. Останній зовсім трохи ближче до 0,1, ніж перший, тому числовий синтаксичний аналізатор, враховуючи вхід 0,1, буде віддавати перевагу останньому.

(Різниця між цими двома цифрами є "найменшою частиною", яку ми повинні вирішити включити, що вказує на висхідне зміщення або виключає, що вводить в бік зменшення. Технічний термін для цього найменшого шматка є ульп.)

У випадку 0,2 ці цифри однакові, вони просто збільшені в два рази. Знову ж таки, ми вважаємо за краще значення, яке трохи вище, ніж 0,2.

Зверніть увагу, що в обох випадках наближення для 0,1 та 0,2 мають незначне піднесення. Якщо ми додамо достатню кількість цих упереджень, вони будуть штовхати номер далі і далі від того, що ми хочемо, і, фактично, у випадку 0,1 + 0,2, зміщення досить високе, щоб отримане число більше не було найближчим числом до 0,3

Зокрема, 0,1 + 0,2 насправді 0,1000000000000000055511151231257827021181583404541015625 + 0.200000000000000011102230246251565404236316680908203125 = 0,3000000000000000444089209850062616169452667236328125, тоді як найближче число до 0,3 є фактично 0,299999999999999988897769753748434595763683319091796875.


П.С. Деякі мови програмування також забезпечують фрези для піци розділити скибочки на точні десяті. Незважаючи на те, що такі різаки для піци нечасті, якщо у вас є доступ до одного, ви повинні використовувати його, коли важливо мати точно одну десяту або одну п'яту частинки.

(Спочатку розміщено на Quora.)


226
2018-02-25 21:41



Зауважте, що існують деякі мови, які містять точну математику. Одним із прикладів є Схема, наприклад, через GNU Guile. Побачити draketo.de/english/exact-math-to-the-rescue - ці математики залишаються дрібними, а в кінці - лише зрізані. - Arne Babenhauserheide
@FloatingRock Насправді, дуже мало основних мов програмування мають вбудовані раціональні цифри. Арне це Схемер, як і я, тому це речі, які ми псуємо. - Chris Jester-Young
@ArneBabenhauserheide Я думаю, варто додати, що це буде працювати тільки з раціональними числами. Отже, якщо ви виконуєте певну математику з ірраціональними числами, такими як pi, вам слід зберегти її як множину pi. Звичайно, будь-яке розрахунку, пов'язане з pi, не може бути представлено як точне десяткове число. - Aidiakapi
@connexo Гаразд. Як би ви запрограмували свого ротатора піци, щоб отримати 36 градусів? Що таке 36 градусів? (Порада: якщо ви можете чітко визначити це, у вас також є шматочки-точна-десята піцерійка). Іншими словами, ви фактично не можете мати 1/360 (ступінь) або 1 / 10 (36 градусів) з тільки двійковою плаваючою крапкою. - Chris Jester-Young
@connexo Крім того, "кожен ідіот" не може обертати піцу точно 36 градусів Люди дуже схильні помилково робити щось настільки точне. - Chris Jester-Young


Помилки округлення з плаваючою точкою. 0.1 не може бути точно представлено в базі-2, як у базі-10, через відсутність простий множник 5. Точно так само, як 1/3 займає нескінченну кількість цифр для представлення у десятковій, але "0.1" в базі-3, 0,1 займає нескінченну кількість цифр в базі-2, де вона відсутня в базі-10. І комп'ютери не мають нескінченного обсягу пам'яті.


199
2018-04-09 12:25



комп'ютери не потребують нескінченного обсягу пам'яті, щоб отримати право 0.1 + 0.2 = 0.3 - Pacerier
@Pacerier Звичайно, вони можуть використовувати два цілих числа необмеженої точності для представлення частки або вони можуть використовувати цитату. Це специфічне поняття "бінарний" або "десятковий", що робить це неможливим - ідея про те, що у вас є послідовність двійкових чи десяткових цифр, а десь там, - точка зору. Щоб отримати точні раціональні результати, нам потрібен кращий формат. - Devin Jeanpierre
@Pacerier: Ні бінарна, ні десяткова плаваюча точка не можуть точно зберігати 1/3 або 1/13. Типи десятих з плаваючою точкою можуть точно представляти значення форми M / 10 ^ E, але менш точні, ніж двійкові числа з плаваючою точкою аналогічного розміру, коли мова йде про представлення більшості інших фракцій. У багатьох програмах корисніше мати більш високу точність з довільними фракціями, ніж точна точність з декількома "спеціальними". - supercat
@ Пацер'є їм робити якщо вони зберігають цифри як бінарні поплавки, що було точкою відповіді. - Mark Amery
@chux. Різниця в точності між двійковими та десятковими типом не величезна, однак різниця у 10: 1 у найкращому випадку і у найгіршому випадку для десяткових типів значно перевищує відмінність 2: 1 з бінарними типами. Мені цікаво, чи є хто-небудь збудованим апаратним або письмовим програмним забезпеченням, щоб ефективно працювати в будь-якому з десяткових типів, оскільки ніхто не може піддаватися ефективній реалізації на апаратному та програмному забезпеченні. - supercat


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

Наприклад:

var result = 1.0 + 2.0;     // result === 3.0 returns true

... замість:

var result = 0.1 + 0.2;     // result === 0.3 returns false

Вираз 0.1 + 0.2 === 0.3 повертає false в JavaScript, але, на щастя, арифметика цілих чисел у плаваючою точці точна, тому помилки десяткового подання можна уникнути шляхом масштабування.

Як практичний приклад, щоб уникнути проблем з плаваючою точкою, коли точність є найважливішою, рекомендується1 обробляти гроші як ціле число, що представляє кількість центів: 2550 центів замість 25.50 долари


1 Дуглас Крокфорд: JavaScript: хороші частини: Додаток A - Жахливі частини (стор. 105).


99
2018-02-23 17:15



Проблема полягає в тому, що саме перетворення є неточним. 16.08 * 100 = 1607.9999999999998. Чи потрібно вдаватися до розщеплення числа та перетворення окремо (як у 16 ​​* 100 + 08 = 1608)? - Jason
Рішення тут полягає у тому, щоб зробити всі ваші розрахунки в цілому, потім розділити на вашу пропорцію (100 у цьому випадку) і круглі лише при поданні даних. Це гарантує, що ваші розрахунки завжди будуть точні. - David Granado
Просто, щоб трохи посипати: числова арифметика точно в точці з плаваючою точкою до точки (призначений для каламбуру). Якщо число більше, ніж 0x1p53 (для вживання шістнадцяткового курсору з плаваючою комою у Java 7 = 9007199254740992), тоді в цьому пункті ulp дорівнює 2, тому 0x1p53 + 1 округлюється до 0x1p53 (і 0x1p53 + 3 округлюється до 0x1p53 + 4, внаслідок круглого столу). :-D Але, звичайно, якщо ваш номер менше, ніж 9 квадрильйонів, ви повинні бути в порядку. :-P - Chris Jester-Young
Отже, як ви отримаєте .1 + .2 показати .3? - CodyBugstein
Джейсон, ти повинен просто навколо результату (int) (16.08 * 100 + 0.5) - Mikhail Semenov


Моя відповідь досить довга, тому я розбив його на три розділи. Оскільки мова йде про математику з плаваючою точкою, я підкреслюю те, що насправді робить машина. Я також вказав на подвійну (64 бітну) точність, але аргумент застосовується однаково до будь-якої арифметики з плаваючою точкою.

Преамбула

Ан IEEE 754 формат дворівневої плаваючою точкою подвійної точності (binary64) Число представляє собою декілька форм

Значення = (-1) ^ s * (1.m51м50... м2м1м0)2 * 2Е-1023

в 64 біт:

  • Перший біт - це знак біт: 1 якщо число є негативним 0 інакше1.
  • Наступні 11 бітів є показник, який компенсувати на 1023. Іншими словами, після прочитання бітових експонентів з числа з подвійною точністю, 1023 слід вирахувати, щоб отримати силу двох.
  • Решта 52 біт є Значення (або мантиса). У мантісі "підозрюваний" 1. є завжди2 опущено, оскільки найзначніший біт будь-якого подвійного значення 1.

1 - IEEE 754 дозволяє використовувати поняття a підписаний нуль - +0 і -0 ставляться по-різному: 1 / (+0) є позитивною нескінченністю; 1 / (-0) це негативна нескінченність. Для нульових значень, мантиса та бітовіддачі є всіма нульовими. Примітка: нульові значення (+0 та -0) явно не класифікуються як денормальні2.

2 - Це не справа денормальні числа, які мають експоненту зсуву нуля (і припускається 0.) Діапазон денормальних подвійних точок числа dхв ≤ | x | ≤ dмакс, де dхв (найменше представлене ненульове число) - 2-1023 - 51 (≈4,94 * 10-324) і дмакс (найбільше денормальне число, для якого мантиса складається цілком з 1с) становить 2-1023 + 1 - 2-1023 - 51 (≈2.225 * 10-308)


Перетворення подвійної точності в дворядковий

Є багато онлайн-перетворювачів для перетворення числа з подвійною точністю з плаваючою комою у бінарну (наприклад, у binaryconvert.com), але ось приклад з кодом C # для отримання представлення IEEE 754 для номера подвійної точності (я відокремлюю три частини з двокрапками (:):

public static string BinaryRepresentation(double value)
{
    long valueInLongType = BitConverter.DoubleToInt64Bits(value);
    string bits = Convert.ToString(valueInLongType, 2);
    string leadingZeros = new string('0', 64 - bits.Length);
    string binaryRepresentation = leadingZeros + bits;

    string sign = binaryRepresentation[0].ToString();
    string exponent = binaryRepresentation.Substring(1, 11);
    string mantissa = binaryRepresentation.Substring(12);

    return string.Format("{0}:{1}:{2}", sign, exponent, mantissa);
}

Початок до пункту: оригінальне питання

(Перейти донизу для версії TL; DR)

Катон Джонстон (питання автора) запитав, чому 0.1 + 0.2! = 0.3.

Написано у бінарному (з двокрапками, що розділяють три частини), уявленням IEEE 754 цих значень є:

0.1 => 0:01111111011:1001100110011001100110011001100110011001100110011010
0.2 => 0:01111111100:1001100110011001100110011001100110011001100110011010

Зауважте, що мантиса складається з повторюваних цифр 0011. Це ключ чому в розрахунках є будь-яка помилка - 0,1, 0,2 та 0,3 не можуть бути представлені в двійковій системі точно в кінцевий число двійкових бітів більше ніж 1/9, 1/3 або 1/7 може бути представлено саме в десяткові цифри.

Перетворення експонентів до десяткового, видалення зсуву та повторне додавання натяку 1 (у квадратних дужках), 0,1 та 0,2 є:

0.1 = 2^-4 * [1].1001100110011001100110011001100110011001100110011010
0.2 = 2^-3 * [1].1001100110011001100110011001100110011001100110011010

Щоб додати два числа, показник має бути однаковим, тобто:

0.1 = 2^-3 *  0.1100110011001100110011001100110011001100110011001101(0)
0.2 = 2^-3 *  1.1001100110011001100110011001100110011001100110011010
sum = 2^-3 * 10.0110011001100110011001100110011001100110011001100111

Оскільки сума не має форми 2н * 1. {bbb} ми збільшуємо показник на одиницю та змінюємо десятковий (бінарний) вказують на отримання:

sum = 2^-2 * 1.0011001100110011001100110011001100110011001100110011(1)

У мантісі зараз 53 біти (53 рядка знаходиться у квадратних дужках у рядку вище). За умовчанням режим округлення для IEEE 754 це "Круглий ближчий"- наприклад, якщо номер х падає між двома значеннями a і б, вибирається значення, у якому найменш значущий біт дорівнює нулю.

a = 2^-2 * 1.0011001100110011001100110011001100110011001100110011
x = 2^-2 * 1.0011001100110011001100110011001100110011001100110011(1)
b = 2^-2 * 1.0011001100110011001100110011001100110011001100110100

Зауважте, що a і б відрізняються лише в останньому розряді; ...0011 + 1 = ...0100. У цьому випадку значення з найменш значущим біт нуля становить б, тому сума:

sum = 2^-2 * 1.0011001100110011001100110011001100110011001100110100

TL; DR

Написання 0.1 + 0.2 в бінарному представленні IEEE 754 (з двокрапками, що розділяють три частини) і порівняння його з 0.3, це (я поставив окремі біти в квадратні дужки):

0.1 + 0.2 => 0:01111111101:0011001100110011001100110011001100110011001100110[100]
0.3       => 0:01111111101:0011001100110011001100110011001100110011001100110[011]

Перетворені назад у десяткову, ці значення:

0.1 + 0.2 => 0.300000000000000044408920985006...
0.3       => 0.299999999999999988897769753748...

Різниця рівно 2-54, що становить ~ 5.5511151231258 × 10-17 - незначний (для багатьох застосувань) у порівнянні з вихідними значеннями.

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

Більшість калькуляторів використовують додаткові вартові цифри щоб обійти цю проблему, яка полягає в тому, як це зробити 0.1 + 0.2 дасть 0.3: останні кілька бітів округлені.


81
2018-03-16 05:27



Моя відповідь була відхилена незабаром після публікації. З тих пір я зробив багато змін (у тому числі, явно зазначаю повторювані біти при написанні 0.1 та 0.2 в бінарному, які я опустив у оригіналі). На випадок, що нижчий виборчик бачить це, чи могли б ви надати мені певні відгуки, щоб я міг поліпшити свою відповідь? Я відчуваю, що моя відповідь додає щось нове, оскільки обробка цієї суми в IEEE 754 не розглядається аналогічним чином в інших відповідях. Хоча "те, що повинен знати кожен комп'ютерний вчений ..." охоплює той самий матеріал, моя відповідь стосується конкретно з футляром 0,1 + 0,2. - Wai Ha Lee


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

Якщо комп'ютер працював в базі 10, 0.1 був би 1 x 10⁻¹, 0.2 був би 2 x 10⁻¹, і 0.3 був би 3 x 10⁻¹. Цілому математику легко і точно, тому додавання 0.1 + 0.2 очевидно, призведе до 0.3.

Комп'ютери зазвичай не працюють у базі 10, вони працюють у базовому 2. Ви, як і раніше, можете отримувати точні результати для деяких значень, наприклад 0.5 є 1 x 2⁻¹ і 0.25 є 1 x 2⁻², і додавання їх результатів 3 x 2⁻², або 0.75. Точно

Проблема постачається з числами, які можуть бути представлені точно в базі 10, але не в базі 2. Ці числа потрібно округлити до найближчого еквівалента. Припускаючи, що найпоширеніший формат з плаваючою комою IEEE 64-розрядний, найближчий до 0.1 є 3602879701896397 x 2⁻⁵⁵, і найближче число до 0.2 є 7205759403792794 x 2⁻⁵⁵; додавання їх разом 10808639105689191 x 2⁻⁵⁵, або точне десяткове значення для 0.3000000000000000444089209850062616169452667236328125. Номери з плаваючою точкою зазвичай округлені для відображення.


48
2018-02-25 21:42



@Марк Дякую за це чітке пояснення, але тоді виникає питання, чому 0.1 + 0.4 точно додає до 0.5 (принаймні в Python 3). Також який найкращий спосіб перевірити рівність при використанні поплавців у Python 3? - pchegoor
@ user2417881 Операції з плаваючою точкою IEEE мають правила округлення для кожної операції, а іноді округлення може дати точну відповідь, навіть якщо два числа зникнуть трохи. Деталі занадто довгі для коментарів, і я ні в якій мірі не є експертом у них. Як ви бачите у цій відповіді, 0,5 - це один з декількох десяткових знаків, які можуть бути представлені в двійковій системі, але це просто збіг. Для перевірки рівності див stackoverflow.com/questions/5595425/.... - Mark Ransom
@ user2417881 ваше запитання зацікавило мене, тому я перетворив його на повне запитання та відповідь: stackoverflow.com/q/48374522/5987 - Mark Ransom