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.16%
此页面可能包含第三方内容,仅供参考(非陈述/保证),不应被视为 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)