Rust - from Mozilla

Interi e float            

Rust prevede i letterali interi e i letterali in virgola (floating point).

Gli interi con i quali possiamo lavorare sono i seguenti:

Prefisso Tipo Range
u64 senza segno - 64 bit da  0 a (2^64) - 1
i64 con segno - 64 bit da -(2^63) a (2^63) - 1
u32 senza segno - 32 bit da 0 a (2^32) - 1
i32 con segno - 32 bit da -(2^31) a (2^31) - 1
u16 senza segno - 16 bit da 0 a (2^16) - 1
i16 con segno - 16 bit da -(2^15) a (2^15) - 1
u8 senza segno - 8 bit da 0 a (2^8) - 1
i8 con segno - 8 bit da -(2^7) a (2^7) - 1
usize / isize puntatore senza e con segno variabile (dipende dal S.O in uso)

Gli interi hanno sostanzialmente quattro formati:
decimale standard - inizia con un decimale e prosegue con una sequenza di altri decimali e underscore
esadecimale - inizia con 0x e prosegue con una sequenza di caratteri esadecimali (da 0 a F) e underscore
ottale - inizia con 0o e prosegue con una sequenza di caratteri ottali (da 0 a 7) e underscore
binario - inizia con 0b e prosegue con una sequenza di caratteri binari (0 e 1) e underscore

Il tipo i32 costituisce il default per l'inferenza di tipo.

Vediamo un esempio relativo alle 4 operazioni di base insieme all'operatore che restituisce il resto di una divisone. In questo stesso esempio compare anche la traformazione da stringa a intero e viceversa:

  Esempio 3.1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use std::io;
use std::io::prelude::*;
fn main()
{
  print!( "Insert a number: ");
  io::stdout().flush().ok().expect("");
  let mut input01 = String::new();
  io::stdin().read_line(&mut input01);
  print!( "Insert another number: ");
  io::stdout().flush().ok().expect("");
  let mut input02 = String::new();
  io::stdin().read_line(&mut input02);
  let x01: i32 = input01.trim().parse()
  .ok()
  .expect("Please type a number!");
  let x02: i32 = input02.trim().parse()
  .ok()
  .expect("Please type a number!");
  println! ("{}", (x01 + x02));
  println! ("{}", (x01 - x02));
  println! ("{}", (x01 * x02));
  println! ("{}", (x01 / x02));
  println! ("{}", ((x01 as f32) / (x02 as f32)));
  println! ("{}", (x01 % x02));
}

Non proprio intuitivo in questo momento.... ad ogni modo vediamo qualche analisi.
Le righe dalla 13 alla 15 e poi dalla 16 alla 18 abbiamo la trasformazione di una stringa in intero. Prendetela per ora così come viene data, tenete presente che mentre in altri linguaggi funzioni tipo parse restituiscono direttamente un intero, in Rust esso ci restituisce una coppia <tipo - errore> per cui non può essere  usata in maniera "semplice". Se vi può consolare, anzichè expect, alle righe 15 e 18, potete usare unwrap().
Le righe dalla 19 alla 25 invece ci mostrano le operazioni di base. I simboli per le operazioni sono quelli standard già visti in latri linguaggi, / è per la divisione % restituisce il resto di una divisione. Da notare che la riga 23, forza una conversione da intero a float 32 di modo da poter avere il risultato decimale anzichè solo la parte intera come si ottiene alla  22. La keyword utilizzata per la conversione è, evidentemente, as. Otterrete 7 cifre decimali, se ne volete di più dovete usare f64 invece di f32, i tipi float sono due, come vedremo..

Un problema che può sorgere, in Rust come in altri linguaggi, è quello di usare un tipo di numero troppo "corto" per le esigenze computazionali. Vediamo l'esempio:

  Esempio 3.2
1
2
3
4
5
6
fn main()
{
  let mut x: i8 = 30;
  x = x + 1000;
  println!("{}", x);
}

x è definita i8 quindi dovrebbe accogliere valori da -128 a 127. Sommandogli 1000 va chiaramente fuori range. Il compilatore di dà un aiutino, riconoscendo la situazione:

warning: literal out of range for i8, #[warn(overflowing_literals)] on by default

ma il programma gira e ci dà come risultato 6 (abbastanza semplice capire perchè).

Gli interi hanno alcune proprietà, ne vediamo alcune giusto per capirne l'uso:

  Esempio 3.3
1
2
3
4
5
6
7
8
fn main()
{
  let x = 5;
  println!("{}", i32::count_zeros(x));
  println!("{}", i32::count_ones(x));
  println!("{:?}", i32::checked_add(23, 7));
  println!("{}", i32::pow(4,3));
}

La riga 4 espone gli zero nella composizione binaria del numero.
La riga 5 espone gli uno nella composizione binaria del numero
La riga 6 propone un check sulla somma di due numeri
La riga 7 è quella più interessante perchè ci propone l'elevamento a potenza.

Sono ovviamente ammesse le classiche rappresentazioni in formato binario, ottale ed esadecimale. I prefissi sono quelli canonici:

0b per il formato binario
0o per il formato ottale
0x per il formato esadecimale.

Non sono ammesse le lettere maiuscole, quindi, ad esempio, il prefisso 0X non è accettato dal compilatore. Vediamo un paio di esempi:

  Esempio 3.4
1
2
3
4
5
6
7
fn main()
{
  let x01 = 0b11;
  let x02 = 0o25;
  let x03 = 0x15b;
  println!("{} {} {}", x01, x02, x03);
}

e in forma inversa:

  Esempio 3.5
1
2
3
4
5
fn main()
{
  let x01 = 30;
  println!("{:b} {:o} {:x}", x01, x01, x01);
}

che permette, partendo da un qualunque numero, di ottenere la sua rappresentazione nei 3 formati.

I numeri floating point hanno due diversi formati:

*** uno o più decimali seguiti da un punto ed eventualmente da un'altra serie di decimali con un esponente opzionale
*** una serie di decimali seguiti da un esponente.

ad esempio:

2.33
3.
12345E+12

i tipi in virgola previsti da Rust sono:

f32, definito su 32 bit
f64, definito su 64 bit 

ovviamente cambia la precisione prevista, rispettivamente su 7 e 15 posizioni, come potete vedere nel seguente esempio.

  Esempio 3.6
1
2
3
4
5
6
7
8
9
fn main()
{
  let x = 7.0;
  let y = 3.0;
  println!("{}", x / y);
  let z = 7.0f32;
  let t = 3.0f32;
  println!("{}", z / t);
}

Le operazioni di base sono ovviamente le stesse viste per i numeri interi, ivi compreso anche il resto della divisione. L'elevamento a potenza è affidata a due funzioni apposite:

f64::powi(float, intero)
f64::powf(float, float)

nb: quanto abbiamo visto e vedremo relativamente alle funzioni per gli f64 vale ovviamente anche per gli f32.

Sul sito ufficiale potete trovare parecchia ulteriore documentazione, in particolare qui. Da parte mia fornisco un piccolo esempio per chiarire l'uso:

  Esempio 3.7
1
2
3
4
5
6
7
8
9
fn main()
{
  let x = 7.1;
  let y = 3.34;
  println!("{}", f64::floor(x));
  println!("{}", f64::ceil(y));
  println!("{}", f64::round(y));
  println!("{}", f64::trunc(y));
}