🎉 [Gate 30 Million Milestone] Share Your Gate Moment & Win Exclusive Gifts!
Gate has surpassed 30M users worldwide — not just a number, but a journey we've built together.
Remember the thrill of opening your first account, or the Gate merch that’s been part of your daily life?
📸 Join the #MyGateMoment# campaign!
Share your story on Gate Square, and embrace the next 30 million together!
✅ How to Participate:
1️⃣ Post a photo or video with Gate elements
2️⃣ Add #MyGateMoment# and share your story, wishes, or thoughts
3️⃣ Share your post on Twitter (X) — top 10 views will get extra rewards!
👉
Precision Issues and Optimization Solutions for Numerical Operations in Rust Smart Contracts
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;
}
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;
}
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###