# Rustスマートコントラクト養成日記(7)数値精算## 1. 浮動小数点演算の精度問題Rust言語は浮動小数点演算をネイティブにサポートしていますが、浮動小数点演算には避けられない計算精度の問題があります。スマートコントラクトを作成する際には、特に重要な経済/金融の意思決定に関わる比率や金利を扱う場合、浮動小数点演算の使用は推奨されません。Rust言語の浮動小数点数はIEEE 754標準に従います。倍精度浮動小数点型f64の例として、その内部のバイナリ表現は以下の通りです:! [](https://img-cdn.gateio.im/social/moments-7bdd27c1211e1cc345bf262666a993da)浮動小数点数は、底が2の科学的表記法で表されます。例えば、0.8125は有限の桁数の2進数0.1101で表すことができます:0.8125 = 0.5 * 1 + 0.25 * 1 + 0.125 * 0 + 0.0625 * 1しかし、0.7のような小数については、無限ループの状況が発生する可能性があります。0.7 = 0.1011001100110011...これにより、有限の桁数の浮動小数点数を正確に表現することができず、"丸め"現象が存在します。NEARブロックチェーン上で10人のユーザーに0.7NEARトークンを配布する例:さび#[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, "");}結果:テストを1回実行する金額の値:0.699999999999999955559スレッド 'tests::precision_test_float' がパニックを起こしました 'アサーションに失敗しました: (left == right) 左: 0.0699999999999999999999, 右: 0.07: ', src/lib.rs:185:9amountの値は正確な0.7ではなく、近似値の0.69999999999999995559であることがわかります。さらに除算の結果も不正確な0.06999999999999999になります。この問題を解決するために、定点数の使用を検討できます。NEAR Protocolでは、通常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, "");}結果:テストを1回実行中テストテスト::p recision_test_integer ... わかりましたテスト結果: ok. 1 合格; 0 失敗; 0 無視; 0 測定; 8 フィルタリングされました; 0.00 秒で終了## 2. Rustの整数計算の精度に関する問題### 2.1 操作の順序同じ算数の優先順位における乗算と除算では、その前後の順序の変化が計算結果に直接影響を与え、整数計算の精度の問題を引き起こす可能性があります。さび#[test]fn precision_test_div_before_mul() { Aを仮定します:U128 = 1_0000; Bを仮定します:U128 = 10_0000; Cを仮定します:U128 = 20; result_0 = a とします .checked_mul(c) .expect("ERR_MUL") .checked_div(b) .expect("ERR_DIV"); result_1 = a とします .checked_div(b) .expect("ERR_DIV") .checked_mul(c) .expect("ERR_MUL"); assert_eq!(result_0,result_1,");}結果:テストを1回実行中スレッド 'tests::precision_test_0' がパニックを起こしました 'アサーションが失敗しました: (left == right) 左:2、右:0:'、src / lib.rs:175:9result_0 = a * c / b と result_1 = (a / b) の計算式は同じですが、結果は異なります。理由は整数の除算が除数より小さい精度を捨てるからです。result_1を計算する際、先に (a / b) を計算すると精度が失われて0になります。一方、result_0を計算する際、最初に a * c = 20_0000 を計算することで除数 b より大きくなり、精度の損失を避けました。### 2.2 小さすぎる数量級さび#[test]fn precision_test_decimals() { Aを仮定します:U128 = 10; Bを仮定します:u128 = 3; C:u128 = 4とします。 小数で仮定します:u128 = 100_0000; result_0 = a とします .checked_div(b) .expect("ERR_DIV") .checked_mul(c) .expect("ERR_MUL"); 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, "");}結果:テストを1回実行中12:13スレッド 'tests::precision_test_decimals' がパニックを起こしました 'アサーションに失敗しました: (left == right) 左:12、右:13:'、src/lib.rs:214:9可視演算プロセスが等価であるresult_0とresult_1の結果は異なり、result_1 = 13は実際の期待値13.3333....により近いです。## 3. 数値精算のためのRustスマートコントラクトの書き方### 3.1 操作の順序を調整する- 整数の乗法を整数の除法よりも優先する。### 3.2 整数の数量級を増加させる- 整数はより大きな数量級を使用し、より大きな分子を作成します。### 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; 記録されたオフセット}#[test]fn record_offset_test() { mutオフセットをしましょう:u128 = 0; for i in 1..7 { println!("ラウンド {}", i); オフセット = distribute(10_000_000_000_000_000_000_000_000, offset); println!("オフセット {}\n", offset); }}結果:テストを1回実行するラウンド1per_user_share 3333333333333333333333333オフセット 1ラウンド2per_user_share 3333333333333333333333333オフセット2ラウンド3per_user_share 400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000オフセット0ラウンド4per_user_share 3333333333333333333333333オフセット 1ラウンド5per_user_share 3333333333333333333333333オフセット2ラウンド6per_user_share 400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000オフセット 0テスト::record_offset_test ... わかりましたテスト結果: ok. 1 合格; 0 不合格; 0 無視; 0 測定; 9 フィルタリング; 0.00秒で終了### 3.4 では、Rust Crate ライブラリ rust-decimal を使用しますこのライブラリは、精度の高い計算と丸め誤差のない小数の金融計算が必要な場合に適しています。### 3.5 丸め機構を考慮するスマートコントラクトを設計する際、丸めの問題は通常「私は得をし、他人は私の羊毛を刈らせない」という原則に従います。この原則に基づいて、切り捨てが私に有利であれば切り捨て、切り上げが私に有利であれば切り上げます。四捨五入は誰に有利かを判断できないため、ほとんど採用されません。! [](https://img-cdn.gateio.im/social/moments-1933a4a2dd723a847f0059d31d1780d1)! [](https://img-cdn.gateio.im/social/moments-6e8b4081214a69423fc7ae022d05c728)
Rustスマートコントラクト数値保険数理科学:浮動小数点トラップと整数精度最適化
Rustスマートコントラクト養成日記(7)数値精算
1. 浮動小数点演算の精度問題
Rust言語は浮動小数点演算をネイティブにサポートしていますが、浮動小数点演算には避けられない計算精度の問題があります。スマートコントラクトを作成する際には、特に重要な経済/金融の意思決定に関わる比率や金利を扱う場合、浮動小数点演算の使用は推奨されません。
Rust言語の浮動小数点数はIEEE 754標準に従います。倍精度浮動小数点型f64の例として、その内部のバイナリ表現は以下の通りです:
!
浮動小数点数は、底が2の科学的表記法で表されます。例えば、0.8125は有限の桁数の2進数0.1101で表すことができます:
0.8125 = 0.5 * 1 + 0.25 * 1 + 0.125 * 0 + 0.0625 * 1
しかし、0.7のような小数については、無限ループの状況が発生する可能性があります。
0.7 = 0.1011001100110011...
これにより、有限の桁数の浮動小数点数を正確に表現することができず、"丸め"現象が存在します。
NEARブロックチェーン上で10人のユーザーに0.7NEARトークンを配布する例:
さび #[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, ""); }
結果:
テストを1回実行する 金額の値:0.699999999999999955559 スレッド 'tests::precision_test_float' がパニックを起こしました 'アサーションに失敗しました: (left == right) 左: 0.0699999999999999999999, 右: 0.07: ', src/lib.rs:185:9
amountの値は正確な0.7ではなく、近似値の0.69999999999999995559であることがわかります。さらに除算の結果も不正確な0.06999999999999999になります。
この問題を解決するために、定点数の使用を検討できます。NEAR Protocolでは、通常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, ""); }
結果:
テストを1回実行中 テストテスト::p recision_test_integer ... わかりました テスト結果: ok. 1 合格; 0 失敗; 0 無視; 0 測定; 8 フィルタリングされました; 0.00 秒で終了
2. Rustの整数計算の精度に関する問題
2.1 操作の順序
同じ算数の優先順位における乗算と除算では、その前後の順序の変化が計算結果に直接影響を与え、整数計算の精度の問題を引き起こす可能性があります。
さび #[test] fn precision_test_div_before_mul() { Aを仮定します:U128 = 1_0000; Bを仮定します:U128 = 10_0000; Cを仮定します:U128 = 20;
}
結果:
テストを1回実行中 スレッド 'tests::precision_test_0' がパニックを起こしました 'アサーションが失敗しました: (left == right) 左:2、右:0:'、src / lib.rs:175:9
result_0 = a * c / b と result_1 = (a / b) の計算式は同じですが、結果は異なります。理由は整数の除算が除数より小さい精度を捨てるからです。result_1を計算する際、先に (a / b) を計算すると精度が失われて0になります。一方、result_0を計算する際、最初に a * c = 20_0000 を計算することで除数 b より大きくなり、精度の損失を避けました。
2.2 小さすぎる数量級
さび #[test] fn precision_test_decimals() { Aを仮定します:U128 = 10; Bを仮定します:u128 = 3; C:u128 = 4とします。 小数で仮定します:u128 = 100_0000;
}
結果:
テストを1回実行中 12:13 スレッド 'tests::precision_test_decimals' がパニックを起こしました 'アサーションに失敗しました: (left == right) 左:12、右:13:'、src/lib.rs:214:9
可視演算プロセスが等価であるresult_0とresult_1の結果は異なり、result_1 = 13は実際の期待値13.3333....により近いです。
3. 数値精算のためのRustスマートコントラクトの書き方
3.1 操作の順序を調整する
3.2 整数の数量級を増加させる
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; 記録されたオフセット }
#[test] fn record_offset_test() { mutオフセットをしましょう:u128 = 0; for i in 1..7 { println!("ラウンド {}", i); オフセット = distribute(10_000_000_000_000_000_000_000_000, offset); println!("オフセット {}\n", offset); } }
結果:
テストを1回実行する ラウンド1 per_user_share 3333333333333333333333333 オフセット 1
ラウンド2 per_user_share 3333333333333333333333333 オフセット2
ラウンド3 per_user_share 400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 オフセット0
ラウンド4 per_user_share 3333333333333333333333333 オフセット 1
ラウンド5 per_user_share 3333333333333333333333333 オフセット2
ラウンド6 per_user_share 400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 オフセット 0
テスト::record_offset_test ... わかりました テスト結果: ok. 1 合格; 0 不合格; 0 無視; 0 測定; 9 フィルタリング; 0.00秒で終了
3.4 では、Rust Crate ライブラリ rust-decimal を使用します
このライブラリは、精度の高い計算と丸め誤差のない小数の金融計算が必要な場合に適しています。
3.5 丸め機構を考慮する
スマートコントラクトを設計する際、丸めの問題は通常「私は得をし、他人は私の羊毛を刈らせない」という原則に従います。この原則に基づいて、切り捨てが私に有利であれば切り捨て、切り上げが私に有利であれば切り上げます。四捨五入は誰に有利かを判断できないため、ほとんど採用されません。
!
!