Чому числа з плаваючою комою є неточними?


198

Чому деякі числа втрачають точність, зберігаючи їх як числа з плаваючою комою?

Наприклад, десяткове число 9.2можна виразити точно як відношення двох десяткових цілих чисел ( 92/10), обидва з яких можна виразити точно у двійковій ( 0b1011100/0b1010). Однак те саме співвідношення, яке зберігається як число з плаваючою комою, ніколи точно не дорівнює 9.2:

32-bit "single precision" float: 9.19999980926513671875
64-bit "double precision" float: 9.199999999999999289457264239899814128875732421875

Як таке, очевидно, просте число може бути "занадто великим", щоб виразити в 64 бітах пам'яті?




Відповіді:


242

У більшості мов програмування числа з плаваючою комою представлені так само, як наукові позначення : з експонентом і мантісою (також називається знаменням). Скажімо 9.2, дуже просте число - це насправді ця частка:

5179139571476070 * 2 -49

Там, де є показник, -49і є мантіса 5179139571476070. Причиною неможливості представити деякі десяткові числа таким чином є те, що і експонент, і мантіса повинні бути цілими числами. Іншими словами, всі плавці повинні бути цілим числом, помноженим на цілу силу 2 .

9.2може бути просто 92/10, але 10 не може бути виражено як 2 n, якщо n обмежено цілими значеннями.


Побачення даних

По-перше, кілька функцій, щоб побачити компоненти, які створюють 32- та 64-бітні float. Замальовуйте їх, якщо ви дбаєте лише про вихід (наприклад, у Python):

def float_to_bin_parts(number, bits=64):
    if bits == 32:          # single precision
        int_pack      = 'I'
        float_pack    = 'f'
        exponent_bits = 8
        mantissa_bits = 23
        exponent_bias = 127
    elif bits == 64:        # double precision. all python floats are this
        int_pack      = 'Q'
        float_pack    = 'd'
        exponent_bits = 11
        mantissa_bits = 52
        exponent_bias = 1023
    else:
        raise ValueError, 'bits argument must be 32 or 64'
    bin_iter = iter(bin(struct.unpack(int_pack, struct.pack(float_pack, number))[0])[2:].rjust(bits, '0'))
    return [''.join(islice(bin_iter, x)) for x in (1, exponent_bits, mantissa_bits)]

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

Python float- це 64-бітове число з подвоєною точністю. В інших мовах, таких як C, C ++, Java і C #, подвійна точність має окремий тип double, який часто реалізується у вигляді 64 біт.

Коли ми називаємо цю функцію своїм прикладом 9.2, ось що ми отримуємо:

>>> float_to_bin_parts(9.2)
['0', '10000000010', '0010011001100110011001100110011001100110011001100110']

Інтерпретація даних

Ви побачите, що я розділив повернене значення на три компоненти. До таких компонентів належать:

  • Знак
  • Експонент
  • Mantissa (також називається Significand, або Фракція)

Знак

Знак зберігається в першому компоненті як єдиний біт. Це легко пояснити: 0означає, що поплавок - це додатне число; 1означає, що це негативно. Оскільки 9.2позитивне, наше знакове значення 0.

Експонент

