Problèmes de précision des calculs numériques dans le développement de smart contracts en Rust
1. Problème de précision des 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 l'écriture de smart contracts, il n'est pas recommandé d'utiliser des opérations sur les nombres à virgule flottante, en particulier lors du traitement de ratios ou de taux d'intérêt liés à des décisions économiques/financières importantes.
Le type flottant à double précision f64 dans le langage Rust suit la norme IEEE 754 et utilise la notation scientifique avec une base de 2. Certains décimales ne peuvent pas être représentées avec précision par un nombre flottant de longueur finie, ce qui entraîne un phénomène de "rond".
Par exemple, lors de la distribution de 0,7 NEAR à 10 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, "");
}
Le résultat affiché montre que la valeur de amount est de 0.69999999999999995559, et non de 0.7 avec précision. Le résultat de la division devient également inexact à 0.06999999999999999, au lieu de 0.07 comme prévu.
Pour résoudre ce problème, on peut envisager d'utiliser des nombres à virgule fixe. Dans le protocole NEAR, on utilise couramment 10^24 yoctoNEAR pour représenter 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, "");
}
Cela permet d'obtenir un résultat de calcul précis : 0,7 NEAR / 10 = 0,07 NEAR.
2. Problème de précision des calculs d'entiers en Rust
L'utilisation des opérations entières peut résoudre le problème de perte de précision des opérations en virgule flottante dans certains scénarios, mais les résultats des calculs entiers ne sont pas non plus entièrement précis et fiables.
2.1 ordre des opérations
Pour les multiplications et les divisions 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 les calculs entiers. Par exemple:
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,"");
}
Le résultat de l'exécution montre que result_0 et result_1 ne sont pas égaux. La raison en est que la division entière abandonne la précision inférieure au diviseur. Lors du calcul de result_1, (a / b) perd d'abord la précision et devient 0 ; tandis que lors du calcul de result_0, a * c est calculé d'abord pour éviter la perte de précision.
2.2 trop petit en magnitude
Lorsqu'il s'agit de calculs décimaux, les opérations sur les entiers peuvent entraîner une perte de précision :
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, "");
}
Les résultats montrent result_0=12, result_1=13, alors que la valeur attendue devrait être 13.3333....
3. Comment écrire des smart contracts Rust pour l'évaluation actuarielle des valeurs numériques
Pour améliorer la précision, les mesures de protection suivantes peuvent être prises :
3.1 Ajustement de l'ordre des opérations
Faire en sorte que la multiplication des entiers ait la priorité sur la division des entiers.
3.2 augmenter l'ordre de grandeur des entiers
Utiliser une plus grande échelle pour créer de plus grandes molécules. Par exemple, représenter 5.123 NEAR peut être exprimé comme 5.123 * 10^10 = 51_230_000_000 pour des calculs ultérieurs.
3.3 perte de précision d'accumulation des calculs
Pour les problèmes de précision inévitables, il est possible d'enregistrer la perte de précision cumulée des calculs. Par exemple :
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(to_yocto)"10"(, offset(;
println!)"Offset {}\n", offset);
}
}
Cette méthode permet d'accumuler les tokens restants à chaque distribution et de les distribuer tous ensemble lors de la prochaine distribution, afin d'atteindre finalement l'objectif d'une distribution suffisante.
( 3.4 Utiliser 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 des smart contracts, le problème d'arrondi est généralement basé sur le principe "Je veux profiter, personne ne doit me tondre". Selon la situation, on choisit d'arrondir vers le bas ou vers le haut, très rarement on utilise 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.
10 J'aime
Récompense
10
8
Partager
Commentaire
0/400
rugpull_survivor
· 07-14 15:34
Beaucoup de gens sont tombés dans le piège des arrondis.
Voir l'originalRépondre0
WhaleWatcher
· 07-14 07:46
Le contrôle de la précision est crucial.
Voir l'originalRépondre0
consensus_failure
· 07-13 12:52
Utiliser une précision fixe est plus sûr
Voir l'originalRépondre0
Ser_Liquidated
· 07-12 19:41
S’il y a un autre problème de précision, la position sera explosée
Voir l'originalRépondre0
DecentralizeMe
· 07-11 16:13
Remplacez par des nombres à virgule fixe.
Voir l'originalRépondre0
BoredWatcher
· 07-11 15:58
Le code est un peu compliqué.
Voir l'originalRépondre0
BrokenDAO
· 07-11 15:53
Puissance de calcul totale en surcharge, c'est problématique.
Développement de contrats intelligents en Rust : surmonter le problème de la précision des calculs numériques
Problèmes de précision des calculs numériques dans le développement de smart contracts en Rust
1. Problème de précision des 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 l'écriture de smart contracts, il n'est pas recommandé d'utiliser des opérations sur les nombres à virgule flottante, en particulier lors du traitement de ratios ou de taux d'intérêt liés à des décisions économiques/financières importantes.
Le type flottant à double précision f64 dans le langage Rust suit la norme IEEE 754 et utilise la notation scientifique avec une base de 2. Certains décimales ne peuvent pas être représentées avec précision par un nombre flottant de longueur finie, ce qui entraîne un phénomène de "rond".
Par exemple, lors de la distribution de 0,7 NEAR à 10 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, ""); }
Le résultat affiché montre que la valeur de amount est de 0.69999999999999995559, et non de 0.7 avec précision. Le résultat de la division devient également inexact à 0.06999999999999999, au lieu de 0.07 comme prévu.
Pour résoudre ce problème, on peut envisager d'utiliser des nombres à virgule fixe. Dans le protocole NEAR, on utilise couramment 10^24 yoctoNEAR pour représenter 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, ""); }
Cela permet d'obtenir un résultat de calcul précis : 0,7 NEAR / 10 = 0,07 NEAR.
2. Problème de précision des calculs d'entiers en Rust
L'utilisation des opérations entières peut résoudre le problème de perte de précision des opérations en virgule flottante dans certains scénarios, mais les résultats des calculs entiers ne sont pas non plus entièrement précis et fiables.
2.1 ordre des opérations
Pour les multiplications et les divisions 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 les calculs entiers. Par exemple:
rouille #[test] fn precision_test_div_before_mul() { let a: u128 = 1_0000; let b: u128 = 10_0000; let c: u128 = 20;
}
Le résultat de l'exécution montre que result_0 et result_1 ne sont pas égaux. La raison en est que la division entière abandonne la précision inférieure au diviseur. Lors du calcul de result_1, (a / b) perd d'abord la précision et devient 0 ; tandis que lors du calcul de result_0, a * c est calculé d'abord pour éviter la perte de précision.
2.2 trop petit en magnitude
Lorsqu'il s'agit de calculs décimaux, les opérations sur les entiers peuvent entraîner une perte de précision :
rouille #[test] fn precision_test_decimals() { let a: u128 = 10; let b: u128 = 3; let c: u128 = 4; let decimal: u128 = 100_0000;
}
Les résultats montrent result_0=12, result_1=13, alors que la valeur attendue devrait être 13.3333....
3. Comment écrire des smart contracts Rust pour l'évaluation actuarielle des valeurs numériques
Pour améliorer la précision, les mesures de protection suivantes peuvent être prises :
3.1 Ajustement de l'ordre des opérations
Faire en sorte que la multiplication des entiers ait la priorité sur la division des entiers.
3.2 augmenter l'ordre de grandeur des entiers
Utiliser une plus grande échelle pour créer de plus grandes molécules. Par exemple, représenter 5.123 NEAR peut être exprimé comme 5.123 * 10^10 = 51_230_000_000 pour des calculs ultérieurs.
3.3 perte de précision d'accumulation des calculs
Pour les problèmes de précision inévitables, il est possible d'enregistrer la perte de précision cumulée des calculs. Par exemple :
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(to_yocto)"10"(, offset(; println!)"Offset {}\n", offset); } }
Cette méthode permet d'accumuler les tokens restants à chaque distribution et de les distribuer tous ensemble lors de la prochaine distribution, afin d'atteindre finalement l'objectif d'une distribution suffisante.
( 3.4 Utiliser 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 des smart contracts, le problème d'arrondi est généralement basé sur le principe "Je veux profiter, personne ne doit me tondre". Selon la situation, on choisit d'arrondir vers le bas ou vers le haut, très rarement on utilise l'arrondi classique.
![]###https://img-cdn.gateio.im/webp-social/moments-6e8b4081214a69423fc7ae022d05c728.webp###