>>> 0.1
0.1
>>> 0.1 + 0.1
0.2
>>> 0.1 + 0.1 + 0.1
0.30000000000000004
>>> '%.53f' % 0.1
'0.10000000000000000555111512312578270211815834045410156'
>>> 1.1 + 2.2 - 3.3
4.440892098500626e-16
0.1 * 10 == sum(0.1 for i in range(10))
False
Ok, ale co z tego? Błąd jest rzędu pięcu tryliardowych. Błąd może i być mały ale głównym problemem jest maszyna licząca w naszym mózgu. Widząc takie równanie człowiek nie zastanawia się jak te liczby wyglądają w zapisie binarnym. Na język w tym przypadku ciśnie się zero.
W drugim przypadku aż ciśnie się na język odpowiedź true. Przecież od podstawówki nam powtarzają że mnożenie to tylko skrócenie dodawania.
Ograniczona pamięć nie tyczy się tylko ułamków nie wymiernych ale większości ułamków dziesiętnych. Im większa precyzja która powiązana jest z liczbą pamięci tym mniejszy błąd. Jak wspomniałem wcześniej precyzja floata w pythonie to 53 czyli jest to podwójna precyzja.
Ciekawostką jest, że pierwszy standard floating points IEEE 754 napisany w 1985r. zakładał możliwość używania artmetyki liczb 2 i 10 ale dopiero w 2005 IBM skonstruował procesor z900 który rzeczywiście działał na liczbach dziesiętnych.
>>> from decimal import getcontext
>>> getcontext()
Context(
prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999,
Emax=999999, capitals=1, clamp=0, flags=[],
traps=[InvalidOperation, DivisionByZero, Overflow])
>>> Decimal('1.5').quantize(Decimal('0'))
Decimal('2')
>>> Decimal('2.5').quantize(Decimal('0'))
Decimal('2')
>>> Decimal('1.5').quantize(Decimal('0'), ROUND_HALF_UP)
Decimal('2')
>>> Decimal('2.5').quantize(Decimal('0'), ROUND_HALF_UP)
Decimal('3')
$$W = m \times p^c$$
c = -1
m = -2
$$W = -2 \times 10^{-1}$$
$$W = -0.2$$
W = (znak, mantysa, cecha)
W = (1, 2, -1)
$$x = 0.1 + 0.01$$
x = (0, 1, -1) + (0, 1, -2)
x = (0, 10, -2) + (0, 1, -2)
x = (0, 11, -2)
$$x = 0.11$$
def __add__(self, other, context=None):
"""Returns self + other.
-INF + INF (or the reverse) cause InvalidOperation errors.
"""
other = _convert_other(other)
if other is NotImplemented:
return other
if context is None:
context = getcontext()
if self._is_special or other._is_special:
ans = self._check_nans(other, context)
if ans:
return ans
if self._isinfinity():
# If both INF, same sign => same as both, opposite => error.
if self._sign != other._sign and other._isinfinity():
return context._raise_error(InvalidOperation, '-INF + INF')
return Decimal(self)
if other._isinfinity():
return Decimal(other) # Can't both be infinity here
exp = min(self._exp, other._exp)
negativezero = 0
if context.rounding == ROUND_FLOOR and self._sign != other._sign:
# If the answer is 0, the sign should be negative, in this case.
negativezero = 1
if not self and not other:
sign = min(self._sign, other._sign)
if negativezero:
sign = 1
ans = _dec_from_triple(sign, '0', exp)
ans = ans._fix(context)
return ans
if not self:
exp = max(exp, other._exp - context.prec-1)
ans = other._rescale(exp, context.rounding)
ans = ans._fix(context)
return ans
if not other:
exp = max(exp, self._exp - context.prec-1)
ans = self._rescale(exp, context.rounding)
ans = ans._fix(context)
return ans
op1 = _WorkRep(self)
op2 = _WorkRep(other)
op1, op2 = _normalize(op1, op2, context.prec)
result = _WorkRep()
if op1.sign != op2.sign:
# Equal and opposite
if op1.int == op2.int:
ans = _dec_from_triple(negativezero, '0', exp)
ans = ans._fix(context)
return ans
if op1.int < op2.int:
op1, op2 = op2, op1
# OK, now abs(op1) > abs(op2)
if op1.sign == 1:
result.sign = 1
op1.sign, op2.sign = op2.sign, op1.sign
else:
result.sign = 0
# So we know the sign, and op1 > 0.
elif op1.sign == 1:
result.sign = 1
op1.sign, op2.sign = (0, 0)
else:
result.sign = 0
# Now, op1 > abs(op2) > 0
if op2.sign == 0:
result.int = op1.int + op2.int
else:
result.int = op1.int - op2.int
result.exp = op1.exp
ans = Decimal(result)
ans = ans._fix(context)
return ans
https://github.com/mirumee/prices
>>> from prices import Price
>>> Price('1.99') + Price(50)
Price('51.99', currency=None)
>>> from prices import Price
>>> price = Price(net='1', gross='1.2') + Price(net=2, gross=4)
>>> price
Price(net='3', gross='5.2', currency=None)
>>> price.net
Decimal('3')
>>> price.gross
Decimal('5.2')
>>> price.tax
Decimal('2.2')
>>> from prices import Price, FixedDiscount
>>> price = Price('1.99')
>>> discount = FixedDiscount(Price(1))
>>> discount.apply(price)
Price('0.99', currency=None)
>>> from prices import Price, LinearTax
>>> price = Price('1.99')
>>> tax = LinearTax('0.23', '23% VAT')
>>> tax.apply(price)
Price(net='1.99', gross='2.4477', currency=None)
>>> from prices import Price
>>> Price('1.99', currency='PLN')
Price('1.99', currency='PLN')
>>> from prices import Price, LinearTax
>>> price = Price('1.99') + Price(50) | LinearTax('0.23', '23% VAT')
>>> inspect_price(price)
((Price('1.99', currency=None) + Price('50', currency=None)) | LinearTax('0.23', name='23% VAT'))
>>> from prices import Price, PriceRange
>>> price = Price(70)
>>> price_range = PriceRange(Price(50), Price(100))
>>> price in price_range
True
>>> Price(1.2)
__main__:1: RuntimeWarning: You should never use floats when dealing with prices!
Price('1.1999999999999999555910790149937383830547332763671875', currency=None)
>>> Decimal('2.5').quantize(Decimal('0'))
Decimal('2')
>>> Price('2.5').quantize(Decimal('0'))
Price('3', currency=None)
>>> Price(10, currency='USD') + Price(10, currency='PLN')
ValueError: Cannot add price in 'USD' to 'PLN'
>>> Price(10, currency='USD') < Price(10, currency='PLN')
ValueError: Cannot compare prices in 'USD' and 'PLN'
https://github.com/mirumee/django-prices
from django.db import models
from django_prices.models import PriceField
class Product(models.Model):
name = models.CharField('Name')
price = PriceField('Price', currency='BTC')
from django import forms
from django_prices.forms import PriceField
class ProductForm(forms.Form):
name = forms.CharField(label='Name')
price = PriceField(label='Price', currency='PLN')
{% load prices %}
Price: {% gross foo.price %}
({% net foo.price %} {% tax foo.price %} tax)
{% load prices_i18n %}
Price: {% gross foo.price %}
({% net foo.price %} {% tax foo.price %} tax)
https://github.com/mirumee/django-prices-openexchangerates
>>> from prices import Price, inspect_price
>>> from django_prices_openexchangerates import exchange_currency
>>> converted_price = exchange_currency(Price(10, currency='USD'), 'EUR')
>>> converted_price
Price('8.84040', currency='EUR')
>>> inspect_price(converted_price)
(Price('10', currency='USD') | CurrencyConversion('USD', 'EUR', rate=Decimal('0.88404')))
{% load prices_multicurrency %}
Price: {% gross_in_currency foo.price 'USD' %}
({% net_in_currency foo.price 'USD' %}
{% tax_in_currency foo.price 'USD' %} tax)