Rust智能合約整數計算精度問題及解決方案

Rust智能合約養成日記(7):整數計算精度問題

往期回顧:

  • Rust智能合約養成日記(1)合約狀態數據定義與方法實現
  • Rust智能合約養成日記(2)編寫Rust智能合約單元測試
  • Rust智能合約養成日記(3)Rust智能合約部署、函數調用及Explorer的使用
  • Rust智能合約養成日記(4)Rust 智能合約整數溢出
  • Rust智能合約養成日記(5)重入攻擊
  • Rust智能合約養成日記(6)拒絕服務攻擊

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

與Solidity不同,Rust原生支持浮點數運算。但浮點數運算存在不可避免的精度問題,因此不推薦在智能合約中使用,尤其是處理重要經濟/金融決策的比率或利率時。

Rust遵循IEEE 754標準表示浮點數。f64雙精度浮點類型在計算機內部採用二進制科學計數法表示。

一些小數可以用有限位數的二進制精確表示,如0.8125可表示爲0.1101。但0.7這樣的小數會產生無限循環的二進制表示,無法用有限位浮點數準確表示,存在"舍入"問題。

在NEAR公鏈上分發0.7個NEAR代幣給10位用戶的例子中:

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.69999999999999995559,result_0爲0.06999999999999999,不等於預期的0.07。

爲解決這個問題,可以使用定點數。在NEAR中,通常用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).unwrap().checked_div(b).unwrap();
let result_1 = a.checked_div(b).unwrap().checked_mul(c).unwrap();

assert_eq!(result_0,result_1);

}

結果顯示result_0 = 2, result_1 = 0。

原因是整數除法會舍棄小於除數的精度。計算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).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);

}

結果顯示result_0 = 12, result_1 = 13,後者更接近實際值13.3333。

3. 如何編寫數值精算的Rust智能合約

爲提高精度,可採取以下措施:

3.1 調整運算的操作順序

讓整數乘法優先於除法。

3.2 增加整數的數量級

使用更大的數量級,創造更大的分子。如定義1 NEAR = 10^24 yoctoNEAR。

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; 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 { offset = distribute(10_000_000_000_000_000_000_000_000, offset); } }

3.4 使用Rust Crate庫rust-decimal

該庫適用於需要高精度且無舍入誤差的小數金融計算。

3.5 考慮舍入機制

智能合約設計中,舍入通常遵循"對我有利"原則:向下取整對我有利則向下,向上取整對我有利則向上,很少採用四舍五入。

EQ0.17%
查看原文
此頁面可能包含第三方內容,僅供參考(非陳述或保證),不應被視為 Gate 認可其觀點表述,也不得被視為財務或專業建議。詳見聲明
  • 讚賞
  • 8
  • 分享
留言
0/400
跨链迷路人vip
· 07-25 17:53
整数计算都搞不定 玩啥合约
回復0
WagmiWarriorvip
· 07-25 11:45
看到第7篇了 细啊细啊
回復0
MetaMisfitvip
· 07-25 01:13
Rust这智能合约坑也忒多了吧
回復0
鸭鸭毛毛vip
· 07-22 22:44
好家伙 浮点数又整事情啦~
回復0
matic填坑工vip
· 07-22 22:44
精度踩坑过的来现身说法!
回復0
MetadataExplorervip
· 07-22 22:44
整数这块经常被忽视啊 蛮关键的
回復0
MEVHuntervip
· 07-22 22:29
精度是一个 mev 貔貅盘... 保持你的浮动紧凑,否则就会被干掉,兄弟
查看原文回復0
TrustlessMaximalistvip
· 07-22 22:16
这浮点运算这坑必须避开啊
回復0
交易,隨時隨地
qrCode
掃碼下載 Gate APP
社群列表
繁體中文
  • 简体中文
  • English
  • Tiếng Việt
  • 繁體中文
  • Español
  • Русский
  • Français (Afrique)
  • Português (Portugal)
  • Bahasa Indonesia
  • 日本語
  • بالعربية
  • Українська
  • Português (Brasil)