00
:
00
:
00
:
00
•Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

Numeri in Rust

Rust offre un sistema numerico ricco e preciso, con molteplici tipi interi e in virgola mobile progettati per coprire ogni esigenza. A differenza di molti linguaggi, Rust non effettua conversioni implicite tra tipi numerici, garantendo massima sicurezza e prevedibilità. In questa guida esamineremo in dettaglio tutti i tipi numerici disponibili.

Tipi Interi con Segno

I tipi interi con segno possono rappresentare numeri positivi e negativi. Usano la rappresentazione in complemento a due:

Tipo Dimensione Range
i8 8 bit da -128 a 127
i16 16 bit da -32.768 a 32.767
i32 32 bit da -2.147.483.648 a 2.147.483.647
i64 64 bit da -9.223.372.036.854.775.808 a 9.223.372.036.854.775.807
i128 128 bit da -2^127 a 2^127 - 1
isize arch Dipende dall’architettura (32 o 64 bit)
fn main() {
    let piccolo: i8 = -100;
    let medio: i16 = 30_000;
    let standard: i32 = 2_000_000;
    let grande: i64 = 9_000_000_000;
    let enorme: i128 = 170_141_183_460_469_231_731_687_303_715_884;
    let indice: isize = -1; // Dipende dalla piattaforma

    println!("i8: {}, i16: {}, i32: {}", piccolo, medio, standard);
    println!("i64: {}, i128: {}, isize: {}", grande, enorme, indice);
}

Tipi Interi senza Segno

I tipi interi senza segno rappresentano solo valori positivi (incluso zero):

Tipo Dimensione Range
u8 8 bit da 0 a 255
u16 16 bit da 0 a 65.535
u32 32 bit da 0 a 4.294.967.295
u64 64 bit da 0 a 18.446.744.073.709.551.615
u128 128 bit da 0 a 2^128 - 1
usize arch Dipende dall’architettura
fn main() {
    let byte: u8 = 255;
    let porta: u16 = 8080;
    let contatore: u32 = 1_000_000;
    let file_size: u64 = 4_294_967_296;
    let lunghezza: usize = 42; // Usato per indici e lunghezze

    println!("byte: {}, porta: {}", byte, porta);
    println!("contatore: {}, file_size: {}", contatore, file_size);
    println!("lunghezza: {}", lunghezza);
}

Il tipo usize è particolarmente importante: viene usato per indicizzare le collezioni e rappresenta la dimensione dell’architettura del processore.

Tipi in Virgola Mobile

Rust ha due tipi floating-point conformi allo standard IEEE 754:

fn main() {
    let singola: f32 = 3.14;        // Precisione singola (32 bit)
    let doppia: f64 = 3.141592653589793; // Precisione doppia (64 bit, predefinito)

    println!("f32: {}", singola);
    println!("f64: {}", doppia);

    // Valori speciali
    let infinito = f64::INFINITY;
    let neg_infinito = f64::NEG_INFINITY;
    let non_numero = f64::NAN;

    println!("Infinito: {}", infinito);
    println!("-Infinito: {}", neg_infinito);
    println!("NaN: {}", non_numero);
    println!("NaN == NaN? {}", non_numero == non_numero); // false!
}

Il tipo predefinito per i letterali in virgola mobile è f64, che offre maggiore precisione con prestazioni simili a f32 sulle CPU moderne a 64 bit.

Letterali Numerici

Rust supporta diverse notazioni per i letterali numerici:

fn main() {
    // Decimale
    let decimale = 98_222;

    // Esadecimale (prefisso 0x)
    let esadecimale = 0xFF;

    // Ottale (prefisso 0o)
    let ottale = 0o77;

    // Binario (prefisso 0b)
    let binario = 0b1111_0000;

    // Byte (solo u8, prefisso b)
    let byte_letterale = b'A'; // Valore ASCII di 'A' = 65

    println!("Decimale: {}", decimale);
    println!("Esadecimale: {} (0xFF)", esadecimale);
    println!("Ottale: {} (0o77)", ottale);
    println!("Binario: {} (0b1111_0000)", binario);
    println!("Byte: {} (b'A')", byte_letterale);
}

Underscore nei Numeri

Gli underscore _ possono essere inseriti ovunque nei letterali numerici per migliorarne la leggibilita, senza influenzare il valore:

fn main() {
    let milione = 1_000_000;
    let pi = 3.141_592_653;
    let esadecimale = 0xFF_EC_DE;
    let binario = 0b1100_1010_0011;

    println!("Milione: {}", milione);
    println!("Pi: {}", pi);
    println!("Hex: {:#X}", esadecimale);
    println!("Bin: {:#b}", binario);
}

Suffissi di Tipo

È possibile specificare il tipo di un letterale numerico tramite un suffisso:

fn main() {
    let a = 42u8;        // u8
    let b = 1_000i64;    // i64
    let c = 3.14f32;     // f32
    let d = 100_usize;   // usize

    println!("a: {}, b: {}, c: {}, d: {}", a, b, c, d);
}

Integer Overflow

In Rust, l’overflow degli interi viene gestito in modo diverso a seconda della modalità di compilazione:

  • Debug: L’overflow causa un panic (il programma si interrompe).
  • Release: L’overflow esegue il wrapping (complemento a due).
fn main() {
    let massimo: u8 = 255;
    // let overflow = massimo + 1; // Panic in debug!

    // Metodi espliciti per gestire l'overflow:

    // Checked: restituisce Option<T>
    let checked = massimo.checked_add(1);
    println!("Checked: {:?}", checked); // None

    // Wrapping: esegue il wrapping
    let wrapped = massimo.wrapping_add(1);
    println!("Wrapped: {}", wrapped); // 0

    // Saturating: si ferma al valore massimo/minimo
    let saturated = massimo.saturating_add(10);
    println!("Saturated: {}", saturated); // 255

    // Overflowing: restituisce (valore, bool_overflow)
    let (valore, overflow) = massimo.overflowing_add(1);
    println!("Overflowing: {} (overflow: {})", valore, overflow); // 0, true
}

Operazioni Numeriche

Rust supporta tutte le operazioni aritmetiche standard:

fn main() {
    // Addizione
    let somma = 5 + 10;

    // Sottrazione
    let differenza = 95.5 - 4.3;

    // Moltiplicazione
    let prodotto = 4 * 30;

    // Divisione
    let quoziente = 56.7 / 32.2;
    let divisione_intera = 5 / 3; // Risultato: 1 (troncato)

    // Resto (modulo)
    let resto = 43 % 5;

    println!("Somma: {}", somma);
    println!("Differenza: {}", differenza);
    println!("Prodotto: {}", prodotto);
    println!("Quoziente: {:.4}", quoziente);
    println!("Divisione intera: {}", divisione_intera);
    println!("Resto: {}", resto);
}

Conversione con as

Rust non esegue conversioni implicite tra tipi numerici. Per convertire, si usa la parola chiave as:

fn main() {
    let intero: i32 = 42;
    let decimale: f64 = intero as f64;
    println!("i32 -> f64: {}", decimale);

    let grande: f64 = 3.99;
    let troncato: i32 = grande as i32; // Tronca la parte decimale
    println!("f64 -> i32: {}", troncato); // 3 (non arrotonda)

    let valore: i32 = 300;
    let piccolo: u8 = valore as u8; // Overflow: 300 % 256 = 44
    println!("i32(300) -> u8: {}", piccolo); // 44

    let negativo: i32 = -1;
    let senza_segno: u32 = negativo as u32;
    println!("i32(-1) -> u32: {}", senza_segno); // 4294967295
}

Per conversioni sicure, è preferibile usare i metodi try_from e try_into:

fn main() {
    let grande: i64 = 1_000_000;

    match i32::try_from(grande) {
        Ok(valore) => println!("Conversione riuscita: {}", valore),
        Err(e) => println!("Errore di conversione: {}", e),
    }

    let troppo_grande: i64 = 10_000_000_000;
    match i32::try_from(troppo_grande) {
        Ok(valore) => println!("Conversione riuscita: {}", valore),
        Err(e) => println!("Errore di conversione: {}", e),
    }
}

Conclusione

Rust offre un sistema numerico completo e sicuro, con tipi che coprono ogni esigenza di precisione e dimensione. L’assenza di conversioni implicite e la gestione esplicita dell’overflow rendono il codice numerico in Rust prevedibile e privo di bug sottili. La scelta del tipo numerico appropriato e l’uso consapevole delle conversioni sono competenze fondamentali per ogni programmatore Rust.