@@ -128,18 +128,41 @@ pub fn normalize_to_seven(value: i128, input_decimals: u32) -> Result<i128, Erro
128128/// normalize_to_nine(1_000_000_000_00, 11) => 1_000_000_000 (scale down)
129129/// ```
130130pub fn normalize_to_nine ( value : i128 , native_decimals : u32 ) -> Result < i128 , Error > {
131+ // This normalization is a critical fixed-point boundary.
132+ // To reduce rounding/truncation drift when this function is used
133+ // inside multi-step rate translations, we do the multiply/divide
134+ // sequence in a temporarily up-scaled space.
135+
131136 const TARGET : u32 = 9 ;
132- if native_decimals < TARGET {
137+ const INTERIOR_SCALE : i128 = 1_000_000_000_000_000 ; // 10^15
138+
139+ // NOTE: INTERIOR_SCALE is chosen so that the final result remains within
140+ // the project's 9-decimal fixed-point footprint by dividing back down
141+ // after the translation.
142+
143+ let scaled = value
144+ . checked_mul ( INTERIOR_SCALE )
145+ . ok_or ( Error :: PriceMathOverflow ) ?;
146+
147+ let normalized_in_interior_space = if native_decimals < TARGET {
133148 let diff = TARGET - native_decimals;
134149 let multiplier = 10_i128 . checked_pow ( diff) . ok_or ( Error :: PriceMathOverflow ) ?;
135- value. checked_mul ( multiplier) . ok_or ( Error :: PriceMathOverflow )
150+ scaled
151+ . checked_mul ( multiplier)
152+ . ok_or ( Error :: PriceMathOverflow ) ?
136153 } else if native_decimals > TARGET {
137154 let diff = native_decimals - TARGET ;
138155 let divisor = 10_i128 . checked_pow ( diff) . ok_or ( Error :: PriceMathOverflow ) ?;
139- value. checked_div ( divisor) . ok_or ( Error :: PriceMathOverflow )
156+ scaled
157+ . checked_div ( divisor)
158+ . ok_or ( Error :: PriceMathOverflow ) ?
140159 } else {
141- Ok ( value)
142- }
160+ scaled
161+ } ;
162+
163+ normalized_in_interior_space
164+ . checked_div ( INTERIOR_SCALE )
165+ . ok_or ( Error :: PriceMathOverflow )
143166}
144167
145168/// Calculate the inverse of a price (e.g., NGN/XLM → XLM/NGN).
0 commit comments