Експонент зберігається в середньому компоненті як 11 біт. У нашому випадку 0b10000000010. У десятковій частині це означає значення 1026. Вигадка цього компонента полягає в тому, що вам потрібно відняти число, що дорівнює 2 (# біт) - 1 - 1, щоб отримати справжній показник; у нашому випадку це означає віднімання 0b1111111111(десяткове число 1023), щоб отримати справжній показник, 0b00000000011(десяткове число 3).

Мантісса

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

6.0221413х10 23

Mantissa була б 6.0221413. Нагадаємо, що мантіса у наукових позначеннях завжди починається з однієї ненульової цифри. Те ж саме стосується двійкового, за винятком того, що двійковий має лише дві цифри: 0і 1. Тож двійкова мантія завжди починається з 1! Коли поплавок зберігається, 1фронт двійкової мантіси опускається, щоб заощадити місце; ми повинні розмістити його в передній частині нашого третього елемента, щоб отримати справжню мантісу:

1.0010011001100110011001100110011001100110011001100110

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

Маючи справу з десятковими числами, ми «переміщаємо десяткову точку» шляхом множення або ділення на потужності 10. У двійкових ми можемо зробити те саме, множивши або ділимо на потужності на 2. Оскільки наш третій елемент має 52 біт, ми ділимо це на 2 52, щоб перемістити 52 місця праворуч:

0,0010011001100110011001100110011001100110011001100110

У десятковій системі числення, це те ж саме , як поділ 675539944105574на 4503599627370496отримати 0.1499999999999999. (Це один приклад співвідношення, яке можна виразити точно у двійковій формі, але лише приблизно у десятковій формі ; детальніше див.: 675539944105574/4503599627370496 .)

Тепер, коли ми перетворили третій компонент у дробове число, додавання 1дає справжню мантісу.

Перекомпонування компонентів

  • Знак (перший компонент): 0за позитивний, 1за негативний
  • Експонент (середній компонент): відніміть 2 (# біт) - 1 - 1, щоб отримати справжній показник
  • Мантісса (останній компонент): розділіть на 2 (# біт) і додайте, 1щоб отримати справжню мантісу

Обчислення числа

Збираючи всі три частини разом, ми отримуємо це двійкове число:

1.0010011001100110011001100110011001100110011001100110 x 10 11

Що ми можемо потім перетворити з двійкового в десятковий:

1.1499999999999999 x 2 3 (неточно!)

І множимо, щоб виявити остаточне подання числа, яке ми почали з ( 9.2) після збереження у вигляді значення з плаваючою комою:

9.1999999999999993


Представляючи себе дробом

9.2

Тепер, коли ми побудували число, можна реконструювати його в простий дріб:

1.0010011001100110011001100110011001100110011001100110 x 10 11

Змініть мантісу на цілу кількість:

10010011001100110011001100110011001100110011001100110 x 10 11-110100

Перетворити в десятковий:

5179139571476070 х 2 3-52

Віднімаємо показник:

5179139571476070 х 2 -49

Перетворіть негативний показник у поділ:

5179139571476070/2 49

Помножимо показник:

5179139571476070/562949953421312

Що дорівнює:

9.1999999999999993

9.5

>>> float_to_bin_parts(9.5)
['0', '10000000010', '0011000000000000000000000000000000000000000000000000']

Вже ви можете бачити мантісу лише 4 цифри, а за ними ціла партія нулів. Але пройдемося кроками.

Зберіть бінарні наукові позначення:

1,0011 x 10 11

Зсуньте десяткову точку:

10011 х 10 11-100

Віднімаємо показник:

10011 х 10 -1

Двійкові до десяткових:

19 х 2 -1

Негативний показник поділу:

19/2 1

Помножимо показник:

19/2

Дорівнює:

9.5



Подальше читання


1
Також є хороший підручник, який показує, як рухати іншим шляхом - з урахуванням десяткового подання числа, як ви будуєте еквівалент плаваючої точки. Підхід "довгого поділу" дуже чітко показує, як ви закінчуєте "залишок" після спроби представити число. Слід додати, якщо ви хочете бути справді "канонічними" зі своєю відповіддю.
Флоріс

1
Якщо ви говорите про Python та плаваючу крапку, я б запропонував принаймні включити підручник з Python у ваші посилання: docs.python.org/3.4/tutorial/floatingpoint.html Це повинно бути єдиним кроком ресурс для питань з плаваючою комою для програмістів Python. Якщо його в чомусь не вистачає (і це майже напевно є), будь ласка, відкрийте проблему в трекері помилок Python для оновлень або змін.
Марк Дікінсон

@mhlester Якщо це перетвориться на вікі спільноти, сміливо включайте мою відповідь у вашу.
Nicu Stiurca

5
Ця відповідь, безумовно, повинна також посилатися на floating-point-gui.de , оскільки це, мабуть, найкраще вступ для початківців. ІМО, це навіть повинно йти вище "Те, що повинен знати кожен комп'ютерний вчений ..." - в ці дні люди, які розумно можуть зрозуміти папір Голдберга, зазвичай це вже добре знають.
Даніель Приден

1
"Це один приклад співвідношення, яке може бути виражене точно у двійковій формі, але лише приблизно у десятковій". Це не правда. Усі ці співвідношення «число над потужністю два» є точними в десяткових числах. Будь-яке наближення полягає лише в скороченні десяткового числа - для зручності.
Рік Реган

29

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

Розглянемо дріб 2/3

У базовій базі 10 ми зазвичай пишемо це як щось подібне

  • 0,666 ...
  • 0,666
  • 0,667

Коли ми дивимось на ці уявлення, ми схильні асоціювати кожне з них із дробом 2/3, хоча лише перше представлення математично дорівнює дробу. Друге та третє подання / наближення мають помилку порядку 0,001, що насправді набагато гірше, ніж помилка між 9,2 та 9,1999999999999993. Насправді, друге представлення навіть не правильно округлене! Тим не менш, у нас немає проблеми з 0.666 як наближення числа 2/3, тому ми не повинні насправді мати проблеми з тим, як наближається 9.2 у більшості програм . (Так, у деяких програмах це важливо.)

Основи чисел

Тож ось, де кількість баз є вирішальним. Якщо ми намагалися представити 2/3 в базі 3, то

(2/3) 10 = 0,2 3

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

Щоб загнати цю точку додому, давайте подивимось на 1/2. Вас може здивувати, що, хоча це абсолютно просте число має точне подання в базі 10 і 2, воно вимагає повторного подання в базі 3.

(1/2) 10 = 0,5 10 = 0,1 2 = 0,1111 ... 3

Чому числа з плаваючою комою є неточними?

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


3
Отже, іншими словами, база-3 був би ідеальним для 1/3як базової 10 ідеально підходить для 1/10. Жодна фракція не працює в базі-2
mhlester

2
@mhlester Так. І взагалі, base-N ідеально підходить для будь-якої дроби, знаменником якої є Nчи кратна.
Nicu Stiurca

2
І це одна з причин, чому деякі числові коробки інструментів відслідковують "що було поділено на що", і в процесі цього можна зберігати "нескінченну точність" для всіх раціональних чисел. Так само, як фізики люблять зберігати рівняння символічними до останнього можливого моменту, у разі πвідміни факторів тощо.
Флоріс

3
@Floris Я також бачив випадки, коли алгоритм, який виконує лише основні арифметичні (тобто зберігає раціональність введення), визначає, чи був вхід (радше) раціональним, виконував математику, використовуючи звичайну арифметику з плаваючою комою, а потім переоцінював раціональну наближення в кінці, щоб виправити будь-які помилки округлення. Зокрема, це робить алгоритм форми ешелону скорочених ешелонів Matlab , і це надзвичайно допомагає чисельній стабільності.
Nicu Stiurca

@SchighSchagh - цікаво, я цього не знав. Я знаю, що чисельна стабільність - це те, чого в ці дні недостатньо навчають подвійній подвійній точності. Що означає, що багато хто сумує, дізнавшись про витонченість багатьох прекрасних алгоритмів. Мені дуже подобаються алгоритми, які обчислюють та виправляють власні помилки.
Флоріс

13

Хоча всі інші відповіді хороші, все одно відсутнє:

Неможливо уявити ірраціональні числа (наприклад , π, sqrt(2), log(3)і т.д.) точно!

І тому насправді їх називають ірраціональними. Жодної кількості бітових сховищ у світі було б недостатньо, щоб вмістити навіть один із них. Лише символічна арифметика здатна зберегти їх точність.

Хоча якщо ви обмежите свої математичні потреби раціональними числами, лише проблема точності стає керованою. Вам потрібно буде зберігати пару (можливо, дуже великих) цілих чисел aі bутримувати число, представлене дробом a/b. Вся ваша арифметика повинна бути виконана на дробах, як у математиці середньої школи (наприклад a/b * c/d = ac/bd).

Але, звичайно , ви все одно зіткнетеся з такою ж проблемою , коли pi, sqrt, log, sinі т.д. беруть участь.

TL; DR

Для апаратної прискореної арифметики може бути представлена ​​лише обмежена кількість раціональних чисел. Кожне нерепрезентабельне число є приблизним. Деякі числа (тобто ірраціональні) ніколи не можуть бути представлені незалежно від системи.


4
Цікаво, що ірраціональні підстави існують. Фінар , наприклад.
Ведрак

5
ірраціональні числа можуть бути (лише) представлені в їх основі. Наприклад, pi 10 в базовому pi
phuclv

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

4

Є нескінченно багато реальних чисел (стільки, що їх неможливо перерахувати), і є нескінченно багато раціональних чисел (їх можна перерахувати).

Представлення з плаваючою комою є кінцевим (як і все, що є в комп'ютері), тому неминуче представити багато багатьох чисел неможливо. Зокрема, 64 біти дозволяють розрізнити лише 18,446,744,073,709,551,616 різних значень (що є нічим порівняно з нескінченністю). За стандартної конвенції 9.2 не є однією з них. Ті, що можуть мати вигляд m.2 ^ e для деяких цілих чисел m і e.


Ви можете придумати іншу систему числення, наприклад 10, де 9.2 матиме точне подання. Але інші числа, скажімо 1/3, представляти все ж неможливо.


Також зауважте, що цифри з плаваючою комою подвійної точності надзвичайно точні. Вони можуть представляти будь-яке число у дуже широкому діапазоні з цілими 15 точними цифрами. Для розрахунків у повсякденному житті 4 або 5 цифр більш ніж достатньо. Вам ніколи не знадобляться ці 15, якщо ви не захочете рахувати кожну мілісекунд свого життя.


1

Чому ми не можемо представити 9.2 у двійковій плаваючій точці?

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

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

Прості коефіцієнти 10 - це 5 і 2, тому в основі 10 ми можемо представити будь-яку частку форми a / (2 b 5 c ).

З іншого боку, єдиним простим коефіцієнтом 2 є 2, тому в основі 2 ми можемо представляти лише дроби виду a / (2 b )

Чому комп'ютери використовують це представлення?

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

Безумовно, можна було б визначити формат дробу, (наприклад, 32-розрядний чисельник та 32-розрядний знаменник. Він міг би представляти числа, які плаваюча точка подвійної точності IEEE не могла, але однаково було б багато чисел, які можна представити у плаваючій точці подвійної точності, які не могли бути представлені у такому форматі дробу фіксованого розміру.

Однак велика проблема полягає в тому, що такий формат - це біль робити розрахунки. З двох причин.

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

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

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


-4

Спробуйте це

DecimalFormat decimalFormat = new DecimalFormat("#.##");
String.valueOf(decimalFormat.format(decimalValue))));

' decimalValue' - це ваше значення для конвертації.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.