Journal de développement des smart contracts en Rust (7) Problèmes de précision dans les opérations sur les nombres à virgule flottante et les entiers
Cet article discutera des problèmes de précision des opérations sur les nombres à virgule flottante et les entiers dans les smart contracts Rust, ainsi que de la façon d'écrire des smart contracts pour les calculs numériques.
1. Problèmes de précision des 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 l'écriture 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 liés à des décisions économiques/financières importantes.
Dans le langage Rust, les nombres à virgule flottante adoptent la norme IEEE 754 et sont représentés en notation scientifique avec une base de 2. Certains décimaux (, comme 0.7), ne peuvent pas être représentés avec précision par des nombres à virgule flottante de longueur finie, ce qui entraîne un phénomène d'"arrondi".
Par exemple, lors de la distribution de 0,7 token 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;
assert_eq!(result_0, 0.07, "");
}
Le résultat de l'exécution montre que la valeur de amount n'est pas exactement 0.7, mais plutôt une valeur approximative de 0.69999999999999995559. Le résultat de la division n'est pas non plus précis, étant 0.06999999999999999 au lieu de l'attendu 0.07.
Pour résoudre ce problème, il est possible d'envisager l'utilisation de nombres à virgule fixe. Dans le protocole NEAR, on utilise généralement la notation 1 NEAR = 10^24 yoctoNEAR :
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, "");
}
Ainsi, on obtient le résultat du calcul actuariel: 0,7 NEAR / 10 = 0,07 NEAR.
2. Problème de précision du calcul des entiers en Rust
2.1 ordre des opérations
L'ordre de multiplication et de division ayant la même priorité arithmétique peut directement influencer le résultat du calcul:
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 sa précision et devient 0 ; tandis que lors du calcul de result_0, le calcul de a * c permet d'éviter la perte de précision.
2.2 trop petit ordre de grandeur
Une taille trop petite peut également entraîner des problèmes 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");
assert_eq!(result_0, result_1, "");
}
Les résultats montrent result_0=12, result_1=13, ce dernier étant plus proche de la valeur attendue de 13.3333.
3. Comment écrire des smart contracts Rust pour l'évaluation numérique
Pour améliorer la précision, les mesures de protection suivantes peuvent être prises :
3.1 Ajuster 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 des ordres de grandeur plus élevés pour créer des molécules plus grandes. Par exemple, représenter 5,123 NEAR comme 5,123 * 10^10 = 51_230_000_000.
3.3 perte de précision dans l'accumulation des calculs
Enregistrer la perte de précision des calculs cumulés :
rouille
const USER_NUM: u128 = 3;
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
}
#(
fn record_offset_test)[test] {
let mut offset: u128 = 0;
pour i dans 1..7 {
offset = distribute(to_yocto)"10"(, offset(;
}
}
Cela permet de conserver les tokens non distribués en attente, et de les distribuer lors de la prochaine distribution.
) 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 exacte et sans erreur d'arrondi.
) 3.5 Considérer le mécanisme d'arrondi
Lors de la conception de smart contracts, on adopte généralement le principe "Je veux en profiter, les autres ne doivent pas me tondre". Selon les circonstances, on choisit d'arrondir à l'entier inférieur ou supérieur, et on utilise très rarement l'arrondi traditionnel.
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.
12 J'aime
Récompense
12
8
Partager
Commentaire
0/400
SandwichHunter
· 07-16 18:07
le petit poulet rust est encore allongé par terre
Voir l'originalRépondre0
WhaleStalker
· 07-16 17:00
Les bugs de code sont vraiment mortels...
Voir l'originalRépondre0
GhostInTheChain
· 07-15 16:28
rust doit encore remplir des décimales... pas simple
Voir l'originalRépondre0
BoredWatcher
· 07-13 18:40
Une bibliothèque Rust peut-elle écrire des contrats ?
Voir l'originalRépondre0
GasFeeNightmare
· 07-13 18:35
La précision des calculs est aussi ennuyeuse que mes frais de gas...
Voir l'originalRépondre0
EyeOfTheTokenStorm
· 07-13 18:34
Le taux de perte de précision affecte directement le ratio de profit et de perte, qui utilise encore des nombres à virgule flottante pour la quantification ? Les parieurs ont dû faire un Rug Pull, non ?
Voir l'originalRépondre0
TokenEconomist
· 07-13 18:32
en fait, perte de précision = f(ordre_des_ops, facteur_d'échelle) ... rust-decimal lib pour la victoire
Voir l'originalRépondre0
just_another_wallet
· 07-13 18:25
Les nombres à virgule flottante sont vraiment problématiques...
Problèmes de précision des calculs numériques et solutions d'optimisation dans les smart contracts Rust
Journal de développement des smart contracts en Rust (7) Problèmes de précision dans les opérations sur les nombres à virgule flottante et les entiers
Cet article discutera des problèmes de précision des opérations sur les nombres à virgule flottante et les entiers dans les smart contracts Rust, ainsi que de la façon d'écrire des smart contracts pour les calculs numériques.
1. Problèmes de précision des 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 l'écriture 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 liés à des décisions économiques/financières importantes.
Dans le langage Rust, les nombres à virgule flottante adoptent la norme IEEE 754 et sont représentés en notation scientifique avec une base de 2. Certains décimaux (, comme 0.7), ne peuvent pas être représentés avec précision par des nombres à virgule flottante de longueur finie, ce qui entraîne un phénomène d'"arrondi".
Par exemple, lors de la distribution de 0,7 token 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;
assert_eq!(result_0, 0.07, ""); }
Le résultat de l'exécution montre que la valeur de amount n'est pas exactement 0.7, mais plutôt une valeur approximative de 0.69999999999999995559. Le résultat de la division n'est pas non plus précis, étant 0.06999999999999999 au lieu de l'attendu 0.07.
Pour résoudre ce problème, il est possible d'envisager l'utilisation de nombres à virgule fixe. Dans le protocole NEAR, on utilise généralement la notation 1 NEAR = 10^24 yoctoNEAR :
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, ""); }
Ainsi, on obtient le résultat du calcul actuariel: 0,7 NEAR / 10 = 0,07 NEAR.
2. Problème de précision du calcul des entiers en Rust
2.1 ordre des opérations
L'ordre de multiplication et de division ayant la même priorité arithmétique peut directement influencer le résultat du calcul:
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 sa précision et devient 0 ; tandis que lors du calcul de result_0, le calcul de a * c permet d'éviter la perte de précision.
2.2 trop petit ordre de grandeur
Une taille trop petite peut également entraîner des problèmes 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, ce dernier étant plus proche de la valeur attendue de 13.3333.
3. Comment écrire des smart contracts Rust pour l'évaluation numérique
Pour améliorer la précision, les mesures de protection suivantes peuvent être prises :
3.1 Ajuster 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 des ordres de grandeur plus élevés pour créer des molécules plus grandes. Par exemple, représenter 5,123 NEAR comme 5,123 * 10^10 = 51_230_000_000.
3.3 perte de précision dans l'accumulation des calculs
Enregistrer la perte de précision des calculs cumulés :
rouille const USER_NUM: u128 = 3;
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 }
#( fn record_offset_test)[test] { let mut offset: u128 = 0; pour i dans 1..7 { offset = distribute(to_yocto)"10"(, offset(; } }
Cela permet de conserver les tokens non distribués en attente, et de les distribuer lors de la prochaine distribution.
) 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 exacte et sans erreur d'arrondi.
) 3.5 Considérer le mécanisme d'arrondi
Lors de la conception de smart contracts, on adopte généralement le principe "Je veux en profiter, les autres ne doivent pas me tondre". Selon les circonstances, on choisit d'arrondir à l'entier inférieur ou supérieur, et on utilise très rarement l'arrondi traditionnel.
![]###https://img-cdn.gateio.im/webp-social/moments-6e8b4081214a69423fc7ae022d05c728.webp###