Розглянемо наступний код:
0.1 + 0.2 == 0.3 -> false
0.1 + 0.2 -> 0.30000000000000004
Чому трапляються ці неточності?
Розглянемо наступний код:
0.1 + 0.2 == 0.3 -> false
0.1 + 0.2 -> 0.30000000000000004
Чому трапляються ці неточності?
Відповіді:
Двійкова математика з плаваючою комою така. У більшості мов програмування він базується на стандарті IEEE 754 . Суть проблеми полягає в тому, що числа представлені в такому форматі як ціле число, що перевищує потужність двох; раціональні числа (наприклад 0.1
, що є 1/10
), знаменник яких не є силою двох, не можуть бути точно представлені.
Для 0.1
стандартного binary64
формату представлення може бути записане точно так само
0.1000000000000000055511151231257827021181583404541015625
у десятковій або0x1.999999999999ap-4
в C99 hexfloat нотації .На відміну від цього, раціональне число 0.1
, яке є 1/10
, можна записати саме так
0.1
у десятковій або0x1.99999999999999...p-4
в аналозі позначення шестигранних пластів С99, де ...
являє собою нескінченну послідовність 9-х.Константи 0.2
і 0.3
у вашій програмі також будуть наближеними до їх справжніх значень. Буває, що найближчий double
до 0.2
більший за раціональне число, 0.2
але найближчий double
до 0.3
менший за раціональне число 0.3
. Сума 0.1
та 0.2
звивистість перевищує раціональне число, 0.3
а значить, не погоджуючись з постійною у вашому коді.
Досить всебічне трактування питань арифметики з плаваючою комою - це те, що повинен знати кожен арифметик з плаваючою комою . Для легшого засвоєння пояснення див. Floating-point-gui.de .
Бічна примітка: Усі позиційні (базові N) системи з точністю поділяють цю проблему з точністю
Прості старі десяткові (базові 10) числа мають однакові питання, через що числа типу 1/3 закінчуються як 0,333333333 ...
Ви просто натрапили на число (3/10), яке може бути легко представити десятковою системою, але не підходить для двійкової системи. Це іде і в обидва напрямки (в деякій мірі): 1/16 - це некрасиве число в десятковій частині (0,0625), але у двійковій формі воно виглядає так само акуратно, як 10 000-е в десятковій (0,0001) ** - якби ми були в звичка використовувати систему чисел базової-2 у повсякденному житті, ви навіть подивитесь на це число і інстинктивно зрозумієте, що можете приїхати туди, вдвічі зменшивши його, вдвічі зменшивши її, знову і знову.
** Звичайно, це не зовсім те, як числа з плаваючою комою зберігаються в пам'яті (вони використовують форму наукової нотації). Однак це ілюструє те, що двійкові помилки точності з плаваючою комою, як правило, з'являються, оскільки "реальні" цифри, з якими ми зазвичай зацікавлені в роботі, настільки часто мають десять - але лише тому, що ми використовуємо систему десяткових чисел day- на сьогоднішній день. Ось чому ми скажемо такі речі, як 71% замість "5 з кожні 7" (71% - це наближення, оскільки 5/7 не може бути представлено точно жодним десятковим числом).
Так що ні: двійкові числа з плаваючою комою не порушені, вони просто виявляються такими ж недосконалими, як і всі інші базові системи N чисел :)
Бічна сторона Примітка: Робота з поплавками в програмуванні
На практиці ця проблема точності означає, що вам потрібно використовувати функції округлення для округлення чисел з плаваючою комою до тих чи інших десяткових знаків, які вас цікавлять, перш ніж їх відображати.
Вам також потрібно замінити тести на рівність порівняннями, які дозволяють отримати певну толерантність, що означає:
Як НЕ робитиif (x == y) { ... }
Натомість робити if (abs(x - y) < myToleranceValue) { ... }
.
де abs
абсолютна величина. myToleranceValue
потрібно вибрати для вашої конкретної програми - і це матиме багато спільного з тим, скільки "хитає кімната", яку ви готові дозволити, і яке найбільше число ви збираєтеся порівнювати (через втрату питань точності ). Остерігайтеся констант стилю "епсілон" у вибраній вами мові. Вони не повинні використовуватися як значення толерантності.
Я вважаю, що я повинен додати перспективу дизайнера обладнання до цього, оскільки розробляю і будую апаратне забезпечення з плаваючою комою. Знання походження помилки може допомогти зрозуміти, що відбувається в програмному забезпеченні, і, зрештою, я сподіваюся, що це допоможе пояснити причини того, чому трапляються помилки з плаваючою комою і, здається, накопичуються з часом.
З інженерної точки зору, більшість операцій з плаваючою комою матиме деякий елемент помилки, оскільки обладнання, яке виконує обчислення з плаваючою комою, вимагає лише помилки менше половини однієї одиниці в останньому місці. Таким чином, багато обладнання зупиняється на точності, необхідній лише для отримання помилки менше половини однієї одиниці в останньому місці для однієї операції, що особливо проблематично при поділі з плаваючою комою. Що становить одну операцію, залежить від того, скільки операндів приймає одиниця. Для більшості це два, але деякі одиниці приймають 3 і більше операндів. Через це не існує гарантії, що повторні операції призведуть до бажаної помилки, оскільки помилки накопичуються з часом.
Більшість процесорів дотримуються стандарту IEEE-754, але деякі використовують денормалізовані або інші стандарти. Наприклад, в IEEE-754 існує денормалізований режим, який дозволяє представляти дуже малі числа з плаваючою комою за рахунок точності. Наступне, однак, стосуватиметься нормалізованого режиму IEEE-754, що є типовим режимом роботи.
У стандарті IEEE-754 дизайнерам апаратних засобів допускається будь-яке значення помилки / epsilon до тих пір, поки на останньому місці це менше половини однієї одиниці, і результат повинен бути менше половини однієї одиниці в останньому місці місце для однієї операції. Це пояснює, чому при повторних операціях помилки складаються. Для подвійної точності IEEE-754 - це 54-й біт, оскільки 53 біти використовуються для представлення числової частини (нормалізованої), також називається мантіси, числа плаваючої точки (наприклад, 5.3 в 5.3e5). У наступних розділах детальніше описуються причини апаратних помилок різних операцій з плаваючою точкою.
Основна причина помилки при поділі з плаваючою комою - алгоритми поділу, що використовуються для обчислення коефіцієнта. Більшість комп'ютерних систем обчислюють поділ, використовуючи множення на обернене, головним чином на Z=X/Y
,Z = X * (1/Y)
. Ділення обчислюється ітераційно, тобто кожен цикл обчислює деякі біти коефіцієнта до досягнення бажаної точності, що для IEEE-754 є останньою помилкою, меншою за одну одиницю. Таблиця зворотних зворотів Y (1 / Y) відома як таблиця вибору коефіцієнтів (QST) у повільному поділі, а розмір у бітах таблиці відбору коефіцієнтів, як правило, є шириною радіусу або кількох бітів коефіцієнт, обчислений у кожній ітерації, плюс кілька захисних біт. Для стандарту IEEE-754, подвійна точність (64-розрядна), це був би розмір радіусу дільника, плюс декілька захисних біт k, де k>=2
. Так, наприклад, типова таблиця вибору коефіцієнта для дільника, що обчислює 2 біти коефіцієнта одночасно (радіус 4), буде 2+2= 4
бітами (плюс кілька необов'язкових біт).
3.1 Помилка округлення підрозділу: наближення взаємної
Які взаємні дані в таблиці вибору коефіцієнтів залежать від методу поділу : повільний поділ, такий як поділ СТО, або швидкий поділ, такий як поділ Гольдшмідта; кожен запис модифікується відповідно до алгоритму поділу, намагаючись отримати мінімально можливу помилку. У будь-якому випадку, все взаємні відповіді є наближеннямифактичної взаємної і ввести деякий елемент помилки. І методи повільного ділення, і швидке ділення обчислюють коефіцієнт ітеративно, тобто деяку кількість бітів коефіцієнта обчислюють кожен крок, потім результат віднімають з дивіденду, і дільник повторює кроки, поки помилка не буде меншою половини одиниці одиниця в останньому місці. Методи повільного ділення обчислюють фіксовану кількість цифр коефіцієнта на кожному кроці і, як правило, є менш дорогими для побудови, а швидкі методи поділу обчислюють змінну кількість цифр за крок і, як правило, дорожче будувати. Найважливіша частина методів поділу полягає в тому, що більшість з них покладається на повторне множення наближенням зворотного зв'язку, тому вони схильні до помилок.
Ще однією причиною помилок округлення у всіх операціях є різні режими усікання остаточної відповіді, які дозволяє IEEE-754. Існують усічення, «круглий бік» до «нуля», « круглий до найближчого» (за замовчуванням), « округлення» та «округлення». Усі методи вводять елемент помилки менше однієї одиниці в останню чергу за одну операцію. З часом і повторних операцій усічення також додає кумулятивно до отриманої помилки. Ця помилка усікання є особливо проблематичною при експоненції, яка передбачає певну форму повторного множення.
Оскільки апаратне забезпечення, яке виконує обчислення з плаваючою комою, повинно отримати результат лише з помилкою менше половини однієї одиниці в останньому місці для однієї операції, помилка зростатиме при повторних операціях, якщо їх не спостерігати. Це причина того, що в обчисленнях, які вимагають обмеженої помилки, математики використовують такі методи, як використання округлої до найближчої парної цифри в останньому місці IEEE-754, оскільки з часом помилки швидше скасовують одна одну Вихід, і інтервальна арифметика поєднується з варіаціями режимів округлення IEEE 754передбачити помилки округлення та виправити їх. Через низьку відносну похибку в порівнянні з іншими режимами округлення, округлення до найближчої парної цифри (останнє місце) є режимом округлення IEEE-754 за замовчуванням.
Зауважте, що режим округлення за замовчуванням, округлення до найближчої парної цифри на останньому місці , гарантує помилку менше половини однієї одиниці в останньому місці за одну операцію. Використання обрізання, округлення та поодинці вниз може призвести до помилки, що перевищує половину однієї одиниці на останньому місці, але менше однієї одиниці на останньому місці, тому такі режими не рекомендуються, якщо вони не використовується в інтервальній арифметиці.
Коротше кажучи, фундаментальною причиною помилок в операціях з плаваючою комою є поєднання усікання апаратних засобів і усічення зворотного у випадку поділу. Оскільки стандарт IEEE-754 вимагає лише помилки менше половини однієї одиниці в останньому місці для однієї операції, помилки з плаваючою комою над повторними операціями будуть додаватися, якщо не буде виправлено.
Перетворюючи .1 або 1/10 в базу 2 (двійковий), ви отримуєте повторюваний візерунок після десяткової крапки, як і намагаючись представити 1/3 в базі 10. Значення не є точним, і тому ви не можете зробити точна математика з ним, використовуючи звичайні методи з плаваючою точкою.
Більшість відповідей тут стосується цього питання в дуже сухих технічних умовах. Я хотів би вирішити це з точки зору того, що нормальна людина може зрозуміти.
Уявіть, що ви намагаєтеся нарізати піци. У вас є роботизований різак для піци, який може різати шматочки піци рівно навпіл. Він може розрізати наполовину цілу піцу, або навпіл існуючий шматочок, але в будь-якому випадку, половина завжди точна.
Цей різак для піци має дуже тонкі рухи, і якщо ви починаєте з цілої піци, то зменшуйте її вдвічі, і продовжуйте вдвічі зменшувати найменший шматочок, ви можете робити навпіл у 53 рази, перш ніж скибочка буде занадто маленькою навіть для її високоточних здібностей . У цей момент ви вже не можете вдвічі зменшувати цей дуже тонкий шматочок, а мусите його включати або виключати таким, яким він є.
Тепер, як би ви склали всі шматочки таким чином, щоб додати до однієї десятої (0,1) або однієї п’ятої (0,2) піци? Дійсно подумай над цим і спробуй це опрацювати. Ви навіть можете спробувати використовувати справжню піцу, якщо у вас є під рукою міфічна точність різака для піци. :-)
Більшість досвідчених програмістів, звичайно, знають справжню відповідь, яка полягає в тому, що немає можливості зібрати точну десяту або п’яту частину піци за допомогою цих скибочок, незалежно від того, наскільки тонко ви їх нарізаєте. Ви можете зробити досить гарне наближення, і якщо скласти наближення 0,1 з наближенням 0,2, ви отримаєте досить гарне наближення 0,3, але все одно це лише наближення.
Для подвійних точних цифр (це точність, яка дозволяє вдвічі зменшити піцу в 53 рази), цифри одразу менші та більші за 0,1 - 0,099999999999999167332731531132594682276248931884765625 та 0,1000000000000000055511151231257827021181583404541015625. Останнє трохи ближче до 0,1, ніж перше, тому числовий аналізатор буде, даючи вхід 0,1, надавати перевагу останньому.
(Різниця між цими двома числами - це "найменший зріз", який ми маємо вирішити або включити, який вводить ухил вгору, або виключити, що вносить ухил вниз. Технічний термін для цього найменшого шматочка - це ulp .)
У випадку 0,2 цифри все однакові, просто збільшені на коефіцієнт 2. Знову ми віддаємо перевагу значенню, яке трохи вище 0,2.
Зауважте, що в обох випадках наближення для 0,1 та 0,2 мають невеликий зміщення вгору. Якщо ми додамо достатньо цих упереджень, вони відсунуть число все далі і далі від того, що ми хочемо, і насправді, у випадку 0,1 + 0,2, зміщення буде досить високим, що отримане число вже не є найближчим числом до 0,3.
Зокрема, 0,1 + 0,2 - це дійсно 0,1000000000000000055511151231257827021181583404541015625 + 0.200000000000000011102230246251565404236316680908203125 = 0.3000000000000000444089209850062616169452667236328125, тоді, як число цифри є найменшим, а потім число факту становить, що є найменшим числом.
PS Деякі мови програмування також надають різаки для піци, які можуть розділити скибочки на точні десяті частини . Хоча такі різаки для піци є рідкістю, якщо у вас є доступ до однієї, вам слід користуватися нею, коли важливо мати можливість отримати рівно одну десяту або п’яту частину шматочка.
Помилки округлення з плаваючою комою. 0,1 не може бути представлено настільки точно в базі-2, як у базовій-10, через відсутність основного коефіцієнта 5. Так само, як 1/3 має нескінченну кількість цифр для подання у десятковій формі, але "0,1" у базі-3, 0,1 приймає нескінченну кількість цифр у базі-2, де немає у базі-10. А комп'ютери не мають нескінченної кількості пам'яті.
Окрім інших правильних відповідей, ви можете розглянути можливість масштабування своїх значень, щоб уникнути проблем з арифметикою з плаваючою комою.
Наприклад:
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) .
Моя відповідь досить довга, тому я розділив її на три розділи. Оскільки питання стосується математики з плаваючою комою, я наголосив на тому, що насправді робить машина. Я також конкретизував подвійну (64-бітну) точність, але аргумент застосовується однаково до будь-якої арифметики з плаваючою точкою.
Преамбула
IEEE 754 з подвійною точністю в довічним форматі з плаваючою точкою (binary64) число являє собою число виду
значення = (-1) ^ s * (1.m 51 m 50 ... m 2 m 1 m 0 ) 2 * 2 e-1023
в 64 бітах:
1
якщо число від’ємне, 0
інакше 1 .1.
завжди 2 опущено, оскільки найзначніший біт будь-якого бінарного значення є 1
.1 - IEEE 754 дозволяє концепцію підписаного нуля - +0
і -0
трактуються по-різному: 1 / (+0)
є позитивна нескінченність; 1 / (-0)
є негативною нескінченністю. При нульових значеннях біти мантіси та експонента всі нульові. Примітка: нульові значення (+0 і -0) явно не класифікуються як деннормальні 2 .
2 - Це не стосується деннормальних чисел , які мають показник зміщення нуля (і мається на увазі 0.
). Діапазон деннормальних чисел подвійної точності d min ≤ | x | ≤ d max , де d min (найменше представлене ненульове число) 2 -1023 - 51 (≈ 4,94 * 10 -324 ) і d max (найбільше денормальне число, з якого мантіса складається повністю з 1
s) становить 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 можуть бути представлені точно в десяткових цифр .
Також зауважте, що ми можемо зменшити потужність в експоненті на 52 і зрушити точку в бінарному поданні праворуч на 52 місця (приблизно як 10 -3 * 1,23 == 10 -5 * 123). Потім це дозволяє представити двійкове уявлення як точне значення, яке воно представляє у вигляді a * 2 p . де 'a' - ціле число.
Перетворення експонентів у десяткові, видалення зміщення та повторне додавання мається на увазі 1
(у квадратних дужках) 0,1 та 0,2:
0.1 => 2^-4 * [1].1001100110011001100110011001100110011001100110011010
0.2 => 2^-3 * [1].1001100110011001100110011001100110011001100110011010
or
0.1 => 2^-56 * 7205759403792794 = 0.1000000000000000055511151231257827021181583404541015625
0.2 => 2^-55 * 7205759403792794 = 0.200000000000000011102230246251565404236316680908203125
Щоб додати два числа, показник повинен бути однаковим, тобто:
0.1 => 2^-3 * 0.1100110011001100110011001100110011001100110011001101(0)
0.2 => 2^-3 * 1.1001100110011001100110011001100110011001100110011010
sum = 2^-3 * 10.0110011001100110011001100110011001100110011001100111
or
0.1 => 2^-55 * 3602879701896397 = 0.1000000000000000055511151231257827021181583404541015625
0.2 => 2^-55 * 7205759403792794 = 0.200000000000000011102230246251565404236316680908203125
sum = 2^-55 * 10808639105689191 = 0.3000000000000000166533453693773481063544750213623046875
Оскільки сума не має виду 2 n * 1. {bbb}, збільшуємо показник на один і зміщуємо десяткову ( двійкову ) точку, щоб отримати:
sum = 2^-2 * 1.0011001100110011001100110011001100110011001100110011(1)
= 2^-54 * 5404319552844595.5 = 0.3000000000000000166533453693773481063544750213623046875
Зараз у мантісі 53 біти (53-й біт знаходиться у квадратних дужках у рядку вище). Режим округлення за замовчуванням для IEEE 754 - " Круглий до найближчого ", тобто якщо число x падає між двома значеннями a і b , вибирається значення, де найменший значущий біт дорівнює нулю.
a = 2^-54 * 5404319552844595 = 0.299999999999999988897769753748434595763683319091796875
= 2^-2 * 1.0011001100110011001100110011001100110011001100110011
x = 2^-2 * 1.0011001100110011001100110011001100110011001100110011(1)
b = 2^-2 * 1.0011001100110011001100110011001100110011001100110100
= 2^-54 * 5404319552844596 = 0.3000000000000000444089209850062616169452667236328125
Зауважте, що a і b відрізняються лише останнім бітом; ...0011
+ 1
= ...0100
. У цьому випадку значення з найменшим значущим бітом нуля дорівнює b , тому сума дорівнює:
sum = 2^-2 * 1.0011001100110011001100110011001100110011001100110100
= 2^-54 * 5404319552844596 = 0.3000000000000000444089209850062616169452667236328125
тоді як двійкове представлення 0,3 становить:
0.3 => 2^-2 * 1.0011001100110011001100110011001100110011001100110011
= 2^-54 * 5404319552844595 = 0.299999999999999988897769753748434595763683319091796875
що відрізняється лише від двійкового представлення суми 0,1 та 0,2 на 2 -54 .
Двійкове представлення 0,1 і 0,2 є найбільш точним поданням чисел, дозволених IEEE 754. Додавання цього подання, завдяки режиму округлення за замовчуванням, призводить до значення, яке відрізняється лише найменш-значущим бітом.
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
: останні кілька бітів округлюються.
Числа з плаваючою комою, що зберігаються в комп'ютері, складаються з двох частин, цілого числа та експонента, до якого береться основа і множиться на цілу частину.
Якби комп’ютер працював у базі 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. Ці числа потрібно округлити до їх найближчого еквівалента. Якщо припустити , що дуже загальний 64-бітний формат IEEE з плаваючою точкою, найближче число до 0.1
є 3602879701896397 x 2⁻⁵⁵
, і найближче число до 0.2
є 7205759403792794 x 2⁻⁵⁵
; додавання їх разом призводить до 10808639105689191 x 2⁻⁵⁵
або точне десяткове значення 0.3000000000000000444089209850062616169452667236328125
. Номери з плаваючою комою, як правило, округлені для відображення.
Помилка округлення з плаваючою комою. З того, що повинен знати кожен вчений-комп’ютер про арифметику з плаваючою комою :
Видавлення нескінченно багатьох реальних чисел на кінцеве число біт вимагає приблизного подання. Хоча існує нескінченна кількість цілих чисел, у більшості програм результат цілочисельних обчислень може зберігатися в 32 бітах. На відміну від будь-якої фіксованої кількості біт, більшість обчислень з реальними числами дають величини, які неможливо точно представити за допомогою такої кількості біт. Тому результат обчислення з плаваючою комою часто повинен бути округленим, щоб повернутися до його кінцевого зображення. Ця помилка округлення є характерною особливістю обчислення з плаваючою комою.
Було розміщено багато хороших відповідей, але я хотів би додати ще одну.
Не всі числа можуть бути представлені через поплавці / подвійні Наприклад, число "0,2" буде представлено як "0.200000003" в одній точності в стандарті IEEE754 з плаваючою точкою.
Моделі для зберігання реальних цифр під капотом представляють плаваючі числа як
Незважаючи на те, що ви можете 0.2
легко набрати , FLT_RADIX
і DBL_RADIX
це 2; не 10 для комп'ютера з FPU, який використовує "Стандарт IEEE для двійкової арифметики з плаваючою точкою (ISO / IEEE Std 754-1985)".
Тому трохи важко точно представити такі числа. Навіть якщо ви вказали цю змінну явно без проміжного обчислення.
Деякі статистичні дані, пов'язані з цим відомим питанням подвійної точності.
При додаванні всіх значень ( a + b ) за допомогою кроку 0,1 (від 0,1 до 100) ми маємо ~ 15% шансів помилки точності . Зауважте, що помилка може призвести до дещо більших або менших значень. Ось кілька прикладів:
0.1 + 0.2 = 0.30000000000000004 (BIGGER)
0.1 + 0.7 = 0.7999999999999999 (SMALLER)
...
1.7 + 1.9 = 3.5999999999999996 (SMALLER)
1.7 + 2.2 = 3.9000000000000004 (BIGGER)
...
3.2 + 3.6 = 6.800000000000001 (BIGGER)
3.2 + 4.4 = 7.6000000000000005 (BIGGER)
При відніманні всіх значень ( a - b, де a> b ) з використанням кроку 0,1 (від 100 до 0,1), у нас ~ 34% шансів помилки точності . Ось кілька прикладів:
0.6 - 0.2 = 0.39999999999999997 (SMALLER)
0.5 - 0.4 = 0.09999999999999998 (SMALLER)
...
2.1 - 0.2 = 1.9000000000000001 (BIGGER)
2.0 - 1.9 = 0.10000000000000009 (BIGGER)
...
100 - 99.9 = 0.09999999999999432 (SMALLER)
100 - 99.8 = 0.20000000000000284 (BIGGER)
* 15% і 34% справді величезні, тому завжди використовуйте BigDecimal, коли точність має велике значення. З двома десятковими цифрами (крок 0.01) ситуація погіршується дещо більше (18% та 36%).
Підсумок
Арифметика з плаваючою точкою є точною, на жаль, вона не добре відповідає нашому звичайному представленню числа 10-базового числа, тому, виявляється, ми часто даємо йому вхід, який трохи не відповідає тому, що ми написали.
Навіть прості числа, такі як 0,01, 0,02, 0,03, 0,04 ... 0,24, не представлені точно як двійкові дроби. Якщо ви порахуєте 0,01, .02, .03 ..., не досягнете рівня 0,25, ви отримаєте перший дріб, представлений у базі 2 . Якби ви спробували це, використовуючи FP, ваш 0,01 був би трохи відключений, тому єдиний спосіб додати 25 з них до приємного точного 0,25 зажадав би довгий ланцюжок причинності, що включає захисні біти та округлення. Важко передбачити, тому ми кидаємо руки і кажемо: "FP - це не точно", але це не зовсім так.
Ми постійно надаємо апаратному забезпеченню FP те, що здається простим у базі 10, але є повторюваним дробом у базі 2.
Як це сталося?
Коли ми пишемо у десятковій формі , кожен дріб (конкретно, кожен закінчуючий десятковий) є раціональним числом форми
a / (2 n x 5 м )
У двійковій формі ми отримуємо лише 2 n додаток, тобто:
а / 2 н
Таким чином , в десяткової системі , ми не можемо уявити 1 / 3 . Оскільки база 10 включає 2 в якості основного множника, кожне число, яке ми можемо записати як двійковий дріб, також може бути записане як дріб 10. Однак навряд чи що-небудь, що ми пишемо як базовий 10 дріб, є репрезентативним у двійковій формі . У діапазоні від 0,01, 0,02, 0,03 ... 0,99 у нашому форматі FP можуть бути представлені лише три числа: 0,25, 0,50 та 0,75, оскільки вони є 1/4, 1/2 та 3/4, усі числа з простим фактором, використовуючи лише 2 n доданок.
У базі 10 ми не можемо уявити 1 / 3 . Але в двійковому коді, ми не можемо зробити +1 / +10 або +1 / +3 .
Отже, хоча кожен двійковий дріб може бути записаний у десятковій частині, зворотне не відповідає дійсності. І насправді більшість десяткових дробів повторюються у двійкових.
Справа з цим
Зазвичай розробникам доручають робити <epsilon зіставлення, кращою порадою може бути округлення до цілісних значень (у бібліотеці C: round () та roundf (), тобто перебування у форматі FP), а потім порівняння. Округлення до певної десяткової довжини дробу вирішує більшість проблем з виходом.
Крім того, про реальні проблеми зі скороченням чисельності (проблеми, які FP був винайдений на ранніх, страшенно дорогих комп'ютерах) фізичні константи Всесвіту та всі інші вимірювання відомі лише відносно невеликій кількості значущих цифр, тому весь проблемний простір так чи інакше був "неточним". "Точність" FP не є проблемою в цьому застосуванні.
Вся проблема дійсно виникає, коли люди намагаються використовувати FP для підрахунку квасолі. Це спрацьовує для цього, але тільки якщо ви дотримуєтесь цілісних значень, який вражає сенс його використання. Ось чому ми маємо всі ці бібліотеки програмного забезпечення з десятковим дробом.
Мені подобається відповідь Піци від Кріса , тому що вона описує актуальну проблему, а не лише звичайне рукоділля про "неточність". Якби FP були просто "неточними", ми могли б це виправити і зробили б це десятиліття тому. Причиною у нас є те, що формат FP компактний і швидкий, і це найкращий спосіб розчавити велику кількість цифр. Крім того, це спадщина від космічної епохи та гонки озброєнь та ранніх спроб вирішити великі проблеми з дуже повільними комп'ютерами за допомогою невеликих систем пам'яті. (Інколи окремі магнітні ядра для 1-бітового зберігання, але це вже інша історія. )
Висновок
Якщо ви просто рахуєте квасоля в банку, програмні рішення, які в першу чергу використовують представлення десяткових рядків, працюють прекрасно. Але ви не можете робити квантову хромодинаміку чи аеродинаміку таким чином.
nextafter()
з цілим збільшенням або зменшенням бінарного представлення поплавця IEEE. Крім того, ви можете порівнювати поплавці як цілі числа і отримати правильну відповідь, за винятком випадків, коли вони обидва негативні (через величину знака та доповнення 2).
Ви пробували рішення з клейкої стрічки?
Спробуйте визначити, коли виникають помилки, і виправте їх за допомогою коротких, якщо тверджень, це не дуже, але для деяких проблем це єдине рішення, і це одна з них.
if( (n * 0.1) < 100.0 ) { return n * 0.1 - 0.000000000000001 ;}
else { return n * 0.1 + 0.000000000000001 ;}
У мене була така ж проблема в науковому симуляційному проекті в #
Щоб запропонувати найкраще рішення, я можу сказати, що я виявив наступний метод:
parseFloat((0.1 + 0.2).toFixed(10)) => Will return 0.3
Дозвольте пояснити, чому це найкраще рішення. Як відповіли інші, зазначені вище, корисно використовувати функцію Javascript toFixed () для вирішення проблеми. Але, швидше за все, ви зіткнетеся з деякими проблемами.
Уявіть , що ви збираєтеся скласти два числа з плаваючою точкою , як 0.2
і 0.7
тут: 0.2 + 0.7 = 0.8999999999999999
.
Очікуваний результат - 0.9
це означає, що вам потрібен результат з однозначною точністю в цьому випадку. Отже, ви повинні були використати(0.2 + 0.7).tofixed(1)
але ви не можете просто дати певний параметр toFixed (), оскільки це залежить від даного числа, наприклад
`0.22 + 0.7 = 0.9199999999999999`
У цьому прикладі вам потрібна двоцифрова точність, так і має бути toFixed(2)
, тож яким повинен бути параметр для розміщення кожного заданого числа з плавкою?
Ви можете сказати, нехай це буде 10 у будь-якій ситуації тоді:
(0.2 + 0.7).toFixed(10) => Result will be "0.9000000000"
Чорт! Що ти будеш робити з тими небажаними нулями після 9? Настав час перетворити його на плаваючий, щоб зробити це так, як вам захочеться:
parseFloat((0.2 + 0.7).toFixed(10)) => Result will be 0.9
Тепер, коли ви знайшли рішення, краще запропонувати його як таку функцію:
function floatify(number){
return parseFloat((number).toFixed(10));
}
Спробуємо самі:
function floatify(number){
return parseFloat((number).toFixed(10));
}
function addUp(){
var number1 = +$("#number1").val();
var number2 = +$("#number2").val();
var unexpectedResult = number1 + number2;
var expectedResult = floatify(number1 + number2);
$("#unexpectedResult").text(unexpectedResult);
$("#expectedResult").text(expectedResult);
}
addUp();
input{
width: 50px;
}
#expectedResult{
color: green;
}
#unexpectedResult{
color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="number1" value="0.2" onclick="addUp()" onkeyup="addUp()"/> +
<input id="number2" value="0.7" onclick="addUp()" onkeyup="addUp()"/> =
<p>Expected Result: <span id="expectedResult"></span></p>
<p>Unexpected Result: <span id="unexpectedResult"></span></p>
Ви можете використовувати його таким чином:
var x = 0.2 + 0.7;
floatify(x); => Result: 0.9
Як W3SCHOOLS передбачає, що є ще одне рішення, ви можете помножити та розділити, щоб вирішити проблему вище:
var x = (0.2 * 10 + 0.1 * 10) / 10; // x will be 0.3
Майте на увазі, що (0.2 + 0.1) * 10 / 10
це не буде працювати взагалі, хоча це здається таким же! Я вважаю за краще перше рішення, оскільки можу застосувати його як функцію, яка перетворює вхідний поплавок у точний вихідний поплавок.
Ці дивні числа з’являються тому, що комп’ютери використовують двійкову (базова 2) система числення для обчислення, тоді як ми використовуємо десяткову (базу 10).
Є більшість дробових чисел, які не можуть бути представлені точно ні в двійкових, ні в десяткових або в обох випадках. Результат - результати округлого (але точного) числа.
Багато численних дублікатів цього питання запитують про вплив округлення плаваючої точки на конкретні числа. На практиці легше зрозуміти, як це працює, дивлячись на точні результати обчислень, що цікавлять, а не просто читати про це. Деякі мови пропонують способи цього зробити, наприклад, перетворення на float
або double
вBigDecimal
в Java.
Оскільки це мовно-агностичне питання, йому потрібні мовно-агностичні інструменти, такі як перетворювач десяткових знаків у плаваючу крапку .
Застосовуючи його до цифр у питанні, трактуються як подвійні:
0,1 перетворює на 0,1000000000000000055511151231257827021181583404541015625,
0,2 перетворює на 0.200000000000000011102230246251565404236316680908203125,
0,3 перетворює на 0,299999999999999988897769753748434595763683319091796875 та
0,30000000000000004 перетворюється на 0,3000000000000000444089209850062616169452667236328125.
Додавання перших двох чисел вручну або в десятковий калькулятор, такий як калькулятор повної точності , показує точну суму фактичних входів 0,3000000000000000166533453693773481063544750213623046875.
Якби її округлили до еквівалента 0,3, помилка округлення склала б 0,0000000000000000277555756156289135105907917022705078125. Округлення до еквівалента 0,30000000000000004 також дає помилку округлення 0,0000000000000000277555756156289135105907917022705078125. Застосовується вимикач з круглим до рівним рівнем.
Повертаючись до перетворювача з плаваючою комою, необмеженою шістнадцяткою для 0,30000000000000004 є 3fd3333333333334, яка закінчується парною цифрою і тому є правильним результатом.
З огляду на те, що про це ніхто не згадував ...
Деякі мови високого рівня, такі як Python та Java, оснащені інструментами для подолання обмежень з бінарними плаваючими точками. Наприклад:
decimal
Модуль Python та BigDecimal
клас Java , які представляють числа внутрішньо з десятковою нотацією (на відміну від двійкової нотації). Обидва мають обмежену точність, тому вони все ще схильні до помилок, однак вони вирішують найпоширеніші проблеми з арифметикою з плаваючою комою з двійковим кодом.
Десяткові знаки дуже приємні при роботі з грошима: десять центів плюс двадцять копійок завжди рівно тридцять центів:
>>> 0.1 + 0.2 == 0.3
False
>>> Decimal('0.1') + Decimal('0.2') == Decimal('0.3')
True
decimal
Модуль Python базується на стандарті IEEE 854-1987 .
fractions
Модуль Python та BigFraction
клас Apache Common . Обидва представляють раціональні числа як (numerator, denominator)
пари, і вони можуть дати більш точні результати, ніж арифметична десяткова плаваюча точка.
Жодне з цих рішень не є ідеальним (особливо якщо ми дивимось на виступи або якщо нам потрібна дуже висока точність), але все ж вони вирішують велику кількість проблем з арифметикою з плаваючою крапкою.
Чи можу я просто додати; люди завжди вважають, що це проблема комп’ютера, але якщо ви порахуєте своїми руками (основа 10), ви не можете отримати, (1/3+1/3=2/3)=true
якщо не маєте нескінченності, щоб додати 0,333 ... до 0,333 ... так само, як і(1/10+2/10)!==3/10
проблеми в базі 2, ви усікаєте його до 0,333 + 0,333 = 0,666 і, ймовірно, округлите його до 0,667, що також буде технічно неточним.
Порахуйте на потрійні, а третини - це не проблема - можливо, якась гонка з 15 пальцями на кожній руці запитає, чому ваша десяткова математика була порушена ...
Вид математики з плаваючою комою, який можна реалізувати в цифровому комп’ютері, обов'язково використовує наближення реальних чисел та операцій над ними. (У стандартній версії розміщено понад півсотні сторінок документації і є комітет, який займається її помилкою та подальшим вдосконаленням.)
Це наближення - це суміш наближень різних видів, кожне з яких можна або ігнорувати, або ретельно враховувати через специфічний спосіб відхилення від точності. Він також включає ряд явних виняткових випадків як на апаратному, так і на програмному рівнях, які більшість людей проходять мимо, роблячи вигляд, що не помічають.
Якщо вам потрібна нескінченна точність (наприклад, використовуючи число π, замість однієї з багатьох його коротших опор), слід написати або використовувати символічну математичну програму.
Але якщо ви добре з думкою про те, що іноді математика з плаваючою комою нечітка у значенні, а логіка і помилки можуть накопичуватися швидко, і ви можете написати свої вимоги та тести, щоб це дозволити, то ваш код може часто обходитися тим, що знаходиться в ваш ФПУ.
Для задоволення я грав із поданням поплавків, дотримуючись визначень із стандарту C99 і написав код нижче.
Код друкує двійкове представлення поплавків у 3 відокремлених групах
SIGN EXPONENT FRACTION
і після цього він друкує суму, що при підсумовуванні з достатньою точністю покаже значення, яке дійсно існує в апаратному забезпеченні.
Отже, коли ви пишете float x = 999...
, компілятор перетворить це число у бітове представлення, надруковане функцією, xx
таким чином, що сума, надрукована функцією, yy
буде дорівнює заданому номеру.
Насправді ця сума є лише наближенням. Для числа 999,999,999 компілятор вставить у бітове представлення поплавця число 1 000 000 000
Після коду я додаю консольний сеанс, в якому я обчислюю суму термінів для обох констант (мінус PI та 999999999), які дійсно існують у апаратному забезпеченні, вставленому туди компілятором.
#include <stdio.h>
#include <limits.h>
void
xx(float *x)
{
unsigned char i = sizeof(*x)*CHAR_BIT-1;
do {
switch (i) {
case 31:
printf("sign:");
break;
case 30:
printf("exponent:");
break;
case 23:
printf("fraction:");
break;
}
char b=(*(unsigned long long*)x&((unsigned long long)1<<i))!=0;
printf("%d ", b);
} while (i--);
printf("\n");
}
void
yy(float a)
{
int sign=!(*(unsigned long long*)&a&((unsigned long long)1<<31));
int fraction = ((1<<23)-1)&(*(int*)&a);
int exponent = (255&((*(int*)&a)>>23))-127;
printf(sign?"positive" " ( 1+":"negative" " ( 1+");
unsigned int i = 1<<22;
unsigned int j = 1;
do {
char b=(fraction&i)!=0;
b&&(printf("1/(%d) %c", 1<<j, (fraction&(i-1))?'+':')' ), 0);
} while (j++, i>>=1);
printf("*2^%d", exponent);
printf("\n");
}
void
main()
{
float x=-3.14;
float y=999999999;
printf("%lu\n", sizeof(x));
xx(&x);
xx(&y);
yy(x);
yy(y);
}
Ось консольний сеанс, в якому я обчислюю реальне значення float, яке існує в апаратному забезпеченні. Я використовував bc
для друку суму термінів, виведених основною програмою. Можна також вставити цю суму в пітон repl
або щось подібне.
-- .../terra1/stub
@ qemacs f.c
-- .../terra1/stub
@ gcc f.c
-- .../terra1/stub
@ ./a.out
sign:1 exponent:1 0 0 0 0 0 0 fraction:0 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 1 0 0 0 0 1 1
sign:0 exponent:1 0 0 1 1 1 0 fraction:0 1 1 0 1 1 1 0 0 1 1 0 1 0 1 1 0 0 1 0 1 0 0 0
negative ( 1+1/(2) +1/(16) +1/(256) +1/(512) +1/(1024) +1/(2048) +1/(8192) +1/(32768) +1/(65536) +1/(131072) +1/(4194304) +1/(8388608) )*2^1
positive ( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29
-- .../terra1/stub
@ bc
scale=15
( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29
999999999.999999446351872
Це воно. Значення 999999999 насправді
999999999.999999446351872
Ви також можете перевірити, bc
що -3.14 також обурений. Не забудьте встановити scale
фактор bc
.
Відображена сума - це те, що знаходиться всередині обладнання. Значення, яке ви отримуєте, обчислюючи його, залежить від встановленої вами шкали. Я встановив scale
коефіцієнт на 15. Математично, з нескінченною точністю, здається, це 1 000 000 000.
Ще один спосіб переглянути це: використовуються 64 біти для представлення чисел. Як наслідок, не можна точно представити більше 2 ** 64 = 18,446,744,073,709,551,616 різних чисел.
Однак Math каже, що вже існує нескінченно багато десяткових знаків між 0 і 1. IEE 754 визначає кодування для ефективного використання цих 64 біт для набагато більшого простору чисел плюс NaN та +/- Нескінченність, тому між чітко представленими числами заповнюються прогалини числа лише приблизні.
На жаль, 0,3 сидить у розриві.
Уявіть, що ви працюєте в базовій частині десяти, скажімо, з 8 цифр точності. Ви перевіряєте, чи
1/3 + 2 / 3 == 1
і дізнайся, що це повертається false
. Чому? Ну, як справжні цифри у нас
1/3 = 0,333 .... і 2/3 = 0,666 ....
Обрізаючи вісім знаків після коми, ми отримуємо
0.33333333 + 0.66666666 = 0.99999999
що, звичайно, відрізняється від 1.00000000
точно 0.00000001
.
Ситуація для двійкових чисел із фіксованою кількістю біт точно аналогічна. Як справжні числа, ми маємо
1/10 = 0,0001100110011001100 ... (база 2)
і
1/5 = 0,0011001100110011001 ... (база 2)
Якби ми усіли їх до, скажімо, семи біт, тоді ми отримаємо
0.0001100 + 0.0011001 = 0.0100101
а з іншого боку,
3/10 = 0,01001100110011 ... (база 2)
який, усічений до семи біт, є 0.0100110
, і вони різняться точно 0.0000001
.
Точна ситуація дещо тонкіша, тому що ці цифри, як правило, зберігаються в наукових позначеннях. Так, наприклад, замість того, щоб зберігати 1/10, оскільки 0.0001100
ми можемо зберігати його як щось подібне1.10011 * 2^-4
, залежно від того, скільки бітів ми виділили для експонента та мантіси. Це впливає на те, скільки цифр точності ви отримаєте для своїх розрахунків.
Підсумок полягає в тому, що через ці помилки округлення ви по суті ніколи не хочете використовувати == на числах з плаваючою комою. Натомість ви можете перевірити, чи абсолютне значення їх різниці менше, ніж якесь фіксовано невелике число.
З Python 3.5 ви можете використовувати math.isclose()
функцію для тестування приблизної рівності:
>>> import math
>>> math.isclose(0.1 + 0.2, 0.3)
True
>>> 0.1 + 0.2 == 0.3
False
Оскільки цей потік трохи розгалужився на загальну дискусію щодо поточних реалізацій з плаваючою точкою, я додам, що є проекти з виправлення їхніх проблем.
Погляньте, наприклад, на https://posithub.org/ , де показано тип номера, який називається posit (та його попередник unum), який обіцяє забезпечити кращу точність з меншою кількістю біт. Якщо моє розуміння є правильним, воно також фіксує тип проблем у питанні. Досить цікавий проект, людина за ним - це математик, доктор Джон Густафсон . Вся справа з відкритим кодом, з багатьма актуальними реалізаціями в C / C ++, Python, Julia та C # ( https://hastlayer.com/arithmetics ).
Насправді це досить просто. Коли у вас є система 10 базової (як наша), вона може виражати лише дроби, які використовують простий коефіцієнт бази. Прості коефіцієнти 10 - це 2 та 5. Отже, 1/2, 1/4, 1/5, 1/8 та 1/10 можна виразити чисто, оскільки всі знаменники використовують прості множники 10. На відміну від 1 / 3, 1/6 та 1/7 - всі десяткові знаки, що повторюються, тому що їх знаменники використовують простий коефіцієнт 3 або 7. У двійкових (або базових 2) єдиний простий коефіцієнт - 2. Отже, ви можете чітко виражати дроби, які містять лише 2 як основний фактор. У двійковій частині 1/2, 1/4, 1/8 виражаються чисто як десяткові знаки. Тоді як 1/5 або 1/10 повторюються десятковими знаками. Тож 0,1 та 0,2 (1/10 та 1/5), очищаючи десятичні знаки в системі базової 10, повторюють десяткові числа в системі базової 2, в якій працює комп'ютер. Коли ви займаєтеся математикою цих повторюваних десяткових знаків,
Десяткові числа, такі як 0.1
, 0.2
і 0.3
не представлені точно у двокадрових кодованих типах з плаваючою комою. Сума наближень для 0.1
та на 0.2
відміну від апроксимації, що використовується для цього 0.3
, отже, хибність цього 0.1 + 0.2 == 0.3
можна зрозуміти тут:
#include <stdio.h>
int main() {
printf("0.1 + 0.2 == 0.3 is %s\n", 0.1 + 0.2 == 0.3 ? "true" : "false");
printf("0.1 is %.23f\n", 0.1);
printf("0.2 is %.23f\n", 0.2);
printf("0.1 + 0.2 is %.23f\n", 0.1 + 0.2);
printf("0.3 is %.23f\n", 0.3);
printf("0.3 - (0.1 + 0.2) is %g\n", 0.3 - (0.1 + 0.2));
return 0;
}
Вихід:
0.1 + 0.2 == 0.3 is false
0.1 is 0.10000000000000000555112
0.2 is 0.20000000000000001110223
0.1 + 0.2 is 0.30000000000000004440892
0.3 is 0.29999999999999998889777
0.3 - (0.1 + 0.2) is -5.55112e-17
Щоб ці обчислення були оцінені більш надійно, вам потрібно буде використовувати представлення на основі десяткових значень для значень з плаваючою комою. Стандарт C не визначає такі типи за замовчуванням, але як розширення, описане в технічному звіті .
В _Decimal32
, _Decimal64
і _Decimal128
типи можуть бути доступні у вашій системі (наприклад, GCC підтримує їх на обрані цілі , але Clang не підтримує їх на OS X ).
Math.sum (javascript) .... вид заміни оператора
.1 + .0001 + -.1 --> 0.00010000000000000286
Math.sum(.1 , .0001, -.1) --> 0.0001
Object.defineProperties(Math, {
sign: {
value: function (x) {
return x ? x < 0 ? -1 : 1 : 0;
}
},
precision: {
value: function (value, precision, type) {
var v = parseFloat(value),
p = Math.max(precision, 0) || 0,
t = type || 'round';
return (Math[t](v * Math.pow(10, p)) / Math.pow(10, p)).toFixed(p);
}
},
scientific_to_num: { // this is from https://gist.github.com/jiggzson
value: function (num) {
//if the number is in scientific notation remove it
if (/e/i.test(num)) {
var zero = '0',
parts = String(num).toLowerCase().split('e'), //split into coeff and exponent
e = parts.pop(), //store the exponential part
l = Math.abs(e), //get the number of zeros
sign = e / l,
coeff_array = parts[0].split('.');
if (sign === -1) {
num = zero + '.' + new Array(l).join(zero) + coeff_array.join('');
} else {
var dec = coeff_array[1];
if (dec)
l = l - dec.length;
num = coeff_array.join('') + new Array(l + 1).join(zero);
}
}
return num;
}
}
get_precision: {
value: function (number) {
var arr = Math.scientific_to_num((number + "")).split(".");
return arr[1] ? arr[1].length : 0;
}
},
sum: {
value: function () {
var prec = 0, sum = 0;
for (var i = 0; i < arguments.length; i++) {
prec = this.max(prec, this.get_precision(arguments[i]));
sum += +arguments[i]; // force float to convert strings to number
}
return Math.precision(sum, prec);
}
}
});
ідея полягає у використанні Math замість операторів, щоб уникнути плавних помилок
Math.sum автоматично визначає точність використання
Math.sum приймає будь-яку кількість аргументів
Розглянемо наступні результати:
error = (2**53+1) - int(float(2**53+1))
>>> (2**53+1) - int(float(2**53+1))
1
Ми можемо ясно бачити точку зупину , коли 2**53+1
- все працює відмінно до тих пір 2**53
.
>>> (2**53) - int(float(2**53))
0
Це відбувається через подвійну точність двійкового: IEEE 754 подвійної точності бінарного формату з плаваючою комою: binary64
На сторінці Вікіпедії для формату з плаваючою комою з двома точністю :
Двійкова точна двійкова плаваюча точка - це широко використовуваний формат на ПК, завдяки ширшому діапазону порівняно з одноточною плаваючою точкою, незважаючи на її продуктивність та вартість пропускної здатності. Як і у форматі з плаваючою комою з одноточною точністю, йому не вистачає точності на цілі числа, якщо порівнювати з цілим форматом одного розміру. Зазвичай він відомий просто як подвійний. Стандарт IEEE 754 визначає, що бінарний64 має такий:
- Біт знаку: 1 біт
- Експонент: 11 біт
- Значна точність: 53 біта (52 явно зберігаються)
Реальне значення, припущене заданою 64-бітовою датою подвійної точності з заданим зміщеним показником і 52-бітовою часткою, становить
або
Дякую @a_guest, що вказав на мене.
Інше питання було названо як дублікат цього:
Чому в C ++ чому результат cout << x
відрізняється від значення, яке показує налагоджувач x
?
У x
питанні є float
змінною.
Одним із прикладів може бути
float x = 9.9F;
Відладчик показує 9.89999962
, вихід cout
операції є 9.9
.
Відповідь виявляється, що cout
за замовчуванням точність float
дорівнює 6, тому вона округляє до 6 знаків після коми.
Дивіться тут для довідки