📢 Gate Square #MBG Posting Challenge# is Live— Post for MBG Rewards!
Want a share of 1,000 MBG? Get involved now—show your insights and real participation to become an MBG promoter!
💰 20 top posts will each win 50 MBG!
How to Participate:
1️⃣ Research the MBG project
Share your in-depth views on MBG’s fundamentals, community governance, development goals, and tokenomics, etc.
2️⃣ Join and share your real experience
Take part in MBG activities (CandyDrop, Launchpool, or spot trading), and post your screenshots, earnings, or step-by-step tutorials. Content can include profits, beginner-friendl
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;
}
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;
}
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###