result_0 = (a / b) * c
let result_0 = a.checked_div(b).expect("ERR_DIV").checked_ mul(c).expect("ERR_MUL");
// result_1 = (a * デシマル / b) * c / デシマル;
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");
Rustスマートコントラクト開発:数値精算と精度制御のテクニック
Rustスマートコントラクト養成日記(7):数値精算
1. 浮動小数点演算の精度問題
Rust言語は浮動小数点演算をネイティブにサポートしていますが、浮動小数点演算には避けられない計算精度の問題があります。スマートコントラクトを作成する際には、特に重要な経済/金融の決定に関わる比率や金利を扱う場合には、浮動小数点演算の使用は推奨されません。
Rust言語の倍精度浮動小数点型f64はIEEE 754標準に従い、底数2の科学的表記法を採用しています。0.7のような特定の小数は有限の桁数の浮動小数点数で正確に表現することができず、"丸め"現象が存在します。
NEARブロックチェーン上で10人のユーザーに0.7NEARトークンを配布するテストで、浮動小数点演算の結果が正確ではありません:
さび 量を仮定します:f64 = 0.7;
除数をしましょう:f64 = 10.0; let result_0 = amount / divisor;
amountの値は0.69999999999999995559で、result_0の値は0.06999999999999999であり、予想の0.07ではありません。
この問題を解決するには、固定小数点を使用できます。 NEARプロトコルでは、通常、1 NEAR = 10^24 yoctoNEARが使用されます。
さび N: u128 = 1_000_000_000_000_000_000_000_000_000_000_000; 量を仮定します: U128 = 700_000_000_000_000_000_000_000_000; 除数をしましょう:u128 = 10; let result_0 = amount / divisor;
これにより正確な計算結果が得られます: 0.7 NEAR / 10 = 0.07 NEAR。
!
2. Rustの整数計算の精度に関する問題
2.1 操作の順序
同じ算数の優先順位での乗算と除算では、その前後の順序の変更が計算結果に直接影響を与える可能性があります。例えば:
錆 Aを仮定します:U128 = 1_0000; Bを仮定します:U128 = 10_0000; 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");
result_0とresult_1の計算結果が異なるのは、整数除算の場合、除数未満の精度が切り捨てられるためです。
2.2 小さすぎる数量
小数値に関する場合、整数演算は精度の損失を引き起こす可能性があります:
錆 Aを仮定します:U128 = 10; Bを仮定します:u128 = 3; C:u128 = 4とします。 小数で仮定します: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 * デシマル / b) * c / デシマル;
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");
result_0計算とresult_1計算が異なり、result_1が実際の期待値に近くなります。
!
3. 数値精算のRustスマートコントラクトの書き方
3.1 操作の順序を調整する
整数の乗算を整数の除算よりも優先させる。
3.2 整数の数量級を増加させる
より大きな数量級を使用して、より大きな分子を作成します。たとえば、5.123 NEARを51_230_000_000 yoctoNEARとして表します。
3.3 運用精度の累積損失
累積した演算精度の損失を記録し、以降の演算で補償します。例えば:
さび fn distribute(amount: u128, オフセット: u128) -> u128 { token_to_distribute = オフセット + 金額とします。 per_user_share = token_to_distribute / USER_NUMとします。 recorded_offset = token_to_distribute - per_user_share * USER_NUM; recorded_offset }
3.4 では、Rust Crate ライブラリ rust-decimal を使用します
このライブラリは、正確な精度計算と丸め誤差のない小数の金融計算を必要とする場合に適しています。
3.5 四捨五入のメカニズムを考慮する
スマートコントラクトの設計において、丸めの問題は通常「私が得をするために、他人が私の利益を得てはいけない」という原則に従います。状況に応じて切り捨て、切り上げ、または四捨五入を選択します。
!