Nu

Numeri in Julia

7 esercizi

Informazioni su Numeri

Julia è un linguaggio di uso generale, che può essere utilizzato per la maggior parte dei compiti di programmazione. Nella pratica, però, i casi d'uso principali tendono a essere nell'ingegneria e nella scienza. Calcoli numerici veloci, versatili e sofisticati sono al centro del suo design.

Numeri interi

Un numero intero è un numero «tondo», senza punto decimale.

Nel concetto Basics abbiamo visto che un valore intero può essere assegnato a una variabile senza specificarne il tipo.

Per migliorare la leggibilità, si possono usare i trattini bassi come separatore di cifre. Il compilatore li ignora.

julia> x = 3
3

julia> typeof(x)
Int64

julia> large_number = 1_234_567_890
1234567890

Internamente, il compilatore userà il tipo intero con segno più appropriato per la tua CPU. Sui PC moderni di solito sarà Int64, che è perfettamente adeguato per la maggior parte dei compiti.

I tipi saranno discussi più approfonditamente in un concetto futuro. Per ora, i tipi numerici dovrebbero essere abbastanza intuitivi da far sì che questo documento abbia senso.

Come vedremo in un concetto successivo, Julia supporta nativamente array multidimensionali molto grandi. Questi possono mettere alla prova sia la quantità di memoria disponibile sia la larghezza di banda per il trasferimento dei dati.

Per dare al programmatore un controllo più fine, è possibile e anche piuttosto comune specificare il tipo intero con o senza segno desiderato. Molte delle persone che imparano Julia e hanno già usato Python potrebbero voler confrontarlo con NumPy, anziché con Python di base.

julia> y = Int8(42)
42

julia> typeof(y)
Int8

julia> z = UInt32(1024)
0x00000400

Nota che Julia, per impostazione predefinita, mostra i numeri interi senza segno in formato esadecimale, perché i tipi senza segno sono spesso usati per operazioni sui bit di basso livello.

I numeri interi si possono anche scrivere in binario, ottale o esadecimale, con i prefissi 0b, 0o e 0x rispettivamente.

julia> a = 0x10
0x10

julia> Int(a)
16

julia> b = 0b1010 # displays as hexadecimal
0x0a

Overflow degli interi e BigInt

Ogni tipo intero ha un valore massimo e un valore minimo che può memorizzare:

julia> typemax(Int8)
127

julia> typemin(Int8)
-128

Uscire da questo intervallo valido causa un overflow degli interi, con risultati che appaiono molto strani.

julia> c = Int8(126)
126

julia> c * c
4

Probabilmente possiamo concordare che 126 * 126 non dovrebbe essere uguale a 4!

Una spiegazione dettagliata richiede di sapere come i registri della CPU memorizzano i numeri interi, cosa che esula dall'ambito di questo concetto.

Il problema si può ridurre usando un tipo «più ampio», fino a Int128 o UInt128 (il significato di «più ampio» è discusso più avanti). Tuttavia, tutti hanno limiti rigidi prima di incorrere in problemi di overflow.

Una soluzione è usare il tipo BigInt, che è limitato solo dalla memoria del tuo computer. È l'implementazione di Julia dell'aritmetica a precisione arbitraria.

julia> 2 ^ 70
0

julia> big = BigInt(2) ^ 70
1180591620717411303424

julia> UInt128(big)
0x00000000000000400000000000000000

Poiché 2 elevato alla 70 non entra in un Int64, il primo calcolo dell'esempio qui sopra fallisce: nota però che non viene mostrato alcun messaggio di errore. Programmatore, attenzione!

La conversione di tipo di 2 a BigInt dà la risposta corretta, e mostrarla in esadecimale chiarisce meglio che si tratta di una potenza di 2.

Numeri in virgola mobile

Non sarà una sorpresa che i numeri in virgola mobile abbiano facoltativamente un punto decimale e una parte frazionaria dopo il punto.

julia> f = 3.45
3.45

julia> typeof(f)
Float64

Naturalmente, la notazione scientifica è supportata.

julia> avogadro = 6.02e23
6.02e23

Come per i numeri interi, il tipo predefinito va bene per la maggior parte degli usi, ma sono disponibili altri tipi con segno. Non esistono tipi in virgola mobile senza segno.

Come scorciatoia, si possono creare valori Float32 usando il suffisso f0.

julia> f32 = 4.56f0
4.56f0

julia> typeof(f32)
Float32

Altre dimensioni di float richiedono una conversione di tipo esplicita, come per i numeri interi.

I valori massimo e minimo potrebbero sorprenderti:

julia> typemax(Float64)
Inf

julia> typemin(Float64)
-Inf

L'infinito è un valore valido!

Tuttavia, l'intervallo utile dei numeri in virgola mobile è limitato: qualsiasi valore molto grande viene semplicemente assegnato a Inf, e qualsiasi valore molto piccolo viene arrotondato a 0.0.

Possiamo usare un'altra coppia di funzioni per vedere questi limiti: all'incirca ± 10 ^ 308.

julia> floatmax(Float64)
1.7976931348623157e308

julia> floatmin(Float64)
2.2250738585072014e-308

Inoltre, la precisione è limitata: viene rappresentato un numero grande ma finito di cifre significative (circa 15 per Float64).

Operatori aritmetici

Come discusso nel concetto Basics, gli operatori aritmetici funzionano per lo più come l'aritmetica standard, quella insegnata ai bambini. Nota che l'elevamento a potenza usa ^, non ** (entrambi sono comuni in altri linguaggi).

2 + 3  # 5 (addition)
2 - 3  # -1 (subtraction)
2 * 3  # 6 (multiplication)
8 / 2  # 4.0 (division)
8 % 3  # 2 (remainder)
2 ^ 3  # 8 (exponentiation)

Tuttavia, vale la pena discutere qualche dettaglio specifico di Julia.

Moltiplicazione

julia> x = 4.2
4.2

julia> 2 * x
8.4

julia> 2x
8.4

julia> 2.4x
10.08

Questo potrebbe sorprendere.

È sempre possibile usare * come operatore infisso, come nella maggior parte degli altri linguaggi di programmazione.

Tuttavia, Julia è stata progettata da persone convinte che il codice debba assomigliare il più possibile a equazioni matematiche.

Poiché i nomi delle variabili devono iniziare con una lettera, anteporre un numero (intero o in virgola mobile) al nome viene interpretato come moltiplicazione implicita.

Per esempio, se vogliamo l'area della superficie di una sfera, invece di 4 * pi * r * r potremmo fare così:

julia> surface(r) = 4π * r^2
surface (generic function with 1 method)

julia> surface(3)
113.09733552923255

Sebbene π sia una costante predefinita, è anche una lettera (greca). Il parser ha quindi comunque bisogno di un * esplicito per separare π da r.

Divisione

Usare / come operatore infisso dà sempre un risultato in virgola mobile, anche con input interi.

Per la divisione intera ci sono più opzioni:

julia> 10 / 3
3.3333333333333335

julia> div(10, 3)
3

julia> 10 ÷ 3
3

julia> 10 // 3
10//3

La funzione div() serve per la divisione intera, con il risultato troncato verso lo zero: verso il basso per i numeri positivi, verso l'alto per quelli negativi.

Come sinonimo, possiamo usare l'operatore infisso ÷, sempre con l'obiettivo di farlo sembrare più matematico. Se usi un editor che riconosce Julia, inseriscilo come \div e poi premi il tasto <Tab>.

L'operatore // avrà bisogno di un concetto tutto suo, più avanti nel percorso.

Per ora possiamo semplicemente dire che il risultato di // è un numero «razionale», il nome formale di quello che la maggior parte delle persone chiama frazione.

I fattori comuni vengono rimossi da numeratore e denominatore, per dare un rapporto tra due numeri interi in quella che viene chiamata «forma ridotta».

julia> rationalnum = 22 // 6
11//3

julia> typeof(rationalnum)
Rational{Int64}

Abbiamo i numeri razionali. E i numeri «irrazionali»?

julia> π
π = 3.1415926535897...

julia> typeof(π)
Irrational{:π}

Un numero irrazionale è un numero che non può essere ridotto a un rapporto tra interi. Esempi comuni sono π, e (il numero di Eulero) e molte radici come √2 (la radice quadrata di 2).

Julia cerca di fare matematica come si deve.

Conversione dei tipi numerici

Questo spesso può avvenire automaticamente:

julia> x = 2 + 3.5
5.5

julia> typeof(x)
Float64

Abbiamo sommato un Int64 a un Float64 e ottenuto un risultato Float64.

In realtà, l'intero è stato convertito silenziosamente in Float64 prima di eseguire la somma.

Julia ha un concetto di «ampiezza» dei tipi numerici.

  • All'interno degli interi, e all'interno dei float, è semplicemente il numero di bit necessari per la memorizzazione. Quindi Int64 è più ampio di Int16.
  • I float sono considerati più ampi degli interi, perché possono memorizzare una parte frazionaria qualsiasi.

Se in un'espressione si usa una miscela di tipi, ciascuno viene «promosso», quando serve, al tipo più ampio tra quelli usati.

Per forzare la conversione, possiamo convertire un intero in un tipo specifico, come in Float64(5).

In alternativa, basta usare float(5) e lasciare che sia il compilatore a scegliere un tipo appropriato.

Le conversioni da float a intero sono inevitabilmente più complicate. Cosa vuoi fare con quello che c'è dopo il punto decimale?

  • La funzione round() converte al numero intero più vicino, e nei casi di parità come 4.5 arrotonda al numero intero pari più vicino.
  • floor() arrotonda per difetto, ceil() per eccesso, trunc() verso lo zero.
  • Se provi a convertire direttamente, per esempio con Int32(), otterrai un InexactError.

Tuttavia, per impostazione predefinita queste funzioni non restituiscono il tipo intero che avresti voluto. Si può specificare il tipo di output desiderato.

julia> round(4.5)
4.0

julia> round(Int64, 4.5)
4

julia> round(Int, 4.5)  # => default integer type
4

julia> ceil(Int16, 4.3)
5

È anche possibile arrotondare a un numero specificato di cifre dopo il punto decimale, usando la parola chiave digits.

julia> round(π, digits=10)
3.1415926536

Vedi il manuale per maggiori dettagli.

Divisione per zero

Sicuramente questo genera semplicemente un errore? In realtà, la situazione non è così semplice.

La divisione intera con ÷ o // produce un errore, come ti aspetteresti.

La divisione in virgola mobile con / adotta quello che si potrebbe considerare un approccio ingegneristico, anziché l'approccio standard dell'informatica:

julia> 2 / 0
Inf

julia> 0 / 0
NaN

Come discusso in una sezione precedente, l'infinito è un numero in virgola mobile valido in Julia, rappresentato da Inf.

Quando anche il numeratore è zero, il risultato è matematicamente indefinito. Julia lo considera allora un valore «non numerico», rappresentato da NaN.

Se questo ti sembra strano, pensalo nel contesto di lavorare con grandi array di dati reali (e quindi spesso piuttosto disordinati). Per andare avanti, la cosa migliore è semplicemente segnalare i valori problematici e proseguire.

Un controllo manuale infinito dei valori sarebbe tedioso da programmare e danneggerebbe sicuramente le prestazioni in fase di esecuzione.

Fermarsi con un messaggio di errore a ogni minimo intoppo renderebbe il tuo programma molto impopolare tra gli utenti!

Confrontare valori in virgola mobile

Come descritto nel concetto Conditionals, il test di uguaglianza si fa di solito con l'operatore ==.

Funziona bene per numeri interi, caratteri, stringhe, ecc. Tuttavia, i valori in virgola mobile hanno una precisione limitata, e modi diversi di calcolare lo stesso risultato possono portare a valori leggermente diversi. Per Float64, questo avverrà di solito intorno alla quindicesima cifra significativa: una piccola differenza, ma non un'«uguaglianza».

Tradizionalmente, il consiglio dato a chi programma è di non usare mai == con i valori in virgola mobile: i risultati sono imprevedibili.

Un'alternativa molto usata è confrontare il valore assoluto della differenza con una tolleranza consentita (spesso chiamata epsilon o ϵ). Quindi, invece di a == b, usa abs(a - b) < epsilon.

Julia offre un'alternativa più pulita con la funzione isapprox().

Per una tolleranza assoluta, la sintassi è isapprox(a, b, atol=epsilon). In questo caso la parola chiave atol è obbligatoria.

La tolleranza relativa è spesso più utile. È quella predefinita, quindi isapprox(a, b) cercherà di scegliere un valore sensato per rtol (le regole sono piuttosto complicate).

Si può anche specificare una tolleranza relativa, come frazione dei valori confrontati. Quindi isapprox(a, b, rtol=0.01 verifica che i valori differiscano meno dell'1%.

Alla maniera tipica di Julia, esiste un operatore matematico che è sinonimo del caso predefinito: a ≈ b (con l'operatore inserito come \approx e poi <tab>).

Concetti futuri correlati

Oltre ai numeri razionali, le parti successive del percorso tratteranno:

Modifica tramite GitHub Il collegamento si apre in una nuova finestra o scheda

Impara Numeri

La pratica è bloccata

Sblocca 2 altri esercizi per esercitarti su Numeri