math - Funzioni Matematiche

Scopo: Fornisce funzioni per operazioni matematiche specializzate

Il modulo math implementa molte delle funzioni IEEE che si troverebbero normalmente nelle librerie della piattaforma nativa C per operazioni matematiche complesse usando valori a virgola mobile, compresi logaritmi e operazioni trigonometriche.

Costanti Speciali

Molte operazioni matematiche si basano su costanti speciali. math include valori per π (pi greco), e , nan (non un numero) e infinito.

# math_constants.py

import math

print('  π: {:.30f}'.format(math.pi))
print('  e: {:.30f}'.format(math.e))
print('nan: {:.30f}'.format(math.nan))
print('inf: {:.30f}'.format(math.inf))

Sia π che e sono limitati nella precisione solo dalla libreria C della virgola mobile della piattaforma.

$ python3 math_constants.py

  π: 3.141592653589793115997963468544
  e: 2.718281828459045090795598298428
nan: nan
inf: inf

Verificare Valori Eccezionali

I calcoli a virgola mobile possono risultare in due tipi di valori eccezionali. Il primo di questi, inf (infinito), compare quando il valore double usato per conservare un valore a virgola mobile va in overflow rispetto a un valore assoluto molto grande.

# math_isinf.py

import math

print('{:^3} {:6} {:6} {:6}'.format(
    'e', 'x', 'x**2', 'isinf'))
print('{:-^3} {:-^6} {:-^6} {:-^6}'.format(
    '', '', '', ''))

for e in range(0, 201, 20):
    x = 10.0 ** e
    y = x * x
    print('{:3d} {:<6g} {:<6g} {!s:6}'.format(
        e, x, y, math.isinf(y),
    ))

Quando l'esponente nell'esempio raggiunge una dimensione sufficiente, la radice quadrata di x non può più essere conservata in un double e il valore viene registrato come infinito.

$ python3 math_isinf.py

 e  x      x**2   isinf
--- ------ ------ ------
  0 1      1      False
 20 1e+20  1e+40  False
 40 1e+40  1e+80  False
 60 1e+60  1e+120 False
 80 1e+80  1e+160 False
100 1e+100 1e+200 False
120 1e+120 1e+240 False
140 1e+140 1e+280 False
160 1e+160 inf    True
180 1e+180 inf    True
200 1e+200 inf    True

Non tutti gli overflow di valori a virgola mobile risultano in valori inf. Calcolando un esponente con valori a virgola mobile, in particolare, viene sollevata una eccezione OverflowError invece di preservare il risultato inf.

# math_overflow.py

x = 10.0 ** 200

print('x    =', x)
print('x*x  =', x * x)
print('x**2 =', end=' ')
try:
    print(x ** 2)
except OverflowError as err:
    print(err)

La discrepanza viene causata da una differenza di implementazione nella libreria usata da C Python.

$ python3 math_overflow.py
x    = 1e+200
x*x  = inf
x**2 = (34, 'Numerical result out of range')

Le operazioni di divisione usando valori infiniti sono indefinite. Il risultato della divisione di un numero per infinito è nan (non un numero).

# math_isnan.py

import math

x = (10.0 ** 200) * (10.0 ** 200)
y = x / x

print('x =', x)
print('isnan(x) =', math.isnan(x))
print('y = x / x =', x / x)
print('y == nan =', y == float('nan'))
print('isnan(y) =', math.isnan(y))

nan non produce un valore di comparazione positivo contro nessun altro valore, anche se stesso, quindi per verificare se un valore è nan si usa isnan().

$ python3 math_isnan.py

x = inf
isnan(x) = False
y = x / x = nan
y == nan = False
isnan(y) = True

Si usa isfinite() per verificare numeri regolari (numeri divisibili senza resto da potenze di 60) o per i valori speciali inf o nan.

# math_isfinite.py

import math

for f in [0.0, 1.0, math.pi, math.e, math.inf, math.nan]:
    print('{:5.2f} {!s}'.format(f, math.isfinite(f)))

isfinite() ritorna False per ciascuno dei casi eccezionali, True altrimenti.

$ python3 math_isfinite.py

 0.00 True
 1.00 True
 3.14 True
 2.72 True
  inf False
  nan False

Confrontare

Confronti tra valori a virgola mobile possono essere inclini a errori, in quanto a causa della rappresentazione numerica ogni passo del calcolo potrebbe potenzialmente introdurre errori. La funzione isclose() usa un algoritmo stabile per minimizzare questi errori e fornire un modo per confronti relativi e assoluti. La formula usata è equivalente a.

    abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)

In modalità predefinita, isclose() usa un confronto relativo con una tolleranza impostata a 1e-09 vale a dire che la differenza tra i valori deve essere inferiore o uguale a 1e-09 volte il valore assoluto più grande tra a e b. La tolleranza si modifica passando l'argomento rel_tol ad isclose(). In questo esempio, i valori devono essere all'interno del 10% di ciascuno.

