Journal de développement des smart contracts Rust (7) : Problème de précision dans le calcul des entiers
Récapitulatif des éditions précédentes :
Journal de développement des smart contracts Rust (1) Définition des données d'état du contrat et mise en œuvre des méthodes
Journal de développement des smart contracts Rust ( Écriture de tests unitaires pour les smart contracts Rust
Journal de développement des smart contracts Rust )3( Déploiement des smart contracts Rust, appel de fonctions et utilisation de l'Explorer
Journal de développement de smart contracts Rust )4( Débordement d'entiers dans les smart contracts Rust
Journal de développement de contrats intelligents Rust )5( attaque de réentrance
Journal de développement des smart contracts Rust )6( attaque par déni de service
1. Problèmes de précision des calculs à virgule flottante
Contrairement à Solidity, Rust prend en charge nativement les opérations à virgule flottante. Cependant, les opérations à virgule flottante présentent des problèmes de précision inévitables, il n'est donc pas recommandé de les utiliser dans des smart contracts, en particulier lors du traitement de ratios ou de taux d'intérêt liés à des décisions économiques/financières importantes.
Rust suit la norme IEEE 754 pour représenter les nombres à virgule flottante. Le type à virgule flottante double précision f64 est représenté en interne dans l'ordinateur par la notation scientifique binaire.
Certaines décimales peuvent être représentées avec une précision binaire à un nombre fini de chiffres, par exemple 0.8125 peut être représenté par 0.1101. Cependant, une décimale comme 0.7 produira une représentation binaire en boucle infinie, ce qui rend impossible une représentation précise avec un nombre fini de chiffres flottants, entraînant un problème d'"arrondi".
Dans l'exemple de distribution de 0,7 NEAR tokens à 10 utilisateurs sur la blockchain NEAR :
rouille
#)
fn precision_test_float[test]( {
let amount: f64 = 0.7;
let divisor: f64 = 10.0;
let result_0 = amount / divisor;
println!)"La valeur du montant : {:.20}", amount(;
assert_eq!)result_0, 0.07(;
}
Le résultat montre que la valeur réelle de amount est 0.69999999999999995559, result_0 est 0.06999999999999999, ce qui n'est pas égal à l'attendu de 0.07.
Pour résoudre ce problème, on peut utiliser des nombres à virgule fixe. Dans NEAR, 1 jeton NEAR est généralement représenté par 10^24 yoctoNEAR. Code modifié :
rouille
#)
fn precision_test_integer[test]( {
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 précis : 0,7 NEAR / 10 = 0,07 NEAR.
2. Problème de précision des calculs entiers en Rust
Bien que les opérations sur les entiers puissent résoudre des problèmes de précision des nombres à virgule flottante dans certains cas, les calculs sur les entiers présentent également des problèmes de précision.
) 2.1 ordre des opérations
Les opérations de multiplication et de division du même niveau, le changement d'ordre peut influencer le résultat :
rouille
fn precision_test_div_before_mul[test]( {
let a: u128 = 1_0000;
let b: u128 = 10_0000;
let c: u128 = 20;
let result_0 = a.checked_mul)c(.unwrap)(.checked_div)b(.unwrap)(;
let result_1 = a.checked_div)b(.unwrap)(.checked_mul)c(.unwrap)(;
assert_eq!)result_0,result_1(;
}
Les résultats montrent result_0 = 2, result_1 = 0.
La raison 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 result_0 calcule d'abord )a * c(, évitant ainsi la perte de précision.
) 2.2 trop petit en magnitude
rouille
fn precision_test_decimals[test]( {
let a: u128 = 10;
let b: u128 = 3;
let c: u128 = 4;
let decimal: u128 = 100_0000;
let result_0 = a.checked_div)b(.unwrap)(.checked_mul)c(.unwrap)(;
let result_1 = a.checked_mul)decimal(.unwrap)(
.checked_div)b(.unwrap)(
.checked_mul)c(.unwrap)(
.checked_div)decimal(.unwrap)(;
println!)"{}:{}", result_0, result_1(;
assert_eq!)result_0, result_1(;
}
Les résultats montrent que result_0 = 12, result_1 = 13, ce dernier étant plus proche de la valeur réelle 13.3333.
) 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 haute précision et sans erreur d'arrondi.
( 3.5 Considérer le mécanisme d'arrondi
Dans la conception des smart contracts, l'arrondi suit généralement le principe "à mon avantage" : si le plancher est à mon avantage, on arrondit vers le bas, si le plafond est à mon avantage, on arrondit vers le haut, et l'arrondi au plus proche est rarement utilisé.
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.
17 J'aime
Récompense
17
8
Partager
Commentaire
0/400
LostBetweenChains
· 07-25 17:53
Je ne peux même pas gérer les calculs entiers, qu'est-ce que je vais faire avec des contrats ?
Voir l'originalRépondre0
WagmiWarrior
· 07-25 11:45
Je suis arrivé au 7ème article, c'est détaillé.
Voir l'originalRépondre0
MetaMisfit
· 07-25 01:13
Rust cette smart contracts a trop de pièges, non ?
Voir l'originalRépondre0
DuckFluff
· 07-22 22:44
Eh bien, les nombres à virgule flottante causent encore des problèmes~
Voir l'originalRépondre0
MaticHoleFiller
· 07-22 22:44
Venez témoigner des pièges de la précision !
Voir l'originalRépondre0
MetadataExplorer
· 07-22 22:44
Les entiers sont souvent négligés, mais ils sont assez importants.
Voir l'originalRépondre0
MEVHunter
· 07-22 22:29
la précision est un pot de miel mev... garde tes floats serrés ou tu vas te faire rekt ser
Voir l'originalRépondre0
TrustlessMaximalist
· 07-22 22:16
Il faut éviter ce piège des opérations à virgule flottante.
Problèmes de précision des calculs entiers dans les smart contracts Rust et solutions
Journal de développement des smart contracts Rust (7) : Problème de précision dans le calcul des entiers
Récapitulatif des éditions précédentes :
1. Problèmes de précision des calculs à virgule flottante
Contrairement à Solidity, Rust prend en charge nativement les opérations à virgule flottante. Cependant, les opérations à virgule flottante présentent des problèmes de précision inévitables, il n'est donc pas recommandé de les utiliser dans des smart contracts, en particulier lors du traitement de ratios ou de taux d'intérêt liés à des décisions économiques/financières importantes.
Rust suit la norme IEEE 754 pour représenter les nombres à virgule flottante. Le type à virgule flottante double précision f64 est représenté en interne dans l'ordinateur par la notation scientifique binaire.
Certaines décimales peuvent être représentées avec une précision binaire à un nombre fini de chiffres, par exemple 0.8125 peut être représenté par 0.1101. Cependant, une décimale comme 0.7 produira une représentation binaire en boucle infinie, ce qui rend impossible une représentation précise avec un nombre fini de chiffres flottants, entraînant un problème d'"arrondi".
Dans l'exemple de distribution de 0,7 NEAR tokens à 10 utilisateurs sur la blockchain NEAR :
rouille #) fn precision_test_float[test]( { let amount: f64 = 0.7;
let divisor: f64 = 10.0;
let result_0 = amount / divisor;
println!)"La valeur du montant : {:.20}", amount(; assert_eq!)result_0, 0.07(; }
Le résultat montre que la valeur réelle de amount est 0.69999999999999995559, result_0 est 0.06999999999999999, ce qui n'est pas égal à l'attendu de 0.07.
Pour résoudre ce problème, on peut utiliser des nombres à virgule fixe. Dans NEAR, 1 jeton NEAR est généralement représenté par 10^24 yoctoNEAR. Code modifié :
rouille
#) fn precision_test_integer[test]( { 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 précis : 0,7 NEAR / 10 = 0,07 NEAR.
2. Problème de précision des calculs entiers en Rust
Bien que les opérations sur les entiers puissent résoudre des problèmes de précision des nombres à virgule flottante dans certains cas, les calculs sur les entiers présentent également des problèmes de précision.
) 2.1 ordre des opérations
Les opérations de multiplication et de division du même niveau, le changement d'ordre peut influencer le résultat :
rouille
fn precision_test_div_before_mul[test]( { let a: u128 = 1_0000; let b: u128 = 10_0000; let c: u128 = 20;
}
Les résultats montrent result_0 = 2, result_1 = 0.
La raison 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 result_0 calcule d'abord )a * c(, évitant ainsi la perte de précision.
) 2.2 trop petit en magnitude
rouille
fn precision_test_decimals[test]( { let a: u128 = 10; let b: u128 = 3; let c: u128 = 4; let decimal: u128 = 100_0000;
}
Les résultats montrent que result_0 = 12, result_1 = 13, ce dernier étant plus proche de la valeur réelle 13.3333.
![])https://img-cdn.gateio.im/webp-social/moments-7bdd27c1211e1cc345bf262666a993da.webp(
3. Comment écrire un smart contracts Rust pour l'évaluation numérique
Pour améliorer la précision, les mesures suivantes peuvent être prises :
) 3.1 Ajustement de l'ordre des opérations
Faire en sorte que la multiplication entière ait la priorité sur la division.
3.2 augmenter l'ordre de grandeur des entiers
Utilisez une plus grande échelle pour créer de plus grosses molécules. Par exemple, définissez 1 NEAR = 10^24 yoctoNEAR.
3.3 perte de précision des calculs cumulés
Enregistrer et accumuler la perte de précision, compenser lors des calculs ultérieurs :
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() { let mut offset: u128 = 0; pour i dans 1..7 { offset = distribute[test]10_000_000_000_000_000_000_000_000, offset(; } }
![])https://img-cdn.gateio.im/webp-social/moments-1933a4a2dd723a847f0059d31d1780d1.webp(
) 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 haute précision et sans erreur d'arrondi.
( 3.5 Considérer le mécanisme d'arrondi
Dans la conception des smart contracts, l'arrondi suit généralement le principe "à mon avantage" : si le plancher est à mon avantage, on arrondit vers le bas, si le plafond est à mon avantage, on arrondit vers le haut, et l'arrondi au plus proche est rarement utilisé.
![])https://img-cdn.gateio.im/webp-social/moments-6e8b4081214a69423fc7ae022d05c728.webp###