# Rustスマートコントラクト養成日記(7):数値精算この記事では、Rustスマートコントラクトプログラミングにおける数値精算問題について探討します。主に浮動小数点演算の精度問題、整数計算の精度問題、そして数値精算のRustスマートコントラクトの書き方などの内容を含みます。## 1. 浮動小数点演算の精度問題Rust言語は浮動小数点演算をネイティブにサポートしていますが、浮動小数点演算には避けられない計算精度の問題があります。スマートコントラクトを作成する際には、特に重要な経済/金融の決定に関わる比率や金利を扱う場合は、浮動小数点演算の使用は推奨されません。Rust言語における倍精度浮動小数点型f64はIEEE 754標準に従い、基数2の科学的表記法を用いて表現されます。しかし、(のような特定の小数(例:0.7))は、有限のビット長の浮動小数点数で正確に表現できず、「丸め」現象が存在します。NEARブロックチェーン上で0.7NEARトークンを10人のユーザーに配布する例では、実際の計算結果に不正確な状況が生じることがあります:さび#[test]fn precision_test_float() { 量を仮定します:f64 = 0.7; 除数をしましょう:f64 = 10.0; let result_0 = amount / divisor; println!("量の値: {:.20}", amount); assert_eq!(result_0, 0.07, "");}実行結果は、amountの値が正確な0.7ではなく、非常に近似した値0.69999999999999995559であることを示しています。さらに、除算の結果も期待される0.07ではなく、不正確な0.06999999999999999に変わります。この問題を解決するために、固定小数点数の使用を検討できます。NEAR Protocolでは、通常10^24を分母として採用し、すなわち10^24個のyoctoNEARが1個のNEARトークンに相当します。修正されたテストコードは以下の通りです:さび#[test]fn precision_test_integer() { 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; assert_eq!(result_0, 70_000_000_000_000_000_000_000_000, "");}これにより、正確な計算結果が得られます: 0.7 NEAR / 10 = 0.07 NEAR。! [](https://img-cdn.gateio.im/social/moments-7bdd27c1211e1cc345bf262666a993da)## 2. Rustの整数計算の精度に関する問題整数演算を使用することで、特定のシナリオでの浮動小数点数の精度損失の問題を解決できますが、整数計算の結果も完全に正確で信頼できるわけではありません。整数計算の精度に影響を与えるいくつかの理由には、### 2.1 操作の順序同じ算数の優先順位の乗算と除算では、その前後の順序の変化が計算結果に直接影響を与える可能性があります。例えば:さび#[test]fn precision_test_div_before_mul() { Aを仮定します:U128 = 1_0000; Bを仮定します:U128 = 10_0000; 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,");}テスト結果は、result_0とresult_1の計算結果が異なることを示しています。これは、整数の除算では、除数より小さい精度が捨てられるためです。result_1を計算する際、(a / b)は最初に計算精度を失い、0になります。一方、result_0を計算する際は、a * cを先に計算することで精度の損失を避けることができます。### 2.2 小さすぎる数量小さな数量の計算に関しては、精度の問題が発生する可能性もあります。さび#[test]fn precision_test_decimals() { Aを仮定します:U128 = 10; Bを仮定します:u128 = 3; C:u128 = 4とします。 小数で仮定します: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, "");}テスト結果は、result_0とresult_1の計算結果が異なり、result_1 = 13が実際の期待計算値13.3333....により近いことを示しています。! [](https://img-cdn.gateio.im/social/moments-1933a4a2dd723a847f0059d31d1780d1)## 3. 数値精算のためのRustスマートコントラクトの書き方精度を高めるために、以下の防護手段を講じることができます:### 3.1 操作の順序を調整する整数の乗法を整数の除法よりも優先させる。### 3.2 整数の数量級を増やすより大きな数量の単位を使用して、より大きな分子を作成します。例えば、5.123 NEARを5.123 * 10^10 = 51_230_000_000として表すことができます。### 3.3 運用精度の累積損失避けられない整数計算の精度問題については、累積した演算精度の損失を記録することを検討できます。例えば:さび定数 USER_NUM: u128 = 3;fn distribute(amount: u128, オフセット: u128) -> u128 { token_to_distribute = オフセット + 金額とします。 per_user_share = token_to_distribute / USER_NUMとします。 println!("per_user_share {}", per_user_share); recorded_offset = token_to_distribute - per_user_share * USER_NUM; recorded_offset}#[test]fn record_offset_test() { mutオフセットをしましょう:u128 = 0; for i in 1..7 { println!("ラウンド {}", i); オフセット = distribute(to_yocto("10"), offset); println!("オフセット {}\n", offset); }}この方法は、精度の損失により未配分のトークンを累積して再分配することができます。### 3.4 では、Rust Crate ライブラリ rust-decimal を使用しますこのライブラリは、有効精度計算と丸め誤差のない小数金融計算が必要な場合に適しています。### 3.5 丸めメカニズムを考慮するスマートコントラクトを設計する際、丸めの問題は通常「私は利益を得て、他人は私の羊毛を刈ってはいけない」という原則に従います。この原則に従い、契約にとって有利であれば切り下げ、契約にとって有利であれば切り上げます。四捨五入は誰にとって有利かが確定できないため、ほとんど採用されません。これらの方法を採用することで、Rustのスマートコントラクトにおいてより正確な数値計算を実現し、契約の信頼性と公平性を向上させることができます。! [](https://img-cdn.gateio.im/social/moments-6e8b4081214a69423fc7ae022d05c728)
Rustスマートコントラクトにおける数値精算のテクニックと注意事項
Rustスマートコントラクト養成日記(7):数値精算
この記事では、Rustスマートコントラクトプログラミングにおける数値精算問題について探討します。主に浮動小数点演算の精度問題、整数計算の精度問題、そして数値精算のRustスマートコントラクトの書き方などの内容を含みます。
1. 浮動小数点演算の精度問題
Rust言語は浮動小数点演算をネイティブにサポートしていますが、浮動小数点演算には避けられない計算精度の問題があります。スマートコントラクトを作成する際には、特に重要な経済/金融の決定に関わる比率や金利を扱う場合は、浮動小数点演算の使用は推奨されません。
Rust言語における倍精度浮動小数点型f64はIEEE 754標準に従い、基数2の科学的表記法を用いて表現されます。しかし、(のような特定の小数(例:0.7))は、有限のビット長の浮動小数点数で正確に表現できず、「丸め」現象が存在します。
NEARブロックチェーン上で0.7NEARトークンを10人のユーザーに配布する例では、実際の計算結果に不正確な状況が生じることがあります:
さび #[test] fn precision_test_float() { 量を仮定します:f64 = 0.7;
除数をしましょう:f64 = 10.0;
let result_0 = amount / divisor;
println!("量の値: {:.20}", amount); assert_eq!(result_0, 0.07, ""); }
実行結果は、amountの値が正確な0.7ではなく、非常に近似した値0.69999999999999995559であることを示しています。さらに、除算の結果も期待される0.07ではなく、不正確な0.06999999999999999に変わります。
この問題を解決するために、固定小数点数の使用を検討できます。NEAR Protocolでは、通常10^24を分母として採用し、すなわち10^24個のyoctoNEARが1個のNEARトークンに相当します。
修正されたテストコードは以下の通りです:
さび #[test] fn precision_test_integer() { 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; assert_eq!(result_0, 70_000_000_000_000_000_000_000_000, ""); }
これにより、正確な計算結果が得られます: 0.7 NEAR / 10 = 0.07 NEAR。
!
2. Rustの整数計算の精度に関する問題
整数演算を使用することで、特定のシナリオでの浮動小数点数の精度損失の問題を解決できますが、整数計算の結果も完全に正確で信頼できるわけではありません。整数計算の精度に影響を与えるいくつかの理由には、
2.1 操作の順序
同じ算数の優先順位の乗算と除算では、その前後の順序の変化が計算結果に直接影響を与える可能性があります。例えば:
さび #[test] fn precision_test_div_before_mul() { Aを仮定します:U128 = 1_0000; Bを仮定します:U128 = 10_0000; Cを仮定します:U128 = 20;
}
テスト結果は、result_0とresult_1の計算結果が異なることを示しています。これは、整数の除算では、除数より小さい精度が捨てられるためです。result_1を計算する際、(a / b)は最初に計算精度を失い、0になります。一方、result_0を計算する際は、a * cを先に計算することで精度の損失を避けることができます。
2.2 小さすぎる数量
小さな数量の計算に関しては、精度の問題が発生する可能性もあります。
さび #[test] fn precision_test_decimals() { Aを仮定します:U128 = 10; Bを仮定します:u128 = 3; C:u128 = 4とします。 小数で仮定します:u128 = 100_0000;
}
テスト結果は、result_0とresult_1の計算結果が異なり、result_1 = 13が実際の期待計算値13.3333....により近いことを示しています。
!
3. 数値精算のためのRustスマートコントラクトの書き方
精度を高めるために、以下の防護手段を講じることができます:
3.1 操作の順序を調整する
整数の乗法を整数の除法よりも優先させる。
3.2 整数の数量級を増やす
より大きな数量の単位を使用して、より大きな分子を作成します。例えば、5.123 NEARを5.123 * 10^10 = 51_230_000_000として表すことができます。
3.3 運用精度の累積損失
避けられない整数計算の精度問題については、累積した演算精度の損失を記録することを検討できます。例えば:
さび 定数 USER_NUM: u128 = 3;
fn distribute(amount: u128, オフセット: u128) -> u128 { token_to_distribute = オフセット + 金額とします。 per_user_share = token_to_distribute / USER_NUMとします。 println!("per_user_share {}", per_user_share); recorded_offset = token_to_distribute - per_user_share * USER_NUM; recorded_offset }
#[test] fn record_offset_test() { mutオフセットをしましょう:u128 = 0; for i in 1..7 { println!("ラウンド {}", i); オフセット = distribute(to_yocto("10"), offset); println!("オフセット {}\n", offset); } }
この方法は、精度の損失により未配分のトークンを累積して再分配することができます。
3.4 では、Rust Crate ライブラリ rust-decimal を使用します
このライブラリは、有効精度計算と丸め誤差のない小数金融計算が必要な場合に適しています。
3.5 丸めメカニズムを考慮する
スマートコントラクトを設計する際、丸めの問題は通常「私は利益を得て、他人は私の羊毛を刈ってはいけない」という原則に従います。この原則に従い、契約にとって有利であれば切り下げ、契約にとって有利であれば切り上げます。四捨五入は誰にとって有利かが確定できないため、ほとんど採用されません。
これらの方法を採用することで、Rustのスマートコントラクトにおいてより正確な数値計算を実現し、契約の信頼性と公平性を向上させることができます。
!