# shutil_disk_usage.py

import shutil

total_b, used_b, free_b = shutil.disk_usage('.')

gib = 2 ** 30  # GiB == gibibyte
gb = 10 ** 9   # GB == gigabyte

print('Totale: {:6.2f} GB  {:6.2f} GiB'.format(
    total_b / gb, total_b / gib))
print('Usato : {:6.2f} GB  {:6.2f} GiB'.format(
    used_b / gb, used_b / gib))
print('Libero: {:6.2f} GB  {:6.2f} GiB'.format(
    free_b / gb, free_b / gib))

Il confronto tra 0.1 e 0.9 fallisce in quanto l'errore vale 0.1.

$ python3 math_isclose.py

   a        b     rel_tol  abs(a-b) tolleranza  close
-------- -------- -------- -------- ---------- --------
 1000.00   900.00     0.10     100.00   100.00     True
  100.00    90.00     0.10      10.00    10.00     True
   10.00     9.00     0.10       1.00     1.00     True
    1.00     0.90     0.10       0.10     0.10     True
    0.10     0.09     0.10       0.01     0.01    False

Per utilizzare una tolleranza fissa o assoluta, si passa abs_tol in luogo di rel_tol.

# math_isclose_abs_tol.py

import math

INPUTS = [
    (1.0, 1.0 + 1e-07, 1e-08),
    (1.0, 1.0 + 1e-08, 1e-08),
    (1.0, 1.0 + 1e-09, 1e-08),
]

print('{:^8} {:^11} {:^8} {:^10} {:^8}'.format(
    'a', 'b', 'abs_tol', 'abs(a-b)', 'close')
)
print('{:-^8} {:-^11} {:-^8} {:-^10} {:-^8}'.format(
    '-', '-', '-', '-', '-'),
)

for a, b, abs_tol in INPUTS:
    close = math.isclose(a, b, abs_tol=abs_tol)
    abs_diff = abs(a - b)
    print('{:8.2f} {:11} {:8} {:0.9f} {!s:>8}'.format(
        a, b, abs_tol, abs_diff, close))

Per una tolleranza assoluta, la differenza tra i valori in input deve essere inferiore alla tolleranza data.

$ python3 math_isclose_abs_tol.py

   a          b      abs_tol   abs(a-b)   close
-------- ----------- -------- ---------- --------
    1.00   1.0000001    1e-08 0.000000100    False
    1.00  1.00000001    1e-08 0.000000010     True
    1.00 1.000000001    1e-08 0.000000001     True

nan e inf sono casi speciali.

# math_isclose_inf.py

import math

print('nan, nan:', math.isclose(math.nan, math.nan))
print('nan, 1.0:', math.isclose(math.nan, 1.0))
print('inf, inf:', math.isclose(math.inf, math.inf))
print('inf, 1.0:', math.isclose(math.inf, 1.0))

nan non è mai vicino a un altro valore, compreso se stesso. inf è vicino solo a se stesso.

$ python3 math_isclose_inf.py

nan, nan: False
nan, 1.0: False
inf, inf: True
inf, 1.0: False

Convertire Valori a Virgola Mobile in Interi

Il modulo math include tre funzioni per convertire valori a virgola mobile in interi. Ognuna prende un approccio diverso, e saranno utili in differenti circostanze.

La funzione più semplice è trunc(), che tronca le cifre decimali, lasciando solo la parte intera del valore. floor() converte il suo input nell'intero più grande precedente e ceil() fornisce l'intero più grande che segue il valore in input.

# math_integers.py

import math

HEADINGS = ('i', 'int', 'trunk', 'floor', 'ceil')
print('{:^5} {:^5} {:^5} {:^5} {:^5}'.format(*HEADINGS))
print('{:-^5} {:-^5} {:-^5} {:-^5} {:-^5}'.format(
    '', '', '', '', '',
))

fmt = '{:5.1f} {:5.1f} {:5.1f} {:5.1f} {:5.1f}'

TEST_VALUES = [
    -1.5,
    -0.8,
    -0.5,
    -0.2,
    0,
    0.2,
    0.5,
    0.8,
    1,
]

for i in TEST_VALUES:
    print(fmt.format(
        i,
        int(i),
        math.trunc(i),
        math.floor(i),
        math.ceil(i),
    ))

trunc() è equivalente alla conversione diretta in int .

$ python3 math_integers.py
  i    int  trunk floor ceil
----- ----- ----- ----- -----
 -1.5  -1.0  -1.0  -2.0  -1.0
 -0.8   0.0   0.0  -1.0   0.0
 -0.5   0.0   0.0  -1.0   0.0
 -0.2   0.0   0.0  -1.0   0.0
  0.0   0.0   0.0   0.0   0.0
  0.2   0.0   0.0   0.0   1.0
  0.5   0.0   0.0   0.0   1.0
  0.8   0.0   0.0   0.0   1.0
  1.0   1.0   1.0   1.0   1.0

