Модуль decimal

При роботі з числами з плаваючою точкою (тобто float) ми стикаємося з тим, що в результаті обчислень ми отримуємо не зовсім вірний результат:

number = 0.1 + 0.1 + 0.1
print(number) # 0.30000000000000004

Проблему може вирішити використання функції round(), яка округлить число. Однак є й інший спосіб, який полягає в використанні вбудованого модуля decimal.

Ключовим компонентом для роботи з числами в цьому модулі є клас Decimal. Для його застосування нам треба створити його об'єкт за допомогою конструктора. У конструктор передається строкове значення, яке представляє число:

from decimal import Decimal
 
number = Decimal("0.1")

Після цього об'єкт Decimal можна використовувати в арифметичних операціях:

from decimal import Decimal
 
number = Decimal("0.1")
number = number + number + number
print(number) # 0.3

В операціях з Decimal можна використовувати цілі числа:

number = Decimal("0.1")
number = number + 2

Однак не можна змішувати в операціях дробові числа float і Decimal:

number = Decimal("0.1")
number = number + 0.1 # тут виникне помилка

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

number = Decimal("0.10")
number = 3 * number
print(number) # 0.30

Рядок "0.10" визначає два знака дробової частини, навіть якщо останні символи будуть представляти нуль. Відповідно "0.100" представляє три знака дробової частини.

Округлення чисел

Об'єкти Decimal мають метод quantize(), який дозволяє округляти числа. У цей метод в якості першого аргументу передається також об'єкт Decimal, який вказує формат округлення числа:

from decimal import Decimal
 
number = Decimal("0.444")
number = number.quantize(Decimal( "1.00"))
print(number) # 0.44
 
number = Decimal( "0.555678")
print(number.quantize(Decimal("1.00"))) # 0.56
 
number = Decimal("0.999")
print(number.quantize(Decimal("1.00"))) # 1.00

Використовуваний рядок "1.00" вказує, що округлення буде йти до двох знаків у дробовій частині.

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

from decimal import Decimal, ROUND_HALF_EVEN 
 
number = Decimal("10.025")
print(number.quantize(Decimal("1.00"), ROUND_HALF_EVEN)) # 10.02
 
number = Decimal("10.035")
print(number.quantize(Decimal("1.00"), ROUND_HALF_EVEN)) # 10.04

Стратегія округлення передається в якості другого параметра в quantize.

Рядок "1.00" означає, що округлення буде йти до двох чисел дробової частини. Але в першому випадку "10.025" - другим знаком йде 2 - парне число, тому, незважаючи на те, що наступне число 5, двійка НЕ ​​округляється до трійки.

У другому випадку "10.035" - другим знаком йде 3 - непарне число, тому воно округляється до 4.

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

  • ROUND_HALF_UP: округлює число в бік підвищення, якщо після нього йде число 5 або вище
  • ROUND_HALF_DOWN: округлює число в бік підвищення, якщо після нього йде число більше 5
number = Decimal ( "10.026")
print(number.quantize(Decimal( "1.00"), ROUND_HALF_DOWN)) # 10.03
 
number = Decimal("10.025")
print (number.quantize (Decimal( "1.00"), ROUND_HALF_DOWN)) # 10.02
  • ROUND_05UP: округлює тільки 0 до одиниці, якщо після нього йде 5
number = Decimal("10.005")
print (number.quantize(Decimal("1.00"), ROUND_05UP)) # 10.01
 
number = Decimal("10.025")
print(number.quantize(Decimal("1.00"), ROUND_05UP)) # 10.02
  • ROUND_CEILING: округлює число в більшу сторону незалежно від того, яке число йде після нього
number = Decimal("10.021")
print(number.quantize(Decimal("1.00"), ROUND_CEILING)) # 10.03
 
number = Decimal("10.025")
print (number.quantize(Decimal("1.00"), ROUND_CEILING)) # 10.03
  • ROUND_FLOOR: не округлює число незалежно від того, яке число йде після нього
number = Decimal("10.021")
print (number.quantize (Decimal("1.00"), ROUND_FLOOR)) # 10.02
 
number = Decimal("10.025")
print(number.quantize(Decimal("1.00"), ROUND_FLOOR)) # 10.02