Чому 1 // 0,01 == 99 у Python?


31

Я думаю, це класичне питання точності з плаваючою точкою, але я намагаюся обернути голову навколо цього результату, працюючи 1//0.01в Python 3.7.5 99.

Я думаю, це очікуваний результат, але чи є спосіб вирішити, коли безпечніше використовувати, int(1/f)а не 1//f?


4
Так, це завжди безпечніше int (1 / f). Просто тому, що // це підлога, і ви неправильно вважаєте це КРУГОМ.
Перді Естакель

4
Можливий дублікат Чи порушена математика
pppery

2
Не дублікат. Це може працювати, як очікувалося, 99,99%, завжди використовуючи round()та ніколи //або int(). Пов'язане питання про порівняння поплавця не має нічого спільного з усіченням і не має такого простого виправлення.
макси

Відповіді:


23

Якби це поділ на реальні числа, 1//0.01було б рівно 100. Оскільки вони наближені з плаваючою комою, хоча 0.01трохи більше 1/100, тобто коефіцієнт трохи менше 100. Це 99. деяке значення, яке потім закреслюється до 99.


3
Це не стосується частини "чи є спосіб вирішити, коли це безпечніше".
Скотт Хантер

10
"Безпечніший" недостатньо визначений.
чепнер

1
Досить повністю ігнорувати це, особливо. коли ОП знає про проблеми з плаваючою точкою?
Скотт Хантер

3
@chepner Якщо "безпечніший" не визначено правильно, можливо, краще попросити роз'яснення: /

2
мені цілком зрозуміло, що "безпечніше" означає "помилка не гірша за дешевий кишеньковий калькулятор"
макси

9

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

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

Тож у вашому випадку 1//0.01спочатку слід перетворити на 1*100//(0.01*100)100.

У більш крайніх випадках ви все одно можете отримати "несподівані" результати. Можливо, буде потрібно додати roundвиклик до чисельника та знаменника, перш ніж виконувати ціле ділення:

1 * 100000000000 // round(0.00000000001 * 100000000000)

Але якщо мова йде про роботу з фіксованими десятковою комою (гроші, центи), то розгляньте роботу з копійками як одиницю , так що вся арифметика може бути виконана як ціла арифметика, а при перетворенні лише в основну грошову одиницю (долар) I / O.

Або ж використайте бібліотеку для десяткових знаків, як десятковий , який:

... надає підтримку швидкої арифметики з десятковою плаваючою колом.

from decimal import Decimal
cent = Decimal(1) / Decimal(100) # Contrary to floating point, this is exactly 0.01
print (Decimal(1) // cent) # 100

3
"що, очевидно, становить 100". Не обов’язково: якщо .01 не точний, то .01 * 100 також не є. Його потрібно «налаштувати» вручну.
glglgl

8

Те, що ви повинні врахувати, це те, що //це floorоператор, і, як такий, вам слід спочатку подумати, ніби ви маєте рівну ймовірність впасти в 100, як у 99 (*) (тому що операція буде мати 100 ± epsilonза epsilon>0умови, що шанси отримати рівно 100,00 ..0 надзвичайно низькі.)

Ви насправді можете бачити те саме зі знаком мінус,

>>> 1//.01
99.0
>>> -1//.01
-100.0

і ви повинні бути настільки ж (не) здивовані.

З іншого боку, int(-1/.01)виконує спочатку ділення, а потім застосовує число int()в номері, яке не є підлогою, а відсіканням до 0 ! що означає, що в такому випадку,

>>> 1/.01
100.0
>>> -1/.01
-100.0

отже,

>>> int(1/.01)
100
>>> int(-1/.01)
-100

Округлення, однак, дасть вам ВАШ очікуваний результат для цього оператора, оскільки, знову ж таки, помилка мала для цих цифр.

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


7

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

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

>>> from decimal import Decimal
>>> Decimal(0.01)
Decimal('0.01000000000000000020816681711721685132943093776702880859375')
>>> from fractions import Fractio
>>> Fraction(0.01)
Fraction(5764607523034235, 576460752303423488) 

Ми можемо використовувати тип Дроби, щоб знайти помилку, спричинену нашим неточним буквалом.

>>> float((Fraction(1)/Fraction(0.01)) - 100)
-2.0816681711721685e-15

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

>>> from numpy import nextafter
>>> nextafter(100,0)-100
-1.4210854715202004e-14

З цього можна припустити, що найближче число з плаваючою точкою 1/0.01000000000000000020816681711721685132943093776702880859375насправді становить саме 100.

Різниця між 1//0.01і int(1/0.01)полягає в округленні. 1 // 0,01 округляє точний результат до наступного цілого числа за один крок. Таким чином ми отримуємо результат 99.

int (1 / 0,01), з іншого боку, обводиться у два етапи, спочатку він округляє результат до найближчого числа з плаваючою точкою подвійної точності (що рівно 100), потім округляє число з плаваючою точкою до наступного цілого числа (яке є знову рівно 100).


Назвати це просто округленням вводить в оману. Це слід назвати або усіканням, або округленням до нуля : int(0.9) == 0іint(-0.9) == 0
максимум

Тут ви говорите про двійкові типи з плаваючою комою. (Є також десятичні типи з плаваючою комою.)
Stephen C

3

Якщо ви виконаєте наступне

from decimal import *

num = Decimal(1) / Decimal(0.01)
print(num)

Вихід буде:

99.99999999999999791833182883

Ось як це представлено внутрішньо, тому округлення його вниз //дасть99


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

Якщо Decimal(0.01)ви запізнилися, помилка вже виникла перед тим, як дзвонити Decimal. Я не впевнений, як це відповідь на запитання ... Спочатку потрібно обчислити точну 0,01 Decimal(1) / Decimal(100), як я показав у своїй відповіді.
трінкот

@trincot Моя відповідь на запитання в заголовку "Чому 1 // 0,01 == 99" Я намагався показати ОП, як плаваючі числа обробляються всередині.
Дощ
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.