Rappresentazioni Alternative per Valori a Virgola Mobile

modf() riceve un singolo numero a virgola mobile e ritorna una tuple contenente le parti intera e frazionata del valore in input.

# math_modf.py

import math

for i in range(6):
    print('{}/2 = {}'.format(i, math.modf(i / 2.0)))

Entrambi i numeri nel valore ritornato sono di tipo float.

$ python3 math_modf.py

0/2 = (0.0, 0.0)
1/2 = (0.5, 0.0)
2/2 = (0.0, 1.0)
3/2 = (0.5, 1.0)
4/2 = (0.0, 2.0)
5/2 = (0.5, 2.0)

frexp() ritorna la mantissa e l'esponente di un valore a virgola mobile, e può essere usata per creare una rappresentazione più portabile del valore.

# math_frexp.py

import math

print('{:^7} {:^7} {:^7}'.format('x', 'm', 'e'))
print('{:-^7} {:-^7} {:-^7}'.format('', '', ''))

for x in [0.1, 0.5, 4.0]:
    m, e = math.frexp(x)
    print('{:7.2f} {:7.2f} {:7d}'.format(x, m, e))

frexp() usa la formula x = m * 2**e e ritorna i valori di m ed e.

$ python3 math_frexp.py
   x       m       e

------- ------- -------
   0.10    0.80      -3
   0.50    0.50       0
   4.00    0.50       3

ldexp() è l'inverso di frexp().

# math_isclose.py

import math

INPUTS = [
    (1000, 900, 0.1),
    (100, 90, 0.1),
    (10, 9, 0.1),
    (1, 0.9, 0.1),
    (0.1, 0.09, 0.1),
]

print('{:^8} {:^8} {:^8} {:^8} {:^8} {:^8}'.format(
    'a', 'b', 'rel_tol', 'abs(a-b)', 'tolleranza', 'close')
)
print('{:-^8} {:-^8} {:-^8} {:-^8} {:-^10} {:-^8}'.format(
    '-', '-', '-', '-', '-', '-'),
)

fmt = '{:8.2f} {:8.2f} {:8.2f}   {:8.2f} {:8.2f} {!s:>8}'

for a, b, rel_tol in INPUTS:
    close = math.isclose(a, b, rel_tol=rel_tol)
    tolerance = rel_tol * max(abs(a), abs(b))
    abs_diff = abs(a - b)
    print(fmt.format(a, b, rel_tol, abs_diff, tolerance, close))

Usando la stessa formula di frexp(), ldexp() riceve i valori di mantissa ed esponente come argomento e ritorna un valore a virgola mobile.

$ python3 math_ldexp.py

   m       e       x
------- ------- -------
   0.80      -3    0.10
   0.50       0    0.50
   0.50       3    4.00

Segni Positivi e Negativi

Il valore assoluto di un numero è il suo valore senza il segno. Si usa fabs() per calcolare il valore assoluto per un numero a virgola mobile.

# math_fabs.py

import math

print(math.fabs(-1.1))
print(math.fabs(-0.0))
print(math.fabs(0.0))
print(math.fabs(1.1))

In termini pratici, il valore assoluto di un float è rappresentato come un valore positivo.

$ python3 math_fabs.py

1.1
0.0
0.0
1.1

Per determinare il segno di un valore, si dà a una serie di valori lo stesso segno oppure si confrontano due valori, si usi copysign() per impostare il segno di un valore valido conosciuto.

# math_copysign.py

import math

HEADINGS = ('f', 's', '< 0', '> 0', '= 0')
print('{:^5} {:^5} {:^5} {:^5} {:^5}'.format(*HEADINGS))
print('{:-^5} {:-^5} {:-^5} {:-^5} {:-^5}'.format(
    '', '', '', '', '',
))

VALUES = [
    -1.0,
    0.0,
    1.0,
    float('-inf'),
    float('inf'),
    float('-nan'),
    float('nan'),
]

for f in VALUES:
    s = int(math.copysign(1, f))
    print('{:5.1f} {:5d} {!s:5} {!s:5} {!s:5}'.format(
        f, s, f < 0, f > 0, f == 0,
    ))

Una funzione supplementare come copysign() è richiesta in quanto il confronto diretto di nan e -nan con altri valori non funziona.

$ python3 math_copysign.py

  f     s    < 0   > 0   = 0
----- ----- ----- ----- -----
 -1.0    -1 True  False False
  0.0     1 False False True
  1.0     1 False True  False
 -inf    -1 True  False False
  inf     1 False True  False
  nan    -1 False False False
  nan     1 False False False

Calcoli Comunemente Usati

Il rappresentare valori precisi in memoria binaria a virgola mobile è impegnativo. Alcuni valori non possono essere rappresentati esattamente, e più spesso un valore viene manipolato attraverso calcoli ripetuti, più facilmente potrebbe essere introdotto un errore di rappresentazione.math include una funzione per calcolare la somma di una serie di valori a virgola mobile usando un algoritmo efficiente che minimizza detti errori.

# math_fsum.py

import math

values = [0.1] * 10

print('Valori in input:', values)

print('sum()       : {:.20f}'.format(sum(values)))

s = 0.0
for i in values:
    s += i
print('ciclo-for   : {:.20f}'.format(s))

print('math.fsum() : {:.20f}'.format(math.fsum(values)))

Data una sequenza di 10 valori, ciascuno uguale a 0.1, il valore atteso per la somma della sequenza è 1.0. Visto che 0.1 non può essere rappresentato esattamente come valore a virgola mobile, errori vengono introdotti durante la somma a meno che essa venga calcolata con fsum().

$ python3 math_fsum.py

Valori in input: [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]
sum()       : 0.99999999999999988898
ciclo-for   : 0.99999999999999988898
math.fsum() : 1.00000000000000000000

factorial() viene comunemente usato per calcolare il numero di permutazioni e combinazioni di una serie di oggetti. Il fattoriale di un intero positivo n, espresso come n!, è definito ricorsivamente com (n-1)! * n e si interrompe quanto 0! == 1.

# math_factorial.py

import math

for i in [0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.1]:
    try:
        print('{:2.0f} {:6.0f}'.format(i, math.factorial(i)))
    except ValueError as err:
        print('Errore calcolando il fattoriale({}): {}'.format(i, err))

factorial() lavora solo con numeri interi, ma accetta come argomento valori float fintanto che essi possano essere convertiti in un intero senza perdere il valore.

$ python3 math_factorial.py
 0      1
 1      1
 2      2
 3      6
 4     24
 5    120
Errore calcolando il fattoriale(6.1): factorial() only accepts integral values

gamma() è come factorial() eccetto che lavora con numeri reali e il valore viene spostato di uno (gamma equivale a (n - 1)!).

# math_gamma.py
import math

for i in [0, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6]:
    try:
        print('{:2.1f} {:6.2f}'.format(i, math.gamma(i)))
    except ValueError as err:
        print('Errore calcolando gamma({}): {}'.format(i, err))

Visto che zero fa sì che il valore iniziale sia negativo, non è consentito.

$ python3 math_gamma.py

Errore calcolando gamma(0): math domain error
1.1   0.95
2.2   1.10
3.3   2.68
4.4  10.14
5.5  52.34
6.6 344.70

lgamma() ritorna il logaritmo naturale del valore assoluto di gamma per il valore in input.

# math_lgamma.py

import math

for i in [0, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6]:
    try:
        print('{:2.1f} {:.20f} {:.20f}'.format(
            i,
            math.lgamma(i),
            math.log(math.gamma(i)),
        ))
    except ValueError as err:
        print('Errore calcolando lgamma({}): {}'.format(i, err))

Usando lgamma() si conserva maggiore precisione rispetto al calcolo separato del logaritmo usando il risultato di gamma().

$ python3 math_lgamma.py

Errore calcolando lgamma(0): math domain error
1.1 -0.04987244125984036103 -0.04987244125983997245
2.2 0.09694746679063825923 0.09694746679063866168
3.3 0.98709857789473387513 0.98709857789473409717
4.4 2.31610349142485727469 2.31610349142485727469
5.5 3.95781396761871651080 3.95781396761871606671
6.6 5.84268005527463252236 5.84268005527463252236

L'operatore modulo (%) calcola il resto di una divisione (es. 5 % 2 = 1). L'operatore costruito nel linguaggio funziona bene con gli interi ma, così come molte altre operazioni con valori a virgola mobile, i calcoli intermedi causano problemi di rappresentazione, risultanti in perdite di dati. fmod() fornisce una implementazione più accurata per i valori a virgola mobile.

# math_fmod.py

import math

print('{:^4} {:^4} {:^5} {:^5}'.format(
    'x', 'y', '%', 'fmod'))
print('{:-^4} {:-^4} {:-^5} {:-^5}'.format(
    '-', '-', '-', '-'))

INPUTS = [
    (5, 2),
    (5, -2),
    (-5, 2),
]

for x, y in INPUTS:
    print('{:4.1f} {:4.1f} {:5.2f} {:5.2f}'.format(
        x,
        y,
        x % y,
        math.fmod(x, y),
    ))

Una fonte di confusione potenzialmente più frequente è il fatto che l'algoritmo usato da fmod() è anche diverso da quello usato da %, quindi anche il segno del risultato è diverso.

$ python3 math_fmod.py

 x    y     %   fmod
---- ---- ----- -----
 5.0  2.0  1.00  1.00
 5.0 -2.0 -1.00  1.00
-5.0  2.0  1.00 -1.00

Si usa gcd() per trovare l'intero più grande che possa dividere due interi: il massimo comun divisore.

# math_gcd.py

import math

print(math.gcd(10, 8))
print(math.gcd(10, 0))
print(math.gcd(50, 225))
print(math.gcd(11, 9))
print(math.gcd(0, 0))

Se entrambi i valori sono 0 il risultato è 0.

$ python3 math_gcd.py

2
10
25
1
0

Esponenti e Logaritmi

Le curve di crescita esponenziale compaiono nelle scienze economiche, fisiche e altre. Python ha un operatore esponenziale built-in ("**"), ma pow() può essere utile quando un callback è necessario come argomento di un'altra funzione.

# math_pow.py

import math

INPUTS = [
    # Typical uses
    (2, 3),
    (2.1, 3.2),

    # Always 1
    (1.0, 5),
    (2.0, 0),

    # Not-a-number
    (2, float('nan')),

    # Roots
    (9.0, 0.5),
    (27.0, 1.0 / 3),
]

for x, y in INPUTS:
    print('{:5.1f} ** {:5.3f} = {:6.3f}'.format(
        x, y, math.pow(x, y)))

Elevando 1 a qualsiasi potenza viene sempre ritornato 1.0, così come elevare qualsiasi valore a potenza di zero. La maggior parte delle operazioni su un valore nan ritorna nan, se l'esponente è inferiore ad 1, pow() calcola una radice.

$ python3 math_pow.py

 2.0 ** 3.000 =  8.000
  2.1 ** 3.200 = 10.742
  1.0 ** 5.000 =  1.000
  2.0 ** 0.000 =  1.000
  2.0 **   nan =    nan
  9.0 ** 0.500 =  3.000
 27.0 ** 0.333 =  3.000

Visto che le radici quadrate (esponente di 1/2) sono usate così frequentemente, esiste una funzione separata per calcolarle.

# math_sqrt.py
import math

print(math.sqrt(9.0))
print(math.sqrt(3))
try:
    print(math.sqrt(-1))
except ValueError as err:
    print('Impossibile calcolare sqrt(-1):', err)

Calcolare la radice quadrata di numeri negativi richiede numeri complessi, che non sono gestiti da math. Qualsiasi tentativo di calcolare la radice quadrata di un valore negativo risulta in un ValueError.

$ python3 ../dumpscripts/math_sqrt.py

3.0
1.7320508075688772
Impossibile calcolare sqrt(-1): math domain error

La funzione di logaritmo trova y dove x = b ** y. Nella modalità predefinita, log() calcola il logaritmo naturale (la base è e). Se viene passato un secondo argomento, quel valore viene usato come base.

# math_log.py

import math

print(math.log(8))
print(math.log(8, 2))
print(math.log(0.5, 2))

I logaritmi dove x è minore di uno restituiscono risultati negativi.

$ python3 math_log.py

2.0794415416798357
3.0
-1.0

Ci sono tre varianti di log(). Vista la rappresentazione in virgola mobile e gli errori di arrotondamento, i valori calcolati da log(x, b) hanno una accuratezza limitata, specialmente per alcune basi. log10() calcola log(x, 10), usando un algoritmo più accurato di log().

# math_log10.py

import math

print('{:2} {:^12} {:^10} {:^20} {:8}'.format(
    'i', 'x', 'accurato', 'inaccurato', 'discordanza',
))
print('{:-^2} {:-^12} {:-^10} {:-^20} {:-^8}'.format(
    '', '', '', '', '',
))

for i in range(0, 10):
    x = math.pow(10, i)
    accurate = math.log10(x)
    inaccurate = math.log(x, 10)
    match = '' if int(inaccurate) == i else '*'
    print('{:2d} {:12.1f} {:10.8f} {:20.18f} {:^5}'.format(
        i, x, accurate, inaccurate, match,
    ))

Le righe nell'output con in coda l'asterisco evidenziano i valori non accurati.

$ python3 math_log10.py

i       x        accurato       inaccurato      discordanza
-- ------------ ---------- -------------------- --------
 0          1.0 0.00000000 0.000000000000000000
 1         10.0 1.00000000 1.000000000000000000
 2        100.0 2.00000000 2.000000000000000000
 3       1000.0 3.00000000 2.999999999999999556   *
 4      10000.0 4.00000000 4.000000000000000000
 5     100000.0 5.00000000 5.000000000000000000
 6    1000000.0 6.00000000 5.999999999999999112   *
 7   10000000.0 7.00000000 7.000000000000000000
 8  100000000.0 8.00000000 8.000000000000000000
 9 1000000000.0 9.00000000 8.999999999999998224   *

Simile a log10(), log2() calcola l'equivalente di math.log(x, 2).

# math_log2.py

import math

print('{:>2} {:^5} {:^5}'.format(
    'i', 'x', 'log2',
))
print('{:-^2} {:-^5} {:-^5}'.format(
    '', '', '',
))

for i in range(0, 10):
    x = math.pow(2, i)
    result = math.log2(x)
    print('{:2d} {:5.1f} {:5.1f}'.format(
        i, x, result,
    ))

A seconda della piattaforma sottostante, l'uso della funzione built-in può offrire una migliore prestazione e accuratezza usando algoritmi dedicati per base 2, che non si trovano nella funzione a scopo più generale.

$ python3 math_log2.py

 i   x   log2
-- ----- -----
 0   1.0   0.0
 1   2.0   1.0
 2   4.0   2.0
 3   8.0   3.0
 4  16.0   4.0
 5  32.0   5.0
 6  64.0   6.0
 7 128.0   7.0
 8 256.0   8.0

log1p() calcola la serie Newton-Mercator (il logaritmo naturale di 1 + x ).

# math_log1p.py

import math

x = 0.0000000000000000000000001
print('x       :', x)
print('1 + x   :', 1 + x)
print('log(1+x):', math.log(1 + x))
print('log1p(x):', math.log1p(x))

log1p() è più accurato per valori di x vicino allo zero in quanto utilizza un algoritmo che compensa gli errori di arrotondamento dalla addizione iniziale.

$ python3 math_log1p.py

x       : 1e-25
1 + x   : 1.0
log(1+x): 0.0
log1p(x): 1e-25

exp() calcola la funzione esponenziale (e**x).

# math_exp.py

import math

x = 2

fmt = '{:.20f}'
print(fmt.format(math.e ** 2))
print(fmt.format(math.pow(math.e, 2)))

Così come altre funzioni per casi speciali, usa un algoritmo che produce risultati più accurati rispetto all'equivalente usato per scopi generici math.pow(math.e, x).

$ python3 math_exp.py

7.38905609893064951876
7.38905609893064951876

expm1() è l'inverso di log1p(), e calcola e**x - 1.

# math_expm1.py
import math

x = 0.0000000000000000000000001

print(x)
print(math.exp(x) - 1)
print(math.expm1(x))

Con piccoli valori di x si perde precisione quando la sottrazione viene eseguita separatamente, com con log1p().

$ python3 math_expm1.py

1e-25
0.0
1e-25

Angoli

Anche se i gradi sono più comunemente usati nelle discussioni sugli angoli di tutti i giorni, i radianti sono l'unità di misura standard per le misurazioni angolari in scienza e matematica. Un radiante è l'angolo creato da due linee che si intersecano al centro di un cerchio, con i loro estremi sulla circonferenza del cerchio, separate da un raggio.

La circonferenza viene calcolata come 2πr, in modo che ci sia relazione tra radiante e π, un valore che compare frequentemente nei calcoli trigonometrici. Questa relazione porta all'uso dei radianti in trigonometria e calcolo, visto che risultano in formule più compatte.

Per convertire da gradi a radianti, si usa radians().

# math_radians.py
import math

print('{:^7} {:^7} {:^7}'.format(
    'Gradi ', 'Radianti', 'Attesi'))
print('{:-^7} {:-^7} {:-^7}'.format(
    '', '', ''))

INPUTS = [
    (0, 0),
    (30, math.pi / 6),
    (45, math.pi / 4),
    (60, math.pi / 3),
    (90, math.pi / 2),
    (180, math.pi),
    (270, 3 / 2.0 * math.pi),
    (360, 2 * math.pi),
]

for deg, expected in INPUTS:
    print('{:7d} {:7.2f} {:7.2f}'.format(
        deg,
        math.radians(deg),
        expected,
    ))

La formula per la conversione è rad = deg * π / 180.

$ python3 math_radians.py

Gradi   Radianti Attesi
------- ------- -------
      0    0.00    0.00
     30    0.52    0.52
     45    0.79    0.79
     60    1.05    1.05
     90    1.57    1.57
    180    3.14    3.14
    270    4.71    4.71
    360    6.28    6.28

Per convertire da radianti a gradi, si usa degrees().

# math_degrees.py
import math

INPUTS = [
    (0, 0),
    (math.pi / 6, 30),
    (math.pi / 4, 45),
    (math.pi / 3, 60),
    (math.pi / 2, 90),
    (math.pi, 180),
    (3 * math.pi / 2, 270),
    (2 * math.pi, 360),
]

print('{:^8} {:^8} {:^8}'.format(
    'Radianti', 'Gradi  ', 'Attesi'))
print('{:-^8} {:-^8} {:-^8}'.format('', '', ''))
for rad, expected in INPUTS:
    print('{:8.2f} {:8.2f} {:8.2f}'.format(
        rad,
        math.degrees(rad),
        expected,
    ))

La formula è deg = rad * 180 / π.

$ python3 math_degrees.py

Radianti Gradi     Attesi
-------- -------- --------
    0.00     0.00     0.00
    0.52    30.00    30.00
    0.79    45.00    45.00
    1.05    60.00    60.00
    1.57    90.00    90.00
    3.14   180.00   180.00
    4.71   270.00   270.00
    6.28   360.00   360.00

Trigonometria

Le funzioni trigonometriche correlano gli angoli in un triangolo alla lunghezza dei suoi lati. Compaiono in formule con proprietà periodiche come armonici, moto circolare, o quando si ha a che fare con angoli. Tutte le funzioni trigonometriche nella libreria standard ricevono angoli espressi in radianti.

Dato un angolo in un triangolo rettangolo, il seno è il rapporto della lunghezza del lato opposto all'angolo dell'ipotenusa (seno A = opposto/ipotenusa). Il coseno è il rapporto della lunghezza del lato adiacente e l'ipotenusa coseno A = adiacente/ipotenusa. La tangente è il rapporto tra la parte opposta e quella adiacente (tangente A = opposto/adiacente).

# math_trig.py

import math

print('{:^7} {:^7} {:^7} {:^7} {:^7}'.format(
    'Gradi  ', 'Radianti', 'Seno', 'Coseno', 'Tangente'))
print('{:-^7} {:-^7} {:-^7} {:-^7} {:-^7}'.format(
    '-', '-', '-', '-', '-'))

fmt = '{:7.2f} {:7.2f} {:7.2f} {:7.2f} {:7.2f}'

for deg in range(0, 361, 30):
    rad = math.radians(deg)
    if deg in (90, 270):
        t = float('inf')
    else:
        t = math.tan(rad)
    print(fmt.format(deg, rad, math.sin(rad), math.cos(rad), t))

La tangente si può anche definire come il rapporto tra il seno dell'angolo al suo coseno, visto il coseno è 0 per radianti π/2 e 3π/2, la tangente è infinita.

$ python3 math_trig.py
Gradi   Radianti  Seno   Coseno  Tangente
------- ------- ------- ------- -------
   0.00    0.00    0.00    1.00    0.00
  30.00    0.52    0.50    0.87    0.58
  60.00    1.05    0.87    0.50    1.73
  90.00    1.57    1.00    0.00     inf
 120.00    2.09    0.87   -0.50   -1.73
 150.00    2.62    0.50   -0.87   -0.58
 180.00    3.14    0.00   -1.00   -0.00
 210.00    3.67   -0.50   -0.87    0.58
 240.00    4.19   -0.87   -0.50    1.73
 270.00    4.71   -1.00   -0.00     inf
 300.00    5.24   -0.87    0.50   -1.73
 330.00    5.76   -0.50    0.87   -0.58
 360.00    6.28   -0.00    1.00   -0.00

Dato un punto (t, y), la lunghezza dell'ipotenusa per il triangolo tra i punti [(0, 0), (x, 0), (x, y)] è (x**2 + y**2) ** 1/2e può essere calcolata con hypot().

# math_hypot.py

import math

print('{:^7} {:^7} {:^10}'.format('X', 'Y', 'Ipotenusa'))
print('{:-^7} {:-^7} {:-^10}'.format('', '', ''))

POINTS = [
    # semplici punti
    (1, 1),
    (-1, -1),
    (math.sqrt(2), math.sqrt(2)),
    (3, 4),  # triangolo 3-4-5
    # sul cerchio
    (math.sqrt(2) / 2, math.sqrt(2) / 2),  # radianti pi/4
    (0.5, math.sqrt(3) / 2),  # radianti pi/3
]

for x, y in POINTS:
    h = math.hypot(x, y)
    print('{:7.2f} {:7.2f} {:7.2f}'.format(x, y, h))

I punti nel cerchio hanno sempre ipotenusa uguale ad 1.

$ python3 math_hypot.py

   X       Y    Ipotenusa
------- ------- ----------
   1.00    1.00    1.41
  -1.00   -1.00    1.41
   1.41    1.41    2.00
   3.00    4.00    5.00
   0.71    0.71    1.00
   0.50    0.87    1.00

La stessa funzione può essere usata per trovare la distanza tra due punti.

# math_distance_2_points.py

import math

print('{:^8} {:^8} {:^8} {:^8} {:^8}'.format(
    'X1', 'Y1', 'X2', 'Y2', 'Distanza',
))
print('{:-^8} {:-^8} {:-^8} {:-^8} {:-^8}'.format(
    '', '', '', '', '',
))

POINTS = [
    ((5, 5), (6, 6)),
    ((-6, -6), (-5, -5)),
    ((0, 0), (3, 4)),  # triangolo 3-4-5
    ((-1, -1), (2, 3)),  # triangolo 3-4-5
]

for (x1, y1), (x2, y2) in POINTS:
    x = x1 - x2
    y = y1 - y2
    h = math.hypot(x, y)
    print('{:8.2f} {:8.2f} {:8.2f} {:8.2f} {:8.2f}'.format(
        x1, y1, x2, y2, h,
    ))

Si usa la differenza tra i valori di x ed y per spostarsi di un punto di arrivo dall'origine, poi si passano i risultati ad hypot().

$ python3 math_distance_2_points.py

   X1       Y1       X2       Y2    Distanza
-------- -------- -------- -------- --------
    5.00     5.00     6.00     6.00     1.41
   -6.00    -6.00    -5.00    -5.00     1.41
    0.00     0.00     3.00     4.00     5.00
   -1.00    -1.00     2.00     3.00     5.00

math definisce anche funzioni trigonometriche inverse.

# math_inverse_trig.py

import math

for r in [0, 0.5, 1]:
    print('arcocoseno({:.1f})    = {:5.2f}'.format(r, math.asin(r)))
    print('arccoseno({:.1f})  = {:5.2f}'.format(r, math.acos(r)))
    print('arcotangente({:.1f}) = {:5.2f}'.format(r, math.atan(r)))
    print()

1.57 corrisponde pressappoco a π/2, o 90 gradi, l'angolo al quale il seno è 1 e il coseno è 0.

$ python3 math_inverse_trig.py

arcocoseno(0.0)    =  0.00
arccoseno(0.0)  =  1.57
arcotangente(0.0) =  0.00

arcocoseno(0.5)    =  0.52
arccoseno(0.5)  =  1.05
arcotangente(0.5) =  0.46

arcocoseno(1.0)    =  1.57
arccoseno(1.0)  =  0.00
arcotangente(1.0) =  0.79

Funzioni Iperboliche

Le funzioni iperboliche appaiono in diverse equazioni lineari e sono usate lavorando con campi elettromagnetici, dinamica dei fluid, relatività speciale e altre forme avanzate di matematica e fisica.

# math_hyperbolic.py
import math

print('{:^6} {:^6} {:^6} {:^6}'.format(
    'X', 'senh', 'cosh', 'tanh',
))
print('{:-^6} {:-^6} {:-^6} {:-^6}'.format('', '', '', ''))

fmt = '{:6.4f} {:6.4f} {:6.4f} {:6.4f}'

for i in range(0, 11, 2):
    x = i / 10.0
    print(fmt.format(
        x,
        math.sinh(x),
        math.cosh(x),
        math.tanh(x),
    ))

Laddove le funzioni di seno e coseno tracciano un cerchio, il coseno iperbolico e il seno iperbolico formano una iperbole a metà.

$ python3 math_hyperbolic.py

  X     senh   cosh   tanh
------ ------ ------ ------
0.0000 0.0000 1.0000 0.0000
0.2000 0.2013 1.0201 0.1974
0.4000 0.4108 1.0811 0.3799
0.6000 0.6367 1.1855 0.5370
0.8000 0.8881 1.3374 0.6640
1.0000 1.1752 1.5431 0.7616

Sono anche disponibili le funzioni inverse: acosh(), asinh() ed atanh().

Funzioni Speciali

La funzione Errore Gaussiano viene usata in statistica.

# math_erf.py

import math

print('{:^5} {:7}'.format('x', 'erf(x)'))
print('{:-^5} {:-^7}'.format('', ''))

for x in [-3, -2, -1, -0.5, -0.25, 0, 0.25, 0.5, 1, 2, 3]:
    print('{:5.2f} {:7.4f}'.format(x, math.erf(x)))

Per la funzione di errore, erf(-x) == -erf(x).

$ python3 math_erf.py

  x   erf(x)
----- -------
-3.00 -1.0000
-2.00 -0.9953
-1.00 -0.8427
-0.50 -0.5205
-0.25 -0.2763
 0.00  0.0000
 0.25  0.2763
 0.50  0.5205
 1.00  0.8427
 2.00  0.9953
 3.00  1.0000

La funzione di errore complementare è 1 - erf(x).

# math_erfc.py

import math

print('{:^5} {:7}'.format('x', 'erfc(x)'))
print('{:-^5} {:-^7}'.format('', ''))

for x in [-3, -2, -1, -0.5, -0.25, 0, 0.25, 0.5, 1, 2, 3]:
    print('{:5.2f} {:7.4f}'.format(x, math.erfc(x)))

L'implementazione di erfc() evita errori di precisione per valori piccoli di x quando si sottrae da 1.

$ python3 math_erfc.py

  x   erfc(x)
----- -------
-3.00  2.0000
-2.00  1.9953
-1.00  1.8427
-0.50  1.5205
-0.25  1.2763
 0.00  1.0000
 0.25  0.7237
 0.50  0.4795
 1.00  0.1573
 2.00  0.0047
 3.00  0.0000

Vedere anche:

math
La documentazione della libreria standard per questo modulo
IEEE floating point arithmetic in Python
Articolo del blog di John Cook sul come compaiano valori speciali e come gestirli quando si eseguono operazioni matematiche in Python.
Scipy
Libreria open source per calcoli scientifici e matematici in Python
PEP 485
Una funzione per verificare l'uguaglianza approssimativa.