Защо умножението и деленето на N коригира представянето с плаваща запетая?

Работя в JavaScript, но проблемът е общ. Вземете тази грешка при закръгляване:

>> 0.1 * 0.2
0.020000000000000004

Този отговор на StackOverflow предоставя хубаво обяснение. По същество някои десетични числа не могат да бъдат представени толкова точно в двоична система. Това е интуитивно, тъй като 1/3 има подобен проблем в база 10. Сега заобикалянето е следното:

>> (0.1 * (1000*0.2)) / 1000
0.02

Въпросът ми е как става това?


person gwg    schedule 11.06.2014    source източник
comment
Интересен въпрос, предполагам, че отговорът трябва да се сведе до това къде се губи точността   -  person Ian    schedule 12.06.2014
comment
Последният може просто да бъде написан като 0.1 * 200 / 1000 или 20 / 1000, които разбира се нямат проблем с плаваща запетая   -  person adeneo    schedule 12.06.2014
comment
@adeneo разбира се? Обяснете.   -  person Niet the Dark Absol    schedule 12.06.2014
comment
@adeneo: Може би трябва да прочетете Какво трябва да знае всеки компютърен специалист Аритметика с плаваща запетая   -  person tmyklebu    schedule 12.06.2014
comment
@tmyklebu - Публикувах това преди 3 часа и го прочетох.   -  person adeneo    schedule 12.06.2014
comment
@adeneo: Това 0.1 * 200 == 20 е нетривиално и зависи по фундаментален начин от двоичното разширение на 0.1. Сравнете с 0.145 * 200 < 29, например. Това не заслужава да бъде заметено под килима само с думата „или“.   -  person tmyklebu    schedule 12.06.2014
comment
@tmyklebu - всъщност зависи от маскирането (гардове) и как се прилагат, когато едната страна на израза е цяло число.   -  person adeneo    schedule 12.06.2014
comment
@adeneo: Не вярвам, че е така. Първо, числата в Javascript винаги са 64-битови с плаваща запетая. Второ, не виждам как да съгласувам резултата от 0.145 * 200 със схемата, която изглежда предлагате. Може би за вас е подходящ още един поглед към WECSSKAFPA .   -  person tmyklebu    schedule 12.06.2014
comment
@tmyklebu - това, което вярвам, няма особено значение, аз не отговорих на въпроса, ти го направи, с два реда, които всъщност не казват нищо. Опитайте се да ми го обясните, като публикувате подходящ отговор, който обяснява това поведение, така че всеки да може да го разбере, и съм сигурен, че ще получите още няколко гласа за. Това би било много по-добре прекарано време, отколкото да спорите с мен за това как работят плаващите точки.   -  person adeneo    schedule 12.06.2014
comment
И това, което е много по-интересно от факта, че числата са 64-битови, е, че те се съхраняват в 53-битова мантиса, след като разберете как работи това, е очевидно защо побитовото преобразуване не съответства на определени плаващи числа.   -  person adeneo    schedule 12.06.2014
comment
@adeneo: Не ме интересуват гласовете за. Объркани сте относно аритметиката с плаваща запетая и сте написали объркан коментар. Прочети документа, който си линкнал, и си изчисти нещата в главата.   -  person tmyklebu    schedule 12.06.2014
comment
@tmyklebu - Отново, ако мислите, че имате добра представа защо това се случва, публикувайте отговор, който обяснява това поведение, а не само един ред, в който се казва, че ако опитате други числа става по-лошо, публикувайте нещо по-съществено, което е по-разбираемо .   -  person adeneo    schedule 12.06.2014


Отговори (3)


Не става. Това, което виждате там, не е точно 0.02, а число, което е достатъчно близко (до 15 значими десетични цифри), за да изглежда като него.

Просто се случва умножаването на операнд по 1000, след което разделянето на резултата на 1000, води до грешки при закръгляване, които дават привидно „правилен“ резултат.

Можете сами да видите ефекта в конзолата на вашия браузър. Преобразувайте числата в двоични с помощта на Number.toString(2) и ще видите разликата:

Конзолата показва ‹code›0.1‹/code›, ‹code›0.2‹/code›, ‹code›0.1*0.2‹/  код› и ‹код›((0.1*(0.2*1000))/1000‹/код› всеки със своите двоични представяния

Корелацията не предполага причинно-следствена връзка.

person Niet the Dark Absol    schedule 11.06.2014

Не става. Опитайте 0.684 и 0.03 вместо това и този трик всъщност ще влоши ситуацията. Или 0.22 и 0.99. Или огромен брой други неща.

person tmyklebu    schedule 11.06.2014
comment
Не ме учудва, че не винаги работи. Това, което питам, е защо изобщо работи. Например (0.22 * (10 * 0.99)) / 10 е добре, но (0.22 * (100 * 0.99)) / 100 не е. - person gwg; 12.06.2014
comment
@ggundersen: Всеки от тези хакове дава приблизително еднаква вероятност за желания отговор. Както и наивната формула. Това е чиста случайност. По-конкретно, този хак не е по-добър от простото умножаване на двете. - person tmyklebu; 12.06.2014
comment
@ggundersen Почти всичко, което променя точната последователност от операнди и операции, може да промени резултата. Понякога новият резултат ще бъде този, който ви харесва повече, понякога такъв, който не ви харесва толкова добре, колкото оригинала. - person Patricia Shanahan; 12.06.2014
comment
@ggundersen Имахте късмет и грешките ви при закръгляване се анулираха, вместо да се сумират. - person Kendall Frey; 12.06.2014

Тъй като числата са константи, изразът може да бъде оценен, преди да присвоите числата на променливи, така че не е необходимо да се използват променливи с плаваща единица, освен тази, която съхранява резултата от израза.

person Aefix    schedule 11.06.2014
comment
Присвояването на стойностите на променливите и след това работата върху променливите също дава точен резултат. - person Niet the Dark Absol; 12.06.2014
comment
В този случай е възможно числата, използвани за представяне на частта в числото с плаваща запетая, всъщност да са успели да го направят точно, така че няма загуба на точност. - person Aefix; 12.06.2014