Precision Issues and Optimization Solutions for Numerical Operations in Rust Smart Contracts

robot
Abstract generation in progress

Rust Smart Contracts Development Diary (7) Floating Point and Integer Arithmetic Precision Issues

This article will discuss the precision issues of floating-point and integer operations in Rust smart contracts, as well as how to write actuarial smart contracts.

1. The Precision Issues of Floating-Point Arithmetic

Rust natively supports floating-point arithmetic, but there are unavoidable precision issues with floating-point calculations. When writing smart contracts, it is not recommended to use floating-point arithmetic, especially when dealing with ratios or interest rates that involve important economic/financial decisions.

In Rust, floating-point numbers use the IEEE 754 standard and are represented in scientific notation with a base of 2. Certain decimals like (, such as 0.7), cannot be accurately represented by a finite-length floating-point number, leading to "rounding" phenomena.

For example, when distributing 0.7 NEAR tokens to 10 users on the NEAR public chain:

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

The execution result shows that the value of amount is not exactly 0.7, but rather an approximate value of 0.69999999999999995559. The result of the division operation is also not precise, being 0.06999999999999999 instead of the expected 0.07.

To solve this problem, consider using fixed-point numbers. In the NEAR Protocol, it is common to use the representation of 1 NEAR = 10^24 yoctoNEAR:

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

This allows for the calculation of the numerical result: 0.7 NEAR / 10 = 0.07 NEAR.

2. The Issue of Integer Calculation Precision in Rust

2.1 Order of Operations

The order of multiplication and division with the same arithmetic priority can directly affect the calculation result:

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

}

The execution result shows that result_0 and result_1 are not equal. The reason is that integer division discards precision that is less than the divisor. When calculating result_1, (a / b) will first lose precision and become 0; while calculating result_0, first calculating a * c avoids the loss of precision.

2.2 too small order of magnitude

A too small magnitude can also lead to precision issues:

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");

assert_eq!(result_0, result_1, "");

}

The results show result_0=12, result_1=13, with the latter being closer to the expected value of 13.3333.

3. How to Write Numeric Actuarial Rust Smart Contracts

To improve accuracy, the following protective measures can be taken:

3.1 Adjust the order of operations

Make integer multiplication take precedence over integer division.

3.2 Increase the order of integers

Use a larger magnitude to create a larger numerator. For example, represent 5.123 NEAR as 5.123 * 10^10 = 51_230_000_000.

3.3 Accumulation of calculation precision loss

Record the cumulative computational precision loss:

rust const USER_NUM: u128 = 3;

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 }

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

This allows the undelivered tokens to be temporarily stored and distributed together during the next distribution.

) 3.4 Using Rust Crate library rust-decimal

This library is suitable for decimal financial calculations that require effective precision and have no rounding errors.

) 3.5 Consideration of rounding mechanism

When designing smart contracts, the principle of "I want to take advantage, and others should not take advantage of me" is usually adopted. Depending on the situation, rounding down or rounding up is chosen, with very few cases of rounding to the nearest.

![]###https://img-cdn.gateio.im/webp-social/moments-6e8b4081214a69423fc7ae022d05c728.webp###

View Original
This page may contain third-party content, which is provided for information purposes only (not representations/warranties) and should not be considered as an endorsement of its views by Gate, nor as financial or professional advice. See Disclaimer for details.
  • Reward
  • 8
  • Share
Comment
0/400
SandwichHuntervip
· 21h ago
The rust noob is lying on the ground again.
View OriginalReply0
WhaleStalkervip
· 22h ago
Code bugs are really deadly...
View OriginalReply0
GhostInTheChainvip
· 07-15 16:28
Rust still needs to fill in the decimals... it's not easy.
View OriginalReply0
BoredWatchervip
· 07-13 18:40
Can the Rust library write contracts?
View OriginalReply0
GasFeeNightmarevip
· 07-13 18:35
The computational precision is as annoying as my gas fee...
View OriginalReply0
EyeOfTheTokenStormvip
· 07-13 18:34
The precision loss rate directly affects the profit and loss ratio. Who is still using floating point numbers for quantitative analysis? Even the gamblers have done a Rug Pull, right?
View OriginalReply0
TokenEconomistvip
· 07-13 18:32
actually, precision loss = f(order_of_ops, scale_factor) ... rust-decimal lib ftw
Reply0
just_another_walletvip
· 07-13 18:25
The floating-point numbers are too problematic...
View OriginalReply0
Trade Crypto Anywhere Anytime
qrCode
Scan to download Gate app
Community
English
  • 简体中文
  • English
  • Tiếng Việt
  • 繁體中文
  • Español
  • Русский
  • Français (Afrique)
  • Português (Portugal)
  • Bahasa Indonesia
  • 日本語
  • بالعربية
  • Українська
  • Português (Brasil)