Rust智能合約中的數值精算技巧與注意事項

Rust智能合約養成日記(7):數值精算

本文將探討Rust智能合約編程中的數值精算問題,主要包括浮點數運算的精度問題、整數計算精度的問題,以及如何編寫數值精算的Rust智能合約等內容。

1. 浮點數運算的精度問題

Rust語言原生支持浮點數運算,但浮點數運算存在着無法避免的計算精度問題。在編寫智能合約時,不推薦使用浮點數運算,特別是在處理涉及重要經濟/金融決策的比率或利率時。

Rust語言中雙精度浮點類型f64遵循IEEE 754標準,採用底數爲2的科學計數法來表達。然而,某些小數(如0.7)無法用有限位長的浮點數來準確表示,會存在"舍入"現象。

以NEAR公鏈上分發0.7個NEAR代幣給十位用戶爲例,實際計算結果會出現不精確的情況:

rust #[test] fn precision_test_float() { let amount: f64 = 0.7;
let divisor: f64 = 10.0;
let result_0 = amount / divisor;
println!("The value of amount: {:.20}", amount); assert_eq!(result_0, 0.07, ""); }

執行結果顯示,amount的值並非準確的0.7,而是一個極爲近似的值0.69999999999999995559。進一步的除法運算結果也變爲不精確的0.06999999999999999,而非預期的0.07。

爲解決這個問題,可以考慮使用定點數。在NEAR Protocol中,通常採用10^24作爲分母,即10^24個yoctoNEAR等價於1個NEAR代幣。

修改後的測試代碼如下:

rust #[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, ""); }

這樣可以獲得精確的運算結果: 0.7 NEAR / 10 = 0.07 NEAR。

2. Rust整數計算精度的問題

雖然使用整數運算可以解決某些場景中的浮點數精度丟失問題,但整數計算的結果也並非完全準確可靠。影響整數計算精度的部分原因包括:

2.1 運算順序

同一算數優先級的乘法與除法,其前後順序的變化可能直接影響計算結果。例如:

rust #[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,"");

}

測試結果顯示,result_0和result_1的計算結果不同。這是因爲對於整數除法,小於除數的精度會被舍棄。在計算result_1時,(a / b)會率先失去計算精度,變爲0;而計算result_0時,先計算a * c可以避免精度丟失。

2.2 過小的數量級

當涉及較小數量級的計算時,也可能出現精度問題:

rust #[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");

println!("{}:{}", result_0, result_1);
assert_eq!(result_0, result_1, "");

}

測試結果顯示,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 積累運算精度的損失

對於無法避免的整數計算精度問題,可以考慮記錄累計的運算精度損失。例如:

rust const USER_NUM: u128 = 3;

fn distribute(amount: u128, offset: u128) -> u128 { let token_to_distribute = offset + amount; let per_user_share = token_to_distribute / USER_NUM; println!("per_user_share {}", per_user_share); let recorded_offset = token_to_distribute - per_user_share * USER_NUM; recorded_offset }

#[test] fn record_offset_test() { let mut offset: u128 = 0; for i in 1..7 { println!("Round {}", i); offset = distribute(to_yocto("10"), offset); println!("Offset {}\n", offset); } }

這種方法可以累積並重新分配因精度損失而未分發的代幣。

3.4 使用Rust Crate庫rust-decimal

該庫適用於需要有效精度計算和沒有舍入誤差的小數金融計算。

3.5 考慮舍入機制

在設計智能合約時,舍入問題通常遵循"我要佔便宜,他人不得薅我羊毛"的原則。根據這個原則,如果向下取整對合約有利,則向下;如果向上取整對合約有利,則向上;四舍五入由於不能確定是對誰有利,因此極少被採用。

通過採用這些方法,可以在Rust智能合約中實現更精確的數值計算,提高合約的可靠性和公平性。

查看原文
此頁面可能包含第三方內容,僅供參考(非陳述或保證),不應被視為 Gate 認可其觀點表述,也不得被視為財務或專業建議。詳見聲明
  • 讚賞
  • 8
  • 分享
留言
0/400
归零冲锋队长vip
· 07-17 09:17
啊又开始折腾这玩意儿了 上次就是精度问题被锁仓合约反杀 亏到吃土
回復0
区块链的薯条vip
· 07-16 10:52
浮点这玩意太坑了 谁用谁倒霉
回復0
MoonBoi42vip
· 07-15 08:48
浮点数精度哪有定点爽~
回復0
链上福尔摩克vip
· 07-14 09:54
显而易见,精度误差正是某些跨链桥被黑客利用的突破口,不寒而栗
回復0
HashRateHermitvip
· 07-14 09:54
精度问题真的坑死我了呀
回復0
GateUser-e87b21eevip
· 07-14 09:47
又搞小数点浮点的事儿 头大了
回復0
BearMarketSurvivorvip
· 07-14 09:45
精度这坑谁都逃不掉啊 这位懂的
回復0
空投舔狗vip
· 07-14 09:35
浮点数舍入这坑太深了 早栽过
回復0
交易,隨時隨地
qrCode
掃碼下載 Gate APP
社群列表
繁體中文
  • 简体中文
  • English
  • Tiếng Việt
  • 繁體中文
  • Español
  • Русский
  • Français (Afrique)
  • Português (Portugal)
  • Bahasa Indonesia
  • 日本語
  • بالعربية
  • Українська
  • Português (Brasil)