📢 Gate Square Exclusive: #WXTM Creative Contest# Is Now Live!
Celebrate CandyDrop Round 59 featuring MinoTari (WXTM) — compete for a 70,000 WXTM prize pool!
🎯 About MinoTari (WXTM)
Tari is a Rust-based blockchain protocol centered around digital assets.
It empowers creators to build new types of digital experiences and narratives.
With Tari, digitally scarce assets—like collectibles or in-game items—unlock new business opportunities for creators.
🎨 Event Period:
Aug 7, 2025, 09:00 – Aug 12, 2025, 16:00 (UTC)
📌 How to Participate:
Post original content on Gate Square related to WXTM or its
Precise Numerical Calculations in Rust Smart Contracts: Integers vs Floating-Point Numbers
Rust smart contracts Development Diary (7): Numerical Calculation
Previous Review:
1. Precision Issues in Floating Point Arithmetic
Unlike the commonly used smart contract programming language Solidity, the Rust language natively supports floating-point arithmetic. However, floating-point arithmetic has unavoidable precision issues. Therefore, it is not recommended to use floating-point arithmetic when writing smart contracts, especially when dealing with ratios or interest rates that involve important economic/financial decisions.
Most mainstream programming languages that represent floating-point numbers generally follow the IEEE 754 standard, and Rust is no exception. The following is an explanation of the double-precision floating-point type f64 in Rust and the binary data storage format in computers:
Floating-point numbers are expressed using scientific notation with a base of 2. For example, the decimal 0.8125 can be represented by the finite binary number 0.1101, and the specific conversion method is as follows:
However, for another decimal 0.7, there will be the following issues during its actual conversion to a floating-point number:
The decimal 0.7 will be represented as 0.101100110011001100.....(, an infinite loop ), which cannot be accurately represented by a finite-length floating-point number, and there is a "舍入(Rounding)" phenomenon.
Assuming that on the NEAR blockchain, it is necessary to distribute 0.7 NEAR tokens to ten users, the specific amount of NEAR tokens each user receives will be calculated and stored in the result_0 variable.
The output result of executing this test case is as follows:
It can be seen that in the above floating-point operations, the value of amount does not accurately represent 0.7, but rather an extremely close value of 0.69999999999999995559. Furthermore, for a single division operation such as amount/divisor, the result will also become the imprecise 0.06999999999999999, rather than the expected 0.07. This illustrates the uncertainty of floating-point operations.
In this regard, we have to consider using other types of numeric representation methods in smart contracts, such as fixed-point numbers.
In the actual writing of smart contracts, a fraction with a fixed denominator is usually used to represent a certain value, such as the fraction "x/N", where "N" is a constant and "x" can vary.
If "N" takes the value of "1,000,000,000,000,000,000", which is "10^18", at this time the decimal can be represented as an integer, like this:
In the NEAR Protocol, the common value for N is "10^24", which means 10^24 yoctoNEAR is equivalent to 1 NEAR token.
Based on this, we can modify the unit test in this section to calculate in the following way:
The result of the numerical calculation can be obtained as follows: 0.7 NEAR / 10 = 0.07 NEAR
2. The Issue of Precision in Rust Integer Calculations
From the description in Section 1 above, it can be seen that using integer operations can solve the problem of precision loss in floating-point operations in certain calculation scenarios.
However, this does not mean that the results of integer calculations are completely accurate and reliable. This section will introduce some of the reasons that affect the precision of integer calculations.
( 2.1 Order of Operations
For multiplication and division with the same arithmetic priority, changing their order may directly affect the calculation result, leading to issues with the precision of integer calculations.
For example, there is the following operation:
The results of the unit tests are as follows:
We can find that result_0 = a * c / b and result_1 = (a / b) * c, although their calculation formulas are the same, the operation results are different.
The specific reason for the analysis is: for integer division, the precision less than the divisor will be discarded. Therefore, in the process of calculating result_1, the first calculation of (a / b) will lose calculation precision first, becoming 0; while in the calculation of result_0, the result of a * c is calculated first as 20_0000, which will be greater than the divisor b, thus avoiding the problem of precision loss and obtaining the correct calculation result.
( 2.2 too small magnitude
.checked_mul(c) .expect("ERR_MUL"); // result_1 = (a * decimal / b) * c / decimal;
let result_1 = a .checked_mul(decimal) // mul decimal .expect("ERR_MUL") .checked_div(b) .expect("ERR_DIV") .checked_mul(c) .expect("ERR_MUL") .checked_div(decimal) // div decimal .expect("ERR_DIV"); println!("{}:{}", result_0, result_1); assert_eq!(result_0, result_1, ""); }
The specific results of this unit test are as follows:
The visible operation processes result_0 and result_1 are equivalent, yet their operation results are not the same, and result_1 = 13 is much closer to the actual expected calculation value: 13.3333....
3. How to Write Numeric Actuarial Rust Smart Contracts
Ensuring correct precision is very important in smart contracts. Although there is also the problem of precision loss in integer operations in Rust language, we can take some protective measures to improve precision and achieve satisfactory results.
( 3.1 Adjust the order of operations
) Increase the order of magnitude of integers 3.2
For example, for a NEAR token, if we define N as described above to be 10, it means that to represent a NEAR value of 5.123, the actual integer value used in calculations will be represented as 5.123 * 10^10 = 51_230_000_000. This value continues to participate in subsequent integer calculations, which can improve calculation accuracy.
( 3.3 Accumulated loss of computational precision
For unavoidable integer calculation precision issues, the project team may consider recording the cumulative loss of calculation precision.
u128 to distribute tokens to USER_NUM users.
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###( { let mut offset: u128 = 0; for i in 1..7 { println!)"Round {}",i(; offset = distribute)to_yocto("10"), offset[test]; println!("Offset {}\n",offset); } }
In this test case, the system distributes 10 tokens to 3 users each time. However, due to the issue of integer arithmetic precision, when calculating per_user_share in the first round, the result of the integer arithmetic is 10 / 3 = 3, meaning that the users in the first round will each receive an average of 3 tokens, with a total of 9 tokens being distributed.
At this time, it can be found that there is still 1 token left in the system that has not been distributed to users. Therefore, it can be considered to temporarily store the remaining token in the global variable offset of the system. When the system calls distribute again to distribute tokens to users, this value will be taken out and attempted to be distributed to users together with the amount of tokens distributed in this round.
The following is a simulated token distribution process: