Rust smart contracts development: overcoming the numerical calculation precision challenge

Numerical Calculation Precision Issues in Rust Smart Contracts Development

1. Precision Issues of Floating Point Operations

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

The double-precision floating-point type f64 in Rust follows the IEEE 754 standard and uses scientific notation with a base of 2 for representation. Certain decimals cannot be accurately represented with a finite-length floating-point number, resulting in a "rounding" phenomenon.

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

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

The output shows that the value of amount is 0.69999999999999995559, not the accurate 0.7. The result of the division operation has also become the imprecise 0.06999999999999999, instead of the expected 0.07.

To solve this problem, you can consider using fixed-point numbers. In the NEAR Protocol, 10^24 yoctoNEAR is commonly used to represent 1 NEAR token. The modified test code:

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 can yield an accurate calculation result: 0.7 NEAR / 10 = 0.07 NEAR.

2. The Issue of Integer Calculation Precision in Rust

Using integer operations can solve the precision loss problem of floating-point operations in certain scenarios, but the results of integer calculations are also not completely accurate and reliable.

2.1 Order of Operations

For multiplication and division with the same arithmetic precedence, changes in their order can directly affect the calculation results, leading to integer calculation precision issues. For example:

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) first loses precision and becomes 0; whereas when calculating result_0, a * c is calculated first to avoid loss of precision.

2.2 Too small magnitude

When it comes to decimal calculations, integer operations may lead to a loss of precision:

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

}

The results show result_0=12, result_1=13, while the actual expected value should be 13.3333....

3. How to Write Numerical Actuarial Rust Smart Contracts

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

3.1 Adjust the order of operations

Let integer multiplication take precedence over integer division.

Increase the order of magnitude of integers 3.2

Using a larger magnitude to create a larger numerator. For example, representing 5.123 NEAR can be done as 5.123 * 10^10 = 51_230_000_000 for subsequent calculations.

3.3 Loss of accumulation operation precision

For unavoidable precision issues, accumulated calculation precision loss can be recorded. For example:

rust const USER_NUM: u128 = 3;

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 }

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

This method can accumulate the remaining tokens from each distribution and distribute them together during the next distribution, ultimately achieving the goal of full issuance.

( 3.4 Using the Rust crate library rust-decimal

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

) 3.5 Consider the rounding mechanism

In the design of smart contracts, rounding issues are often addressed using the principle of "I want to benefit, while others must not take advantage of me." Depending on the situation, either rounding down or rounding up is chosen, with very little use 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
rugpull_survivorvip
· 07-14 15:34
Many people have fallen into the rounding pit.
View OriginalReply0
WhaleWatchervip
· 07-14 07:46
Precision control is crucial.
View OriginalReply0
consensus_failurevip
· 07-13 12:52
More stable with fixed precision
View OriginalReply0
Ser_Liquidatedvip
· 07-12 19:41
If there are any more precision issues, it will get liquidated.
View OriginalReply0
DecentralizeMevip
· 07-11 16:13
Let's replace it with fixed-point numbers.
View OriginalReply0
BoredWatchervip
· 07-11 15:58
The code is a bit convoluted.
View OriginalReply0
BrokenDAOvip
· 07-11 15:53
Total computing power overflow is troublesome.
View OriginalReply0
MEVSandwichMakervip
· 07-11 15:51
It's an old pit, I suggest you avoid it.
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)