Scopo | Artimetica decimale usando numeri a virgola fissa e mobile |
Versione Python | 2.4 e superiore |
A partire dal 1 gennaio 2021 le versioni 2.x di Python non sono piu' supportate. Ti invito a consultare la corrispondente versione 3.x dell'articolo per il modulo decimal
Il modulo decimal implementa l'aritmetica a virgola fissa e mobile usando il modello familiare alla maggior parte delle persone, piuttosto che la versione a virgola mobile IEEE implementata dalla maggior parte dell'hardware dei computer. Una istanza di Decimal può rappresentare esattamente un qualsiasi numero, arrotondare per eccesso o difetto, ed applicare un limite al numero delle cifre significative.
I valori decimali sono rappresentati come istanze della classe Decimal . Il costruttore chiede un intero come parametro, oppure una stringa. I numeri a virgola mobile devono essere convertiti a stringa prima di essere usati per creare un Decimal , lasciando che sia il chiamante a gestire esplicitamente il numero di cifre per i valori che non possono essere espressi esattamente usando rappresentazioni hardware di virgola mobile.
import decimal
fmt = '{0:<20} {1:<20}'
print fmt.format('Input', 'Output')
print fmt.format('-' * 20, '-' * 20)
# Intero
print fmt.format(5, decimal.Decimal(5))
# Stringa
print fmt.format('3.14', decimal.Decimal('3.14'))
# Float
print fmt.format(repr(0.1), decimal.Decimal(str(0.1)))
Si nota che il valore a virgola mobile di
0.1
non è rappresentato come un valore esatto, quindi la rappresentazione come float è diversa dal valore Decimal.
$ python decimal_create.py Input Output -------------------- -------------------- 5 5 3.14 3.14 0.10000000000000001 0.1
Meno convenientemente, i decimali possono anche essere creati da tuple che contengono un flag di segno (
0
per positivo,
1
per negativo), una tupla di cifre, ed un esponente intero.
import decimal
# Tuple
t = (1, (1, 1), -2)
print 'Input :', t
print 'Decimale:', decimal.Decimal(t)
$ python decimal_tuple.py Input : (1, (1, 1), -2) Decimale: -0.11
Decimal sovrascrive gli operatori aritmetici semplici, così che una volta che si ha un valore lo si può manipolare pressochè allo stesso modo dei tipi numerici built-in.
import decimal
a = decimal.Decimal('5.1')
b = decimal.Decimal('3.14')
c = 4
d = 3.14
print 'a =', a
print 'b =', b
print 'c =', c
print 'd =', d
print
print 'a + b =', a + b
print 'a - b =', a - b
print 'a * b =', a * b
print 'a / b =', a / b
print
print 'a + c =', a + c
print 'a - c =', a - c
print 'a * c =', a * c
print 'a / c =', a / c
print
print 'a + d =',
try:
print a + d
except TypeError, e:
print e
Gli operatori decimali accettano anche parametri interi, ma i valori a virgola mobile devono essere convertiti in istanze di Decimal.
$ python decimal_operators.py a = 5.1 b = 3.14 c = 4 d = 3.14 a + b = 8.24 a - b = 1.96 a * b = 16.014 a / b = 1.624203821656050955414012739 a + c = 9.1 a - c = 1.1 a * c = 20.4 a / c = 1.275 a + d = unsupported operand type(s) for +: 'Decimal' and 'float'
Oltre l'aritmetica base, Decimal include dei metodi per trovare i logaritmi di base 10 e naturali.
import decimal
d = decimal.Decimal(100)
print 'd :', d
print 'log10 :', d.log10()
print 'ln :', d.ln()
$ python decimal_log.py d : 100 log10 : 2 ln : 4.605170185988091368035982909
Oltre ai valori numerici che ci si aspetta, Decimal può rappresentare parecchi valori speciali, inclusi valori positivi e negativi per l'infinito, "non un numero" (NaN), e zero.
import decimal
for value in [ 'Infinity', 'NaN', '0' ]:
print decimal.Decimal(value), decimal.Decimal('-' + value)
print
# Matematica con infinity
print 'Infinito + 1:', (decimal.Decimal('Infinity') + 1)
print '-Infinito + 1:', (decimal.Decimal('-Infinity') + 1)
# Stampa le comparazioni di NaN
print decimal.Decimal('NaN') == decimal.Decimal('Infinity')
print decimal.Decimal('NaN') != decimal.Decimal(1)
L'aggiungere valori ad infinito restituisce un altro valore infinito. Il confronto per eguaglianza con NaN (non numeri - n.d.t.) restituisce sempre False ed il confronto per diseguaglianza restituisce sempre True. Il confronto per ordinamento contro NaN è indefinito e restituisce sempre un errore.
$ python decimal_special.py Infinity -Infinity NaN -NaN 0 -0 Infinito + 1: Infinity -Infinito + 1: -Infinity False True
Fino ad qui tutti gli esempi hanno usato i comportamenti predefiniti del modulo decimal. E' possibile sovrascrivere le impostazioni tipo il mantenimento della precisione, come viene eseguito l'arrotondamento, la gestione di errori, ecc. Tutte queste impostazioni sono gestite tramite un context . Context può essere applicato a tutte le istanze di Decimal in un thread o localmente all'interno di una piccola regione di codice.
Contesto corrente
Per ottenere il contesto globale corrente, si usa
getcontext()
.
import decimal
print decimal.getcontext()
$ python decimal_getcontext.py Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=999999999, capitals=1, flags=[], traps=[Overflow, InvalidOperation, DivisionByZero])
Precisione
L'attributo prec di context controlla la precisione mantenuta per i nuovi valori creati come risultato aritmetico. I valori letterali sono mantenuti come descritto.
import decimal
d = decimal.Decimal('0.123456')
for i in range(4):
decimal.getcontext().prec = i
print i, ':', d, d * 1
$ python decimal_precision.py 0 : 0.123456 0 1 : 0.123456 0.1 2 : 0.123456 0.12 3 : 0.123456 0.123
Arrotondamento
Ci sono parecchie opzioni di arrotondamento per manterere i valori con la precisione desiderata.
import decimal
context = decimal.getcontext()
ROUNDING_MODES = [
'ROUND_CEILING',
'ROUND_DOWN',
'ROUND_FLOOR',
'ROUND_HALF_DOWN',
'ROUND_HALF_EVEN',
'ROUND_HALF_UP',
'ROUND_UP',
'ROUND_05UP',
]
header_fmt = '{0:20} {1:^10} {2:^10} {3:^10}'
print 'POSITIVI:'
print
print header_fmt.format(' ', '1/8 (1)', '1/8 (2)', '1/8 (3)')
print header_fmt.format(' ', '-' * 10, '-' * 10, '-' * 10)
for rounding_mode in ROUNDING_MODES:
print '{0:20}'.format(rounding_mode),
for precision in [ 1, 2, 3 ]:
context.prec = precision
context.rounding = getattr(decimal, rounding_mode)
value = decimal.Decimal(1) / decimal.Decimal(8)
print '{0:<10}'.format(value),
print
print
print 'NEGATIVI:'
print header_fmt.format(' ', '-1/8 (1)', '-1/8 (2)', '-1/8 (3)')
print header_fmt.format(' ', '-' * 10, '-' * 10, '-' * 10)
for rounding_mode in ROUNDING_MODES:
print '{0:20}'.format(rounding_mode),
for precision in [ 1, 2, 3 ]:
context.prec = precision
context.rounding = getattr(decimal, rounding_mode)
value = decimal.Decimal(-1) / decimal.Decimal(8)
print '{0:<10}'.format(value),
print
$ python decimal_rounding.py POSITIVI: 1/8 (1) 1/8 (2) 1/8 (3) ---------- ---------- ---------- ROUND_CEILING 0.2 0.13 0.125 ROUND_DOWN 0.1 0.12 0.125 ROUND_FLOOR 0.1 0.12 0.125 ROUND_HALF_DOWN 0.1 0.12 0.125 ROUND_HALF_EVEN 0.1 0.12 0.125 ROUND_HALF_UP 0.1 0.13 0.125 ROUND_UP 0.2 0.13 0.125 ROUND_05UP 0.1 0.12 0.125 NEGATIVI: -1/8 (1) -1/8 (2) -1/8 (3) ---------- ---------- ---------- ROUND_CEILING -0.1 -0.12 -0.125 ROUND_DOWN -0.1 -0.12 -0.125 ROUND_FLOOR -0.2 -0.13 -0.125 ROUND_HALF_DOWN -0.1 -0.12 -0.125 ROUND_HALF_EVEN -0.1 -0.12 -0.125 ROUND_HALF_UP -0.1 -0.13 -0.125 ROUND_UP -0.2 -0.13 -0.125 ROUND_05UP -0.1 -0.12 -0.125
Contesto locale
Con Python 2.5 o superiore si può anche applicare context ad un sottoinsieme del proprio codice con una istruzione
with
ed un gestore di context.
import decimal with decimal.localcontext() as c: c.prec = 2 print 'Precisione locale:', c.prec print '3.14 / 3 =', (decimal.Decimal('3.14') / 3) print print 'Precisione predefinita:', decimal.getcontext().prec print '3.14 / 3 =', (decimal.Decimal('3.14') / 3) $ python decimal_context_manager.py Precisione locale: 2 3.14 / 3 = 1.0 Precisione predefinita: 28 3.14 / 3 = 1.046666666666666666666666667
Context per istanza
Context può essere usato per costruire istanze di Decimal, applicando i parametri di precisione ed arrotondamento alla conversione dal tipo in input. Questo consente alla propria applicazione di selezionare la precisione dei valori costanti separatamente dalla precisione per i dati dell'utente.
import decimal
# Imposta un contesto con precisione limitata
c = decimal.getcontext().copy()
c.prec = 3
# Crea la costante
pi = c.create_decimal('3.1415')
# Il valore costante viene arrotondato
print 'PI:', pi
print 'RESULT:', decimal.Decimal('2.01') * pi
$ python decimal_instance_context.py PI: 3.14 RESULT: 6.3114
Thread
Il context "globale" è in realtà locale al thread, quindi ogni thread può essere potenzialmente configurato usando valori diversi.
import decimal
import threading
from Queue import Queue
class Multiplier(threading.Thread):
def __init__(self, a, b, prec, q):
self.a = a
self.b = b
self.prec = prec
self.q = q
threading.Thread.__init__(self)
def run(self):
c = decimal.getcontext().copy()
c.prec = self.prec
decimal.setcontext(c)
self.q.put( (self.prec, a * b) )
return
a = decimal.Decimal('3.14')
b = decimal.Decimal('1.234')
q = Queue()
threads = [ Multiplier(a, b, i, q) for i in range(1, 6) ]
for t in threads:
t.start()
for t in threads:
t.join()
for i in range(5):
prec, value = q.get()
print prec, '\t', value
$ python decimal_thread_context.py 1 4 2 3.9 3 3.87 4 3.875 5 3.8748
Vedere anche: