Rust smart contracts : pièges des nombres à virgule flottante et optimisation de la précision des entiers

Journal de développement des smart contracts Rust (7) Calculs numériques

1. Problèmes de précision dans les opérations sur les nombres à virgule flottante

Le langage Rust prend en charge nativement les opérations sur les nombres à virgule flottante, mais ces opérations présentent des problèmes de précision de calcul inévitables. Lors de la rédaction de smart contracts, il est déconseillé d'utiliser des opérations sur les nombres à virgule flottante, en particulier lors du traitement de taux ou de taux d'intérêt impliquant des décisions économiques/financières importantes.

Le langage Rust suit la norme IEEE 754 pour les nombres à virgule flottante. Prenons l'exemple du type à virgule flottante double précision f64, sa représentation binaire interne est comme suit :

Les nombres à virgule flottante sont exprimés en notation scientifique à base 2. Par exemple, 0.8125 peut être représenté par le nombre binaire à une longueur finie 0.1101 :

0.8125 = 0.5 * 1 + 0.25 * 1 + 0.125 * 0 + 0.0625 * 1

Cependant, pour un nombre décimal aussi petit que 0.7, il peut y avoir des cas de répétition infinie :

0.7 = 0.1011001100110011...

Cela entraîne une incapacité à représenter avec précision les nombres à virgule flottante de longueur limitée, ce qui provoque un phénomène d'"arrondi".

Prenons l'exemple de la distribution de 0,7 NEAR tokens à dix utilisateurs sur la blockchain NEAR :

rouille #[test] fn precision_test_float() { let amount: f64 = 0.7;
let divisor: f64 = 10.0;
let result_0 = amount / divisor;
println!("La valeur du montant : {:.20}", montant); assert_eq!(result_0, 0.07, ""); }

Résultat d'exécution :

exécuter 1 test La valeur du montant : 0.69999999999999995559 le fil 'tests::precision_test_float' a paniqué à 'assertion échouée : (left == right) gauche: 0.06999999999999999, droite: 0.07: ', src/lib.rs:185:9

On peut voir que la valeur de amount n'est pas exactement 0.7, mais plutôt une valeur approximative de 0.69999999999999995559. De plus, le résultat de la division devient également imprécis à 0.06999999999999999.

Pour résoudre ce problème, on peut envisager d'utiliser des nombres à virgule fixe. Dans le protocole NEAR, on utilise généralement une représentation où 10^24 yoctoNEAR équivaut à 1 jeton NEAR.

Code de test modifié :

rouille #[test] fn precision_test_integer() { let N: u128 = 1_000_000_000_000_000_000_000_000;
let amount: u128 = 700_000_000_000_000_000_000_000; let divisor: u128 = 10;
let result_0 = amount / divisor; assert_eq!(result_0, 70_000_000_000_000_000_000_000, ""); }

Résultat de l'exécution:

exécution d'un test test tests::precision_test_integer ... ok résultat du test : ok. 1 réussi ; 0 échoué ; 0 ignoré ; 0 mesuré ; 8 filtrés ; terminé en 0,00s

2. Problème de précision des calculs d'entiers en Rust

2.1 ordre des opérations

Pour la multiplication et la division ayant la même priorité arithmétique, le changement de l'ordre peut directement affecter le résultat du calcul, entraînant des problèmes de précision dans le calcul des entiers.

rouille #[test] fn precision_test_div_before_mul() { let a: u128 = 1_0000; let b: u128 = 10_0000; let c: u128 = 20;

let result_0 = a

.checked_mul(c) .expect("ERR_MUL") .checked_div(b) .expect("ERR_DIV");

let result_1 = a

.checked_div(b) .expect("ERR_DIV") .checked_mul(c) .expect("ERR_MUL");

assert_eq!(result_0,result_1,"");

}

Résultat d'exécution:

exécution d'un test le fil 'tests::precision_test_0' a paniqué à 'échec de l'assertion : (left == right) gauche: 2, droite: 0: ', src/lib.rs:175:9

On peut constater que result_0 = a * c / b et result_1 = (a / b) * c, bien que les formules de calcul soient identiques, les résultats sont différents. La raison en est que la division entière abandonne la précision inférieure au diviseur. Lors du calcul de result_1, le calcul de (a / b) conduit à une perte de précision qui devient 0 ; tandis que lors du calcul de result_0, on calcule d'abord a * c = 20_0000, ce qui est supérieur au diviseur b, évitant ainsi la perte de précision.

2.2 une magnitude trop petite

rouille #[test] fn precision_test_decimals() { let a: u128 = 10; let b: u128 = 3; let c: u128 = 4; let decimal: u128 = 100_0000;

let result_0 = a
    .checked_div(b)
    .expect("ERR_DIV")
    .checked_mul(c)
    .expect("ERR_MUL");

let result_1 = a
    .checked_mul(decimal)  
    .expect("ERR_MUL")
    .checked_div(b)
    .expect("ERR_DIV")

.checked_mul(c) .expect("ERR_MUL") .checked_div(decimal)
.expect("ERR_DIV");

println!("{}:{}", result_0, result_1);
assert_eq!(result_0, result_1, "");

}

Résultat de l'exécution:

exécution de 1 test 12:13 le fil 'tests::precision_test_decimals' a paniqué à 'assertion échouée : (left == right) left: 12, right: 13: ', src/lib.rs:214:9

On peut voir que les résultats result_0 et result_1, qui sont équivalents dans le processus de calcul, sont différents, et que result_1 = 13 est plus proche de l'attente réelle de 13.3333...

3. Comment rédiger des smart contracts Rust pour l'évaluation numérique

3.1 Ajuster l'ordre des opérations

  • Faire en sorte que la multiplication des entiers soit prioritaire par rapport à la division des entiers.

3.2 Augmenter l'ordre de grandeur des entiers

  • Utiliser des ordres de grandeur plus élevés pour créer des numérateurs plus grands.

3.3 perte de précision des calculs accumulés

Concernant le problème inévitable de la précision des calculs entiers, il est possible de considérer l'enregistrement des pertes de précision cumulées.

rouille const USER_NUM: u128 = 3;

u128 { let token_to_distribute = offset + amount; let per_user_share = token_to_distribute / USER_NUM; println!("per_user_share {}", per_user_share); let recorded_offset = token_to_distribute - per_user_share * USER_NUM; recorded_offset }

#( fn record_offset_test)[test] { let mut offset: u128 = 0; pour i dans 1..7 { println!("Round {}", i); offset = distribute(10_000_000_000_000_000_000_000_000, offset); println!("Offset {}\n", offset); } }

Résultat de l'exécution:

exécuter 1 test Round 1 per_user_share 3333333333333333333333333 Décalage 1

Round 2 per_user_share 3333333333333333333333333 Offset 2

Round 3 per_user_share 4000000000000000000000000 Offset 0

Round 4 per_user_share 3333333333333333333333333 Offset 1

Round 5 per_user_share 3333333333333333333333333 Offset 2

Round 6 per_user_share 4000000000000000000000000 Décalage 0

test tests::record_offset_test ... ok résultat du test : ok. 1 passé ; 0 échoué ; 0 ignoré ; 0 mesuré ; 9 filtrés ; terminé en 0.00s

( 3.4 Utilisation de la bibliothèque Rust Crate rust-decimal

Cette bibliothèque est adaptée aux calculs financiers décimaux nécessitant une précision efficace et sans erreur d'arrondi.

) 3.5 Considérer le mécanisme d'arrondi

Lors de la conception de smart contracts, le problème de l'arrondi est généralement abordé selon le principe "Je veux profiter, les autres ne doivent pas me tondre". Selon ce principe, si arrondir vers le bas est à mon avantage, alors je fais ça ; si arrondir vers le haut est à mon avantage, alors je fais ça ; l'arrondi traditionnel ne peut pas déterminer à qui cela profite, donc il est très rarement utilisé.

![]###https://img-cdn.gateio.im/webp-social/moments-1933a4a2dd723a847f0059d31d1780d1.webp###

Voir l'original
Cette page peut inclure du contenu de tiers fourni à des fins d'information uniquement. Gate ne garantit ni l'exactitude ni la validité de ces contenus, n’endosse pas les opinions exprimées, et ne fournit aucun conseil financier ou professionnel à travers ces informations. Voir la section Avertissement pour plus de détails.
  • Récompense
  • 5
  • Partager
Commentaire
0/400
LiquidationKingvip
· 07-16 06:01
Ce que tu dis à propos de 0,7 n'est pas une grande affaire, il faut un gros krach pour que ce soit intéressant.
Voir l'originalRépondre0
BankruptcyArtistvip
· 07-16 05:57
Ah, écrire des smart contracts m'a vraiment causé des problèmes avec les nombres à virgule flottante.
Voir l'originalRépondre0
GmGmNoGnvip
· 07-16 05:55
Ce bug peut me faire perdre toute ma précision.
Voir l'originalRépondre0
NotSatoshivip
· 07-16 05:55
Dans les smart contracts, jouer avec des points flottants peut faire exploser votre tête.
Voir l'originalRépondre0
SandwichVictimvip
· 07-16 05:40
Le problème de précision de Schrödinger
Voir l'originalRépondre0
  • Épingler
Trader les cryptos partout et à tout moment
qrCode
Scan pour télécharger Gate app
Communauté
Français (Afrique)
  • 简体中文
  • English
  • Tiếng Việt
  • 繁體中文
  • Español
  • Русский
  • Français (Afrique)
  • Português (Portugal)
  • Bahasa Indonesia
  • 日本語
  • بالعربية
  • Українська
  • Português (Brasil)