Journal de développement des smart contracts en Rust (7) : Calcul numérique
1. Problème de précision dans les calculs en 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 ratios ou de taux d'intérêt impliquant des décisions économiques/financières importantes.
Le type de nombre à virgule flottante en double précision f64 dans le langage Rust suit la norme IEEE 754 et utilise une notation scientifique en base 2. Certains décimales comme 0.7 ne peuvent pas être représentées avec précision par un nombre à virgule flottante de longueur finie, ce qui entraîne un phénomène d'"arrondi".
Lors d'un test de distribution de 0,7 NEAR tokens à dix utilisateurs sur la blockchain NEAR, le résultat des opérations flottantes n'est pas précis :
rouille
let amount: f64 = 0.7;
let divisor: f64 = 10.0;
let result_0 = amount / divisor;
la valeur de amount est de 0.69999999999999995559, la valeur de result_0 est de 0.06999999999999999, au lieu de l'attendu 0.07.
Pour résoudre ce problème, on peut utiliser des nombres à virgule fixe. Dans le protocole NEAR, on utilise généralement la manière suivante pour représenter : 1 NEAR = 10^24 yoctoNEAR.
rouille
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;
Cela permet d'obtenir un résultat de calcul précis : 0,7 NEAR / 10 = 0,07 NEAR.
2. Problèmes de précision des calculs d'entiers en Rust
2.1 ordre des opérations
Dans les opérations de multiplication et de division ayant la même priorité arithmétique, l'ordre des opérations peut directement influencer le résultat du calcul. Par exemple :
rouille
let a: u128 = 1_0000;
let b: u128 = 10_0000;
let c: u128 = 20;
// result_0 = a * c / b
let result_0 = a.checked_mul(c).expect("ERR_MUL").checked_div(b).expect("ERR_DIV");
// result_1 = a / b * c
let result_1 = a.checked_div(b).expect("ERR_DIV").checked_mul(c).expect("ERR_MUL");
Les résultats de result_0 et result_1 sont différents, car pour la division entière, la précision inférieure au diviseur sera abandonnée.
2.2 trop petit ordre de grandeur
Lorsqu'il s'agit de petites valeurs, les opérations sur les entiers peuvent entraîner une perte de précision:
rouille
let a: u128 = 10;
let b: u128 = 3;
let c: u128 = 4;
let decimal: u128 = 100_0000;
// result_0 = (a / b) * c
let result_0 = a.checked_div(b).expect("ERR_DIV").checked_mul(c).expect("ERR_MUL");
// result_1 = (a * decimal / b) * c / decimal;
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");
Le résultat de result_0 et result_1 est différent, result_1 est plus proche de la valeur attendue réelle.
3. Comment écrire des smart contracts Rust pour l'évaluation numérique
3.1 Ajuster l'ordre des opérations
Faire en sorte que la multiplication entière prenne priorité sur la division entière.
3.2 augmenter l'ordre de grandeur des entiers
Utiliser une plus grande échelle pour créer des molécules plus grandes. Par exemple, représenter 5,123 NEAR comme 51_230_000_000 yoctoNEAR.
3.3 perte de précision des opérations d'accumulation
Enregistrer la perte de précision des calculs cumulés et la compenser dans les calculs ultérieurs. Par exemple :
rouille
u128 {
let token_to_distribute = offset + amount;
let per_user_share = token_to_distribute / USER_NUM;
let recorded_offset = token_to_distribute - per_user_share * USER_NUM;
recorded_offset
}
( 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
Dans la conception de smart contracts, le problème d'arrondi est généralement basé sur le principe "Je veux en profiter, les autres ne doivent pas me tondre la laine sur le dos". En fonction de la situation, choisissez l'arrondi vers le bas, l'arrondi vers le haut ou l'arrondi classique.
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.
6 J'aime
Récompense
6
3
Partager
Commentaire
0/400
BearMarketBuilder
· 08-05 07:58
Le trou des nombres à virgule flottante est tout simplement aussi profond que la mer.
Voir l'originalRépondre0
SelfSovereignSteve
· 08-05 07:53
Les nombres à virgule flottante sont vraiment problématiques, les smart contracts vont forcément échouer, je n'ose absolument pas les utiliser.
Voir l'originalRépondre0
DegenGambler
· 08-05 07:49
Joueurs de Rust, faites attention, le piège de la précision n'est pas petit.
Développement de smart contracts en Rust : techniques de calcul numérique et de contrôle de la précision
Journal de développement des smart contracts en Rust (7) : Calcul numérique
1. Problème de précision dans les calculs en 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 ratios ou de taux d'intérêt impliquant des décisions économiques/financières importantes.
Le type de nombre à virgule flottante en double précision f64 dans le langage Rust suit la norme IEEE 754 et utilise une notation scientifique en base 2. Certains décimales comme 0.7 ne peuvent pas être représentées avec précision par un nombre à virgule flottante de longueur finie, ce qui entraîne un phénomène d'"arrondi".
Lors d'un test de distribution de 0,7 NEAR tokens à dix utilisateurs sur la blockchain NEAR, le résultat des opérations flottantes n'est pas précis :
rouille let amount: f64 = 0.7;
let divisor: f64 = 10.0; let result_0 = amount / divisor;
la valeur de amount est de 0.69999999999999995559, la valeur de result_0 est de 0.06999999999999999, au lieu de l'attendu 0.07.
Pour résoudre ce problème, on peut utiliser des nombres à virgule fixe. Dans le protocole NEAR, on utilise généralement la manière suivante pour représenter : 1 NEAR = 10^24 yoctoNEAR.
rouille 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;
Cela permet d'obtenir un résultat de calcul précis : 0,7 NEAR / 10 = 0,07 NEAR.
2. Problèmes de précision des calculs d'entiers en Rust
2.1 ordre des opérations
Dans les opérations de multiplication et de division ayant la même priorité arithmétique, l'ordre des opérations peut directement influencer le résultat du calcul. Par exemple :
rouille let a: u128 = 1_0000; let b: u128 = 10_0000; let c: u128 = 20;
// result_0 = a * c / b let result_0 = a.checked_mul(c).expect("ERR_MUL").checked_div(b).expect("ERR_DIV");
// result_1 = a / b * c
let result_1 = a.checked_div(b).expect("ERR_DIV").checked_mul(c).expect("ERR_MUL");
Les résultats de result_0 et result_1 sont différents, car pour la division entière, la précision inférieure au diviseur sera abandonnée.
2.2 trop petit ordre de grandeur
Lorsqu'il s'agit de petites valeurs, les opérations sur les entiers peuvent entraîner une perte de précision:
rouille let a: u128 = 10; let b: u128 = 3; let c: u128 = 4; let decimal: u128 = 100_0000;
// result_0 = (a / b) * c let result_0 = a.checked_div(b).expect("ERR_DIV").checked_mul(c).expect("ERR_MUL");
// result_1 = (a * decimal / b) * c / decimal; 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");
Le résultat de result_0 et result_1 est différent, result_1 est plus proche de la valeur attendue réelle.
3. Comment écrire des smart contracts Rust pour l'évaluation numérique
3.1 Ajuster l'ordre des opérations
Faire en sorte que la multiplication entière prenne priorité sur la division entière.
3.2 augmenter l'ordre de grandeur des entiers
Utiliser une plus grande échelle pour créer des molécules plus grandes. Par exemple, représenter 5,123 NEAR comme 51_230_000_000 yoctoNEAR.
3.3 perte de précision des opérations d'accumulation
Enregistrer la perte de précision des calculs cumulés et la compenser dans les calculs ultérieurs. Par exemple :
rouille u128 { let token_to_distribute = offset + amount; let per_user_share = token_to_distribute / USER_NUM; let recorded_offset = token_to_distribute - per_user_share * USER_NUM; recorded_offset }
( 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
Dans la conception de smart contracts, le problème d'arrondi est généralement basé sur le principe "Je veux en profiter, les autres ne doivent pas me tondre la laine sur le dos". En fonction de la situation, choisissez l'arrondi vers le bas, l'arrondi vers le haut ou l'arrondi classique.
![]###https://img-cdn.gateio.im/webp-social/moments-6e8b4081214a69423fc7ae022d05c728.webp###