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
· 7小时前
啊又开始折腾这玩意儿了 上次就是精度问题被锁仓合约反杀 亏到吃土
回复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)