soroban-fixed-point-math has Incorrect Rounding and Overflow Handling in Signed Fixed-Point Math with Negatives
Description
soroban-fixed-point-math is a fixed-point math library for Soroban smart contacts. In versions 1.3.0 and 1.4.0, the mulDiv(x, y, z) function incorrectly handled cases where both the intermediate product $x * y$ and the divisor $z$ were negative. The logic assumed that if the intermediate product was negative, the final result must also be negative, neglecting the sign of $z$. This resulted in rounding being applied in the wrong direction for cases where both $x * y$ and $z$ were negative. The functions most at risk are fixed_div_floor and fixed_div_ceil, as they often use non-constant numbers as the divisor $z$ in mulDiv. This error is present in all signed FixedPoint and SorobanFixedPoint implementations, including i64, i128, and I256. Versions 1.3.1 and 1.4.1 contain a patch. No known workarounds for this issue are available.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
soroban-fixed-point-mathcrates.io | >= 1.4.0, < 1.4.1 | 1.4.1 |
soroban-fixed-point-mathcrates.io | >= 1.3.0, < 1.3.1 | 1.3.1 |
Affected products
1- Range: v1.0.0, v1.1.0, v1.1.1, …
Patches
1c9233f709419fix: better edge case handling and test suite improvements (#9)
7 files changed · +2762 −638
Cargo.toml+1 −1 modified@@ -12,4 +12,4 @@ keywords = ["no_std", "wasm"] rust-version = "1.89" [dependencies] -soroban-sdk = { version = "23.0.2" } +soroban-sdk = { version = "23" }
src/i128.rs+906 −158 modified@@ -28,10 +28,12 @@ pub(crate) fn mul_div_floor(x: i128, y: i128, z: i128) -> Option<i128> { /// Performs floor(r / z) fn div_floor(r: i128, z: i128) -> Option<i128> { - if r < 0 || (r > 0 && z < 0) { + if (r < 0 && z > 0) || (r > 0 && z < 0) { // ceiling is taken by default for a negative result + // if there is any remainder, sub 1 to round floor let remainder = r.checked_rem_euclid(z)?; - (r / z).checked_sub(if remainder > 0 { 1 } else { 0 }) + r.checked_div(z)? + .checked_sub(if remainder > 0 { 1 } else { 0 }) } else { // floor taken by default for a positive or zero result r.checked_div(z) @@ -46,13 +48,15 @@ pub(crate) fn mul_div_ceil(x: i128, y: i128, z: i128) -> Option<i128> { /// Performs ceil(r / z) fn div_ceil(r: i128, z: i128) -> Option<i128> { - if r <= 0 || (r > 0 && z < 0) { + if r == 0 || (r < 0 && z > 0) || (r > 0 && z < 0) { // ceiling is taken by default for a negative or zero result r.checked_div(z) } else { // floor taken by default for a positive result + // if there is any remainder, add 1 to round ceil let remainder = r.checked_rem_euclid(z)?; - (r / z).checked_add(if remainder > 0 { 1 } else { 0 }) + r.checked_div(z)? + .checked_add(if remainder > 0 { 1 } else { 0 }) } } @@ -112,372 +116,1116 @@ fn scaled_mul_div_ceil(x: &i128, env: &Env, y: &i128, z: &i128) -> i128 { #[cfg(test)] mod test_fixed_point { + use core::i128; - /********** fixed_mul_floor **********/ - + use super::{mul_div_ceil, mul_div_floor}; use crate::FixedPoint; + /********** mul_div_floor **********/ + #[test] - fn test_fixed_mul_floor_rounds_down() { + fn test_mul_div_floor_rounds_down() { + // Real result = 483_5313675.8 let x: i128 = 1_5391283; let y: i128 = 314_1592653; - let denominator: i128 = 1_0000001; + let z: i128 = 1_0000001; - let result = x.fixed_mul_floor(y, denominator).unwrap(); + let result = mul_div_floor(x, y, z).unwrap(); + + assert_eq!(result, 483_5313675); + } + + #[test] + fn test_mul_div_floor_exact() { + // Real result = 12 + let x: i128 = 8; + let y: i128 = 3; + let z: i128 = 2; + + let result = mul_div_floor(x, y, z).unwrap(); + + assert_eq!(result, 12); + } + + #[test] + fn test_mul_div_floor_negative_exact() { + // Real result = -12 + let x: i128 = 8; + let y: i128 = -3; + let z: i128 = 2; + + let result = mul_div_floor(x, y, z).unwrap(); - assert_eq!(result, 483_5313675) + assert_eq!(result, -12); } #[test] - fn test_fixed_mul_floor_negative_rounds_down() { + fn test_mul_div_floor_mul_negative_rounds_down() { + // Real result = -483_5313675.8 let x: i128 = -1_5391283; let y: i128 = 314_1592653; - let denominator: i128 = 1_0000001; + let z: i128 = 1_0000001; - let result = x.fixed_mul_floor(y, denominator).unwrap(); + let result = mul_div_floor(x, y, z).unwrap(); + + assert_eq!(result, -483_5313676); + } + + #[test] + fn test_mul_div_floor_div_negative_rounds_down() { + // Real result = -483_5313675.8 + let x: i128 = 1_5391283; + let y: i128 = 314_1592653; + let z: i128 = -1_0000001; + + let result = mul_div_floor(x, y, z).unwrap(); + + assert_eq!(result, -483_5313676); + } + + #[test] + fn test_mul_div_floor_x_y_negative_rounds_down() { + // Real result = 483_5313675.8 + let x: i128 = -1_5391283; + let y: i128 = -314_1592653; + let z: i128 = 1_0000001; + + let result = mul_div_floor(x, y, z).unwrap(); - assert_eq!(result, -483_5313676) + assert_eq!(result, 483_5313675); } #[test] - fn test_fixed_mul_floor_large_number() { + fn test_mul_div_floor_y_z_negative_rounds_down() { + // Real result = 483_5313675.8 + let x: i128 = 1_5391283; + let y: i128 = -314_1592653; + let z: i128 = -1_0000001; + + let result = mul_div_floor(x, y, z).unwrap(); + + assert_eq!(result, 483_5313675); + } + + #[test] + fn test_mul_div_floor_all_negative_rounds_down() { + // Real result = -483_5313675.8 + let x: i128 = -1_5391283; + let y: i128 = -314_1592653; + let z: i128 = -1_0000001; + + let result = mul_div_floor(x, y, z).unwrap(); + + assert_eq!(result, -483_5313676); + } + + #[test] + fn test_mul_div_floor_mul_zero() { + let x: i128 = 1_5391283; + let y: i128 = 0; + let z: i128 = 1_0000001; + + let result = mul_div_floor(x, y, z).unwrap(); + + assert_eq!(result, 0); + } + + #[test] + fn test_mul_div_floor_div_zero() { + let x: i128 = 1_5391283; + let y: i128 = 314_1592653; + let z: i128 = 0; + + let result = mul_div_floor(x, y, z); + + assert_eq!(result, None); + } + + #[test] + fn test_mul_div_floor_large_number() { let x: i128 = 170_141_183_460_469_231_731; let y: i128 = 1_000_000_000_000_000_000; - let denominator: i128 = 1_000_000_000_000_000_000; + let z: i128 = 1_000_000_000_000_000_000; - let result = x.fixed_mul_floor(y, denominator).unwrap(); + let result = mul_div_floor(x, y, z).unwrap(); + + assert_eq!(result, 170_141_183_460_469_231_731); + } - assert_eq!(result, 170_141_183_460_469_231_731) + #[test] + fn test_mul_div_floor_negative_large_number() { + let x: i128 = -170_141_183_460_469_231_731; + let y: i128 = 1_000_000_000_000_000_000; + let z: i128 = 1_000_000_000_000_000_000; + + let result = mul_div_floor(x, y, z).unwrap(); + + assert_eq!(result, -170_141_183_460_469_231_731); + } + + #[test] + fn test_mul_div_floor_small_number() { + let x: i128 = 1; + let y: i128 = 2; + let z: i128 = 3; + + let result = mul_div_floor(x, y, z).unwrap(); + + assert_eq!(result, 0); + } + + #[test] + fn test_mul_div_floor_negative_small_number() { + let x: i128 = -1; + let y: i128 = 2; + let z: i128 = 3; + + let result = mul_div_floor(x, y, z).unwrap(); + + assert_eq!(result, -1); } #[test] - fn test_fixed_mul_floor_phantom_overflow() { + fn test_mul_div_floor_phantom_overflow() { let x: i128 = 170_141_183_460_469_231_731; let y: i128 = 1_000_000_000_000_000_001; - let denominator: i128 = 1_000_000_000_000_000_000; + let z: i128 = 1_000_000_000_000_000_000; - let result = x.fixed_mul_floor(y, denominator); + let result = mul_div_floor(x, y, z); - assert_eq!(None, result); + assert_eq!(result, None); } - /********** fixed_mul_ceil **********/ + #[test] + fn test_mul_div_floor_negative_phantom_overflow() { + let x: i128 = -170_141_183_460_469_231_731; + let y: i128 = 1_000_000_000_000_000_001; + let z: i128 = 1_000_000_000_000_000_000; + + let result = mul_div_floor(x, y, z); + + assert_eq!(result, None); + } + + /********** mul_div_ceil **********/ #[test] - fn test_fixed_mul_ceil_rounds_up() { + fn test_mul_div_ceil_rounds_up() { + // Real result = 483_5313675.8 let x: i128 = 1_5391283; let y: i128 = 314_1592653; - let denominator: i128 = 1_0000001; + let z: i128 = 1_0000001; - let result = x.fixed_mul_ceil(y, denominator).unwrap(); + let result = mul_div_ceil(x, y, z).unwrap(); + + assert_eq!(result, 483_5313676); + } + + #[test] + fn test_mul_div_ceil_exact() { + // Real result = 12 + let x: i128 = 8; + let y: i128 = 3; + let z: i128 = 2; + + let result = mul_div_ceil(x, y, z).unwrap(); + + assert_eq!(result, 12); + } + + #[test] + fn test_mul_div_ceil_negative_exact() { + // Real result = -12 + let x: i128 = 8; + let y: i128 = -3; + let z: i128 = 2; + + let result = mul_div_ceil(x, y, z).unwrap(); - assert_eq!(result, 483_5313676) + assert_eq!(result, -12); } #[test] - fn test_fixed_mul_ceil_negative_rounds_up() { + fn test_mul_div_ceil_mul_negative_rounds_up() { + // Real result = -483_5313675.8 let x: i128 = -1_5391283; let y: i128 = 314_1592653; - let denominator: i128 = 1_0000001; + let z: i128 = 1_0000001; - let result = x.fixed_mul_ceil(y, denominator).unwrap(); + let result = mul_div_ceil(x, y, z).unwrap(); + + assert_eq!(result, -483_5313675); + } + + #[test] + fn test_mul_div_ceil_div_negative_rounds_up() { + // Real result = -483_5313675.8 + let x: i128 = 1_5391283; + let y: i128 = 314_1592653; + let z: i128 = -1_0000001; + + let result = mul_div_ceil(x, y, z).unwrap(); + + assert_eq!(result, -483_5313675); + } + + #[test] + fn test_mul_div_ceil_x_y_negative_rounds_up() { + // Real result = 483_5313675.8 + let x: i128 = -1_5391283; + let y: i128 = -314_1592653; + let z: i128 = 1_0000001; + + let result = mul_div_ceil(x, y, z).unwrap(); + + assert_eq!(result, 483_5313676); + } + + #[test] + fn test_mul_div_ceil_y_z_negative_rounds_up() { + // Real result = 483_5313675.8 + let x: i128 = 1_5391283; + let y: i128 = -314_1592653; + let z: i128 = -1_0000001; + + let result = mul_div_ceil(x, y, z).unwrap(); + + assert_eq!(result, 483_5313676); + } + + #[test] + fn test_mul_div_ceil_all_negative_rounds_up() { + // Real result = -483_5313675.8 + let x: i128 = -1_5391283; + let y: i128 = -314_1592653; + let z: i128 = -1_0000001; + + let result = mul_div_ceil(x, y, z).unwrap(); + + assert_eq!(result, -483_5313675); + } + + #[test] + fn test_mul_div_ceil_mul_zero() { + let x: i128 = 1_5391283; + let y: i128 = 0; + let z: i128 = 1_0000001; + + let result = mul_div_ceil(x, y, z).unwrap(); + + assert_eq!(result, 0); + } + + #[test] + fn test_mul_div_ceil_div_zero() { + let x: i128 = 1_5391283; + let y: i128 = 314_1592653; + let z: i128 = 0; - assert_eq!(result, -483_5313675) + let result = mul_div_ceil(x, y, z); + + assert_eq!(result, None); } #[test] - fn test_fixed_mul_ceil_large_number() { + fn test_mul_div_ceil_large_number() { let x: i128 = 170_141_183_460_469_231_731; let y: i128 = 1_000_000_000_000_000_000; - let denominator: i128 = 1_000_000_000_000_000_000; + let z: i128 = 1_000_000_000_000_000_000; - let result = x.fixed_mul_ceil(y, denominator).unwrap(); + let result = mul_div_ceil(x, y, z).unwrap(); + + assert_eq!(result, 170_141_183_460_469_231_731); + } + + #[test] + fn test_mul_div_ceil_negative_large_number() { + let x: i128 = -170_141_183_460_469_231_731; + let y: i128 = 1_000_000_000_000_000_000; + let z: i128 = 1_000_000_000_000_000_000; + + let result = mul_div_ceil(x, y, z).unwrap(); + + assert_eq!(result, -170_141_183_460_469_231_731); + } + + #[test] + fn test_mul_div_ceil_small_number() { + let x: i128 = 1; + let y: i128 = 2; + let z: i128 = 3; + + let result = mul_div_ceil(x, y, z).unwrap(); + + assert_eq!(result, 1); + } + + #[test] + fn test_mul_div_ceil_negative_small_number() { + let x: i128 = -1; + let y: i128 = 2; + let z: i128 = 3; + + let result = mul_div_ceil(x, y, z).unwrap(); - assert_eq!(result, 170_141_183_460_469_231_731) + assert_eq!(result, 0); } #[test] - fn test_fixed_mul_ceil_phantom_overflow() { + fn test_mul_div_ceil_phantom_overflow() { let x: i128 = 170_141_183_460_469_231_731; let y: i128 = 1_000_000_000_000_000_001; - let denominator: i128 = 1_000_000_000_000_000_000; + let z: i128 = 1_000_000_000_000_000_000; + + let result = mul_div_ceil(x, y, z); + + assert_eq!(result, None); + } + + #[test] + fn test_mul_div_ceil_negative_phantom_overflow() { + let x: i128 = -170_141_183_460_469_231_731; + let y: i128 = 1_000_000_000_000_000_001; + let z: i128 = 1_000_000_000_000_000_000; + + let result = mul_div_ceil(x, y, z); + + assert_eq!(result, None); + } + + /********** fixed_mul_floor **********/ + + #[test] + fn test_fixed_mul_floor() { + // Real result = 104_9522835.2 + let x: i128 = 3_1423141; + let y: i128 = 4_1234142; + let denominator: i128 = 0_1234567; + + let result = x.fixed_mul_floor(y, denominator).unwrap(); + assert_eq!(result, 104_9522835); + + let result = (-x).fixed_mul_floor(y, denominator).unwrap(); + assert_eq!(result, -104_9522836); + + let result = (-x).fixed_mul_floor(-y, denominator).unwrap(); + assert_eq!(result, 104_9522835); + + let invalid = x.fixed_mul_floor(y, 0); + assert_eq!(invalid, None); + } + + /********** fixed_mul_ceil **********/ + + #[test] + fn test_fixed_mul_ceil() { + // Real result = 104_9522835.2 + let x: i128 = 3_1423141; + let y: i128 = 4_1234142; + let denominator: i128 = 0_1234567; + + let result = x.fixed_mul_ceil(y, denominator).unwrap(); + assert_eq!(result, 104_9522836); + + let result = (-x).fixed_mul_ceil(y, denominator).unwrap(); + assert_eq!(result, -104_9522835); - let result = x.fixed_mul_ceil(y, denominator); + let result = (-x).fixed_mul_ceil(-y, denominator).unwrap(); + assert_eq!(result, 104_9522836); - assert_eq!(None, result); + let invalid = x.fixed_mul_ceil(y, 0); + assert_eq!(invalid, None); } /********** fixed_div_floor **********/ #[test] - fn test_fixed_div_floor_rounds_down() { + fn test_fixed_div_floor() { + // Real result = 204_1150997.8 let x: i128 = 314_1592653; let y: i128 = 1_5391280; let denominator: i128 = 1_0000000; let result = x.fixed_div_floor(y, denominator).unwrap(); + assert_eq!(result, 204_1150997); + + let result = (-x).fixed_div_floor(y, denominator).unwrap(); + assert_eq!(result, -204_1150998); + + let result = (-x).fixed_div_floor(-y, denominator).unwrap(); + assert_eq!(result, 204_1150997); - assert_eq!(result, 204_1150997) + let invalid = x.fixed_div_floor(0, denominator); + assert_eq!(invalid, None); } + /********** fixed_div_ceil **********/ + #[test] - fn test_fixed_div_floor_negative_rounds_down() { + fn test_fixed_div_ceil() { + // Real result = 204_1150997.8 let x: i128 = 314_1592653; - let y: i128 = -1_5391280; + let y: i128 = 1_5391280; let denominator: i128 = 1_0000000; - let result = x.fixed_div_floor(y, denominator).unwrap(); + let result = x.fixed_div_ceil(y, denominator).unwrap(); + assert_eq!(result, 204_1150998); + + let result = (-x).fixed_div_ceil(y, denominator).unwrap(); + assert_eq!(result, -204_1150997); + + let result = (-x).fixed_div_ceil(-y, denominator).unwrap(); + assert_eq!(result, 204_1150998); - assert_eq!(result, -204_1150998) + let invalid = x.fixed_div_ceil(0, denominator); + assert_eq!(invalid, None); } +} + +#[cfg(test)] +mod test_soroban_fixed_point { + use core::i128; + + use super::{scaled_mul_div_ceil, scaled_mul_div_floor}; + use crate::SorobanFixedPoint; + use soroban_sdk::Env; + + /********** scaled_mul_div_floor **********/ #[test] - fn test_fixed_div_floor_large_number() { - let x: i128 = 170_141_183_460_469_231_731; - let y: i128 = 1_000_000_000_000_000_000; - let denominator: i128 = 1_000_000_000_000_000_000; + fn test_scaled_mul_div_floor_rounds_down() { + // Real result = 483_5313675.8 + let env = Env::default(); + let x: i128 = 1_5391283; + let y: i128 = 314_1592653; + let z: i128 = 1_0000001; - let result = x.fixed_div_floor(y, denominator).unwrap(); + let result = scaled_mul_div_floor(&x, &env, &y, &z); - assert_eq!(result, 170_141_183_460_469_231_731) + assert_eq!(result, 483_5313675); } #[test] - fn test_fixed_div_floor_phantom_overflow() { - let x: i128 = 170_141_183_460_469_231_732; - let y: i128 = 1_000_000_000_000_000_000; - let denominator: i128 = 1_000_000_000_000_000_000; + fn test_scaled_mul_div_floor_exact() { + // Real result = 12 + let env = Env::default(); + let x: i128 = 8; + let y: i128 = 3; + let z: i128 = 2; - let result = x.fixed_div_floor(y, denominator); + let result = scaled_mul_div_floor(&x, &env, &y, &z); - assert_eq!(None, result); + assert_eq!(result, 12); } - /********** fixed_div_ceil **********/ + #[test] + fn test_scaled_mul_div_floor_negative_exact() { + // Real result = -12 + let env = Env::default(); + let x: i128 = 8; + let y: i128 = -3; + let z: i128 = 2; + + let result = scaled_mul_div_floor(&x, &env, &y, &z); + + assert_eq!(result, -12); + } #[test] - fn test_fixed_div_ceil_rounds_down() { - let x: i128 = 314_1592653; - let y: i128 = 1_5391280; - let denominator: i128 = 1_0000000; + fn test_scaled_mul_div_floor_mul_negative_rounds_down() { + // Real result = -483_5313675.8 + let env = Env::default(); + let x: i128 = -1_5391283; + let y: i128 = 314_1592653; + let z: i128 = 1_0000001; - let result = x.fixed_div_ceil(y, denominator).unwrap(); + let result = scaled_mul_div_floor(&x, &env, &y, &z); - assert_eq!(result, 204_1150998) + assert_eq!(result, -483_5313676); } #[test] - fn test_fixed_div_ceil_negative_rounds_down() { - let x: i128 = 314_1592653; - let y: i128 = -1_5391280; - let denominator: i128 = 1_0000000; + fn test_scaled_mul_div_floor_div_negative_rounds_down() { + // Real result = -483_5313675.8 + let env = Env::default(); + let x: i128 = 1_5391283; + let y: i128 = 314_1592653; + let z: i128 = -1_0000001; - let result = x.fixed_div_ceil(y, denominator).unwrap(); + let result = scaled_mul_div_floor(&x, &env, &y, &z); + + assert_eq!(result, -483_5313676); + } + + #[test] + fn test_scaled_mul_div_floor_x_y_negative_rounds_down() { + // Real result = 483_5313675.8 + let env = Env::default(); + let x: i128 = -1_5391283; + let y: i128 = -314_1592653; + let z: i128 = 1_0000001; + + let result = scaled_mul_div_floor(&x, &env, &y, &z); - assert_eq!(result, -204_1150997) + assert_eq!(result, 483_5313675); } #[test] - fn test_fixed_div_ceil_large_number() { + fn test_scaled_mul_div_floor_y_z_negative_rounds_down() { + // Real result = 483_5313675.8 + let env = Env::default(); + let x: i128 = 1_5391283; + let y: i128 = -314_1592653; + let z: i128 = -1_0000001; + + let result = scaled_mul_div_floor(&x, &env, &y, &z); + + assert_eq!(result, 483_5313675); + } + + #[test] + fn test_scaled_mul_div_floor_all_negative_rounds_down() { + // Real result = -483_5313675.8 + let env = Env::default(); + let x: i128 = -1_5391283; + let y: i128 = -314_1592653; + let z: i128 = -1_0000001; + + let result = scaled_mul_div_floor(&x, &env, &y, &z); + + assert_eq!(result, -483_5313676); + } + + #[test] + fn test_scaled_mul_div_floor_mul_zero() { + let env = Env::default(); + let x: i128 = 1_5391283; + let y: i128 = 0; + let z: i128 = 1_0000001; + + let result = scaled_mul_div_floor(&x, &env, &y, &z); + + assert_eq!(result, 0); + } + + #[test] + #[should_panic] + fn test_scaled_mul_div_floor_div_zero() { + let env = Env::default(); + let x: i128 = 1_5391283; + let y: i128 = 314_1592653; + let z: i128 = 0; + + scaled_mul_div_floor(&x, &env, &y, &z); + } + + #[test] + fn test_scaled_mul_div_floor_large_number() { + let env = Env::default(); let x: i128 = 170_141_183_460_469_231_731; let y: i128 = 1_000_000_000_000_000_000; - let denominator: i128 = 1_000_000_000_000_000_000; + let z: i128 = 1_000_000_000_000_000_000; - let result = x.fixed_div_ceil(y, denominator).unwrap(); + let result = scaled_mul_div_floor(&x, &env, &y, &z); - assert_eq!(result, 170_141_183_460_469_231_731) + assert_eq!(result, 170_141_183_460_469_231_731); } #[test] - fn test_fixed_div_ceil_phantom_overflow() { - let x: i128 = 170_141_183_460_469_231_732; + fn test_scaled_mul_div_floor_negative_large_number() { + let env = Env::default(); + let x: i128 = -170_141_183_460_469_231_731; let y: i128 = 1_000_000_000_000_000_000; - let denominator: i128 = 1_000_000_000_000_000_000; + let z: i128 = 1_000_000_000_000_000_000; - let result = x.fixed_div_ceil(y, denominator); + let result = scaled_mul_div_floor(&x, &env, &y, &z); - assert_eq!(None, result); + assert_eq!(result, -170_141_183_460_469_231_731); } -} -#[cfg(test)] -mod test_soroban_fixed_point { - use crate::SorobanFixedPoint; - use soroban_sdk::Env; + #[test] + fn test_scaled_mul_div_floor_small_number() { + let env = Env::default(); + let x: i128 = 1; + let y: i128 = 2; + let z: i128 = 3; - /********** fixed_mul_floor **********/ + let result = scaled_mul_div_floor(&x, &env, &y, &z); + + assert_eq!(result, 0); + } + + #[test] + fn test_scaled_mul_div_floor_negative_small_number() { + let env = Env::default(); + let x: i128 = -1; + let y: i128 = 2; + let z: i128 = 3; + + let result = scaled_mul_div_floor(&x, &env, &y, &z); + + assert_eq!(result, -1) + } + + #[test] + fn test_scaled_mul_div_floor_phantom_overflow_uses_i256() { + // i128::MAX is odd + let env = Env::default(); + let x: i128 = (i128::MAX - 1) / 2; + let y: i128 = 2 * 10i128.pow(18); + let z: i128 = 10i128.pow(18); + + let result = scaled_mul_div_floor(&x, &env, &y, &z); + + assert_eq!(result, i128::MAX - 1); + } + + #[test] + fn test_scaled_mul_div_floor_negative_phantom_overflow_uses_i256() { + let env = Env::default(); + let x: i128 = i128::MIN / 2; + let y: i128 = 2 * 10i128.pow(18); + let z: i128 = 10i128.pow(18); + + let result = scaled_mul_div_floor(&x, &env, &y, &z); + + assert_eq!(result, i128::MIN); + } #[test] - fn test_fixed_mul_floor_rounds_down() { + fn test_scaled_mul_div_floor_phantom_overflow_rounds_down() { + // Real Result = 333_3x18.3.. + let env = Env::default(); + let x: i128 = 100 * 10i128.pow(18); + let y: i128 = 10 * 10i128.pow(18); + let z: i128 = 3 * 10i128.pow(18); + + let result = scaled_mul_div_floor(&x, &env, &y, &z); + + assert_eq!(result, 333_333_333_333_333_333_333); + } + + #[test] + #[should_panic] + fn test_scaled_mul_div_floor_result_overflow() { + let env = Env::default(); + let x: i128 = (i128::MAX - 1) / 2 + 1; + let y: i128 = 2 * 10i128.pow(18); + let z: i128 = 10i128.pow(18); + + scaled_mul_div_floor(&x, &env, &y, &z); + } + + #[test] + #[should_panic] + fn test_scaled_mul_div_floor_result_negative_overflow() { + let env = Env::default(); + let x: i128 = i128::MIN / 2 - 1; + let y: i128 = 2 * 10i128.pow(18); + let z: i128 = 10i128.pow(18); + + scaled_mul_div_floor(&x, &env, &y, &z); + } + + /********** scaled_mul_div_ceil **********/ + + #[test] + fn test_scaled_mul_div_ceil_rounds_up() { + // Real result = 483_5313675.8 let env = Env::default(); let x: i128 = 1_5391283; let y: i128 = 314_1592653; - let denominator: i128 = 1_0000001; + let z: i128 = 1_0000001; - let result = x.fixed_mul_floor(&env, &y, &denominator); + let result = scaled_mul_div_ceil(&x, &env, &y, &z); + + assert_eq!(result, 483_5313676); + } + + #[test] + fn test_scaled_mul_div_ceil_exact() { + // Real result = 12 + let env = Env::default(); + let x: i128 = 8; + let y: i128 = 3; + let z: i128 = 2; + + let result = scaled_mul_div_ceil(&x, &env, &y, &z); + + assert_eq!(result, 12); + } + + #[test] + fn test_scaled_mul_div_ceil_negative_exact() { + // Real result = -12 + let env = Env::default(); + let x: i128 = 8; + let y: i128 = -3; + let z: i128 = 2; + + let result = scaled_mul_div_ceil(&x, &env, &y, &z); - assert_eq!(result, 483_5313675) + assert_eq!(result, -12); } #[test] - fn test_fixed_mul_floor_negative_rounds_down() { + fn test_scaled_mul_div_ceil_mul_negative_rounds_up() { + // Real result = -483_5313675.8 let env = Env::default(); let x: i128 = -1_5391283; let y: i128 = 314_1592653; - let denominator: i128 = 1_0000001; + let z: i128 = 1_0000001; - let result = x.fixed_mul_floor(&env, &y, &denominator); + let result = scaled_mul_div_ceil(&x, &env, &y, &z); - assert_eq!(result, -483_5313676) + assert_eq!(result, -483_5313675); } #[test] - fn test_fixed_mul_floor_phantom_overflow_scales() { + fn test_scaled_mul_div_ceil_div_negative_rounds_up() { + // Real result = -483_5313675.8 let env = Env::default(); - let x: i128 = 170_141_183_460_469_231_731; - let y: i128 = 10i128.pow(27); - let denominator: i128 = 10i128.pow(18); + let x: i128 = 1_5391283; + let y: i128 = 314_1592653; + let z: i128 = -1_0000001; - let result = x.fixed_mul_floor(&env, &y, &denominator); + let result = scaled_mul_div_ceil(&x, &env, &y, &z); - assert_eq!(result, 170_141_183_460_469_231_731 * 10i128.pow(9)); + assert_eq!(result, -483_5313675); } - /********** fixed_mul_ceil **********/ + #[test] + fn test_scaled_mul_div_ceil_x_y_negative_rounds_up() { + // Real result = 483_5313675.8 + let env = Env::default(); + let x: i128 = -1_5391283; + let y: i128 = -314_1592653; + let z: i128 = 1_0000001; + + let result = scaled_mul_div_ceil(&x, &env, &y, &z); + + assert_eq!(result, 483_5313676); + } #[test] - fn test_fixed_mul_ceil_rounds_up() { + fn test_scaled_mul_div_ceil_y_z_negative_rounds_up() { + // Real result = 483_5313675.8 let env = Env::default(); let x: i128 = 1_5391283; - let y: i128 = 314_1592653; - let denominator: i128 = 1_0000001; + let y: i128 = -314_1592653; + let z: i128 = -1_0000001; - let result = x.fixed_mul_ceil(&env, &y, &denominator); + let result = scaled_mul_div_ceil(&x, &env, &y, &z); - assert_eq!(result, 483_5313676) + assert_eq!(result, 483_5313676); } #[test] - fn test_fixed_mul_ceil_negative_rounds_up() { + fn test_scaled_mul_div_ceil_all_negative_rounds_up() { + // Real result = -483_5313675.8 let env = Env::default(); let x: i128 = -1_5391283; - let y: i128 = 314_1592653; - let denominator: i128 = 1_0000001; + let y: i128 = -314_1592653; + let z: i128 = -1_0000001; - let result = x.fixed_mul_ceil(&env, &y, &denominator); + let result = scaled_mul_div_ceil(&x, &env, &y, &z); + + assert_eq!(result, -483_5313675); + } - assert_eq!(result, -483_5313675) + #[test] + fn test_scaled_mul_div_ceil_mul_zero() { + let env = Env::default(); + let x: i128 = 1_5391283; + let y: i128 = 0; + let z: i128 = 1_0000001; + + let result = scaled_mul_div_ceil(&x, &env, &y, &z); + + assert_eq!(result, 0); } #[test] - fn test_fixed_mul_ceil_large_number() { + #[should_panic] + fn test_scaled_mul_div_ceil_div_zero() { + let env = Env::default(); + let x: i128 = 1_5391283; + let y: i128 = 314_1592653; + let z: i128 = 0; + + scaled_mul_div_ceil(&x, &env, &y, &z); + } + + #[test] + fn test_scaled_mul_div_ceil_large_number() { let env = Env::default(); let x: i128 = 170_141_183_460_469_231_731; let y: i128 = 1_000_000_000_000_000_000; - let denominator: i128 = 1_000_000_000_000_000_000; + let z: i128 = 1_000_000_000_000_000_000; + + let result = scaled_mul_div_ceil(&x, &env, &y, &z); + + assert_eq!(result, 170_141_183_460_469_231_731); + } + + #[test] + fn test_scaled_mul_div_ceil_negative_large_number() { + let env = Env::default(); + let x: i128 = -170_141_183_460_469_231_731; + let y: i128 = 1_000_000_000_000_000_000; + let z: i128 = 1_000_000_000_000_000_000; + + let result = scaled_mul_div_ceil(&x, &env, &y, &z); + + assert_eq!(result, -170_141_183_460_469_231_731); + } + + #[test] + fn test_scaled_mul_div_ceil_small_number() { + let env = Env::default(); + let x: i128 = 1; + let y: i128 = 2; + let z: i128 = 3; + + let result = scaled_mul_div_ceil(&x, &env, &y, &z); + + assert_eq!(result, 1); + } + + #[test] + fn test_scaled_mul_div_ceil_negative_small_number() { + let env = Env::default(); + let x: i128 = -1; + let y: i128 = 2; + let z: i128 = 3; + + let result = scaled_mul_div_ceil(&x, &env, &y, &z); + + assert_eq!(result, 0); + } + + #[test] + fn test_scaled_mul_div_ceil_phantom_overflow_uses_i256() { + // i128::MAX is odd + let env = Env::default(); + let x: i128 = (i128::MAX - 1) / 2; + let y: i128 = 2 * 10i128.pow(18); + let z: i128 = 10i128.pow(18); + + let result = scaled_mul_div_ceil(&x, &env, &y, &z); + + assert_eq!(result, i128::MAX - 1); + } + + #[test] + fn test_scaled_mul_div_ceil_negative_phantom_overflow_uses_i256() { + let env = Env::default(); + let x: i128 = i128::MIN / 2; + let y: i128 = 2 * 10i128.pow(18); + let z: i128 = 10i128.pow(18); + + let result = scaled_mul_div_ceil(&x, &env, &y, &z); + + assert_eq!(result, i128::MIN); + } + + #[test] + fn test_scaled_mul_div_ceil_phantom_overflow_rounds_up() { + // Real Result = 333_3x18.3.. + let env = Env::default(); + let x: i128 = 100 * 10i128.pow(18); + let y: i128 = 10 * 10i128.pow(18); + let z: i128 = 3 * 10i128.pow(18); + + let result = scaled_mul_div_ceil(&x, &env, &y, &z); + + assert_eq!(result, 333_333_333_333_333_333_334); + } + + #[test] + #[should_panic] + fn test_scaled_mul_div_ceil_result_overflow() { + // i128::MAX is odd + let env = Env::default(); + let x: i128 = (i128::MAX - 1) / 2 + 1; + let y: i128 = 2 * 10i128.pow(18); + let z: i128 = 10i128.pow(18); + + scaled_mul_div_ceil(&x, &env, &y, &z); + } + + #[test] + #[should_panic] + fn test_scaled_mul_div_ceil_result_negative_overflow() { + let env = Env::default(); + let x: i128 = i128::MIN / 2 - 1; + let y: i128 = 2 * 10i128.pow(18); + let z: i128 = 10i128.pow(18); + + scaled_mul_div_ceil(&x, &env, &y, &z); + } + + /********** fixed_mul_floor **********/ + + #[test] + fn test_fixed_mul_floor() { + // Real result = 104_9522835.2 + let env = Env::default(); + let x: i128 = 3_1423141; + let y: i128 = 4_1234142; + let denominator: i128 = 0_1234567; + + let result = x.fixed_mul_floor(&env, &y, &denominator); + assert_eq!(result, 104_9522835); + + let result = (-x).fixed_mul_floor(&env, &y, &denominator); + assert_eq!(result, -104_9522836); + + let result = (-x).fixed_mul_floor(&env, &(-y), &denominator); + assert_eq!(result, 104_9522835); + } + + #[test] + fn test_fixed_mul_floor_uses_i256() { + // Real result = 246800e18 + let env = Env::default(); + let x: i128 = 1234 * 10i128.pow(18); + let y: i128 = 200 * 10i128.pow(18); + let denominator: i128 = 10i128.pow(18); + + let result = x.fixed_mul_floor(&env, &y, &denominator); + assert_eq!(result, 246800 * 10i128.pow(18)); + } + + #[test] + #[should_panic] + fn test_fixed_mul_floor_panics() { + let env = Env::default(); + let x: i128 = 10i128.pow(7); + + x.fixed_mul_floor(&env, &x, &0); + } + + /********** fixed_mul_ceil **********/ + + #[test] + fn test_fixed_mul_ceil() { + // Real result = 104_9522835.2 + let env = Env::default(); + let x: i128 = 3_1423141; + let y: i128 = 4_1234142; + let denominator: i128 = 0_1234567; let result = x.fixed_mul_ceil(&env, &y, &denominator); + assert_eq!(result, 104_9522836); + + let result = (-x).fixed_mul_ceil(&env, &y, &denominator); + assert_eq!(result, -104_9522835); - assert_eq!(result, 170_141_183_460_469_231_731) + let result = (-x).fixed_mul_ceil(&env, &(-y), &denominator); + assert_eq!(result, 104_9522836); } #[test] - fn test_fixed_mul_ceil_phantom_overflow_scales() { + fn test_fixed_mul_ceil_uses_i256() { + // Real result = 246800e18 let env = Env::default(); - let x: i128 = 170_141_183_460_469_231_731; - let y: i128 = 10i128.pow(27); + let x: i128 = 1234 * 10i128.pow(18); + let y: i128 = 200 * 10i128.pow(18); let denominator: i128 = 10i128.pow(18); let result = x.fixed_mul_ceil(&env, &y, &denominator); + assert_eq!(result, 246800 * 10i128.pow(18)); + } + + #[test] + #[should_panic] + fn test_fixed_mul_ceil_panics() { + let env = Env::default(); + let x: i128 = 10i128.pow(7); - assert_eq!(result, 170_141_183_460_469_231_731 * 10i128.pow(9)); + x.fixed_mul_ceil(&env, &x, &0); } /********** fixed_div_floor **********/ #[test] - fn test_fixed_div_floor_rounds_down() { + fn test_fixed_div_floor() { + // Real result = 204_1150997.8 let env = Env::default(); let x: i128 = 314_1592653; let y: i128 = 1_5391280; let denominator: i128 = 1_0000000; let result = x.fixed_div_floor(&env, &y, &denominator); + assert_eq!(result, 204_1150997); - assert_eq!(result, 204_1150997) + let result = (-x).fixed_div_floor(&env, &y, &denominator); + assert_eq!(result, -204_1150998); + + let result = (-x).fixed_div_floor(&env, &(-y), &denominator); + assert_eq!(result, 204_1150997); } #[test] - fn test_fixed_div_floor_negative_rounds_down() { + fn test_fixed_div_floor_uses_i256() { + // Real result = 5000e18 let env = Env::default(); - let x: i128 = 314_1592653; - let y: i128 = -1_5391280; - let denominator: i128 = 1_0000000; + let x: i128 = 1000 * 10i128.pow(18); + let y: i128 = 2 * 10i128.pow(17); + let denominator: i128 = 10i128.pow(18); let result = x.fixed_div_floor(&env, &y, &denominator); - - assert_eq!(result, -204_1150998) + assert_eq!(result, 5000 * 10i128.pow(18)); } #[test] - fn test_fixed_div_floor_phantom_overflow_scales() { + #[should_panic] + fn test_fixed_div_floor_panics() { let env = Env::default(); - let x: i128 = 170_141_183_460_469_231_731; - let y: i128 = 10i128.pow(18); - let denominator: i128 = 10i128.pow(27); + let x: i128 = 10i128.pow(7); - let result = x.fixed_div_floor(&env, &y, &denominator); - - assert_eq!(result, 170_141_183_460_469_231_731 * 10i128.pow(9)); + x.fixed_div_floor(&env, &0, &x); } /********** fixed_div_ceil **********/ #[test] - fn test_fixed_div_ceil_rounds_down() { + fn test_fixed_div_ceil() { + // Real result = 204_1150997.8 let env = Env::default(); let x: i128 = 314_1592653; let y: i128 = 1_5391280; let denominator: i128 = 1_0000000; let result = x.fixed_div_ceil(&env, &y, &denominator); + assert_eq!(result, 204_1150998); - assert_eq!(result, 204_1150998) - } + let result = (-x).fixed_div_ceil(&env, &y, &denominator); + assert_eq!(result, -204_1150997); - #[test] - fn test_fixed_div_ceil_negative_rounds_down() { - let env = Env::default(); - let x: i128 = 314_1592653; - let y: i128 = -1_5391280; - let denominator: i128 = 1_0000000; - - let result = x.fixed_div_ceil(&env, &y, &denominator); - - assert_eq!(result, -204_1150997) + let result = (-x).fixed_div_ceil(&env, &(-y), &denominator); + assert_eq!(result, 204_1150998); } #[test] - fn test_fixed_div_ceil_large_number() { + fn test_fixed_div_ceil_uses_i256() { + // Real result = 5000e18 let env = Env::default(); - let x: i128 = 170_141_183_460_469_231_731; - let y: i128 = 1_000_000_000_000_000_000; - let denominator: i128 = 1_000_000_000_000_000_000; + let x: i128 = 1000 * 10i128.pow(18); + let y: i128 = 2 * 10i128.pow(17); + let denominator: i128 = 10i128.pow(18); let result = x.fixed_div_ceil(&env, &y, &denominator); - - assert_eq!(result, 170_141_183_460_469_231_731) + assert_eq!(result, 5000 * 10i128.pow(18)); } #[test] - fn test_fixed_div_ceil_phantom_overflow_scales() { + #[should_panic] + fn test_fixed_div_ceil_panics() { let env = Env::default(); - let x: i128 = 170_141_183_460_469_231_731; - let y: i128 = 10i128.pow(18); - let denominator: i128 = 10i128.pow(27); - - let result = x.fixed_div_floor(&env, &y, &denominator); + let x: i128 = 10i128.pow(7); - assert_eq!(result, 170_141_183_460_469_231_731 * 10i128.pow(9)); + x.fixed_div_ceil(&env, &0, &x); } }
src/i256.rs+523 −105 modified@@ -23,23 +23,23 @@ impl SorobanFixedPoint for I256 { /// Performs floor(x * y / z) pub(crate) fn mul_div_floor(env: &Env, x: &I256, y: &I256, z: &I256) -> I256 { let zero = I256::from_i32(env, 0); - let r = x.mul(&y); - if r < zero || (r > zero && z.clone() < zero) { + let r = x.mul(y); + if (r < zero && z > &zero) || (r > zero && z < &zero) { // ceiling is taken by default for a negative result - let remainder = r.rem_euclid(&z); + let remainder = r.rem_euclid(z); let one = I256::from_i32(env, 1); - r.div(&z).sub(if remainder > zero { &one } else { &zero }) + r.div(z).sub(if remainder > zero { &one } else { &zero }) } else { // floor taken by default for a positive or zero result - r.div(&z) + r.div(z) } } /// Performs ceil(x * y / z) pub(crate) fn mul_div_ceil(env: &Env, x: &I256, y: &I256, z: &I256) -> I256 { let zero = I256::from_i32(env, 0); let r = x.mul(&y); - if r <= zero || (r > zero && z.clone() < zero) { + if r == zero || (r < zero && z > &zero) || (r > zero && z < &zero) { // ceiling is taken by default for a negative or zero result r.div(&z) } else { @@ -52,209 +52,627 @@ pub(crate) fn mul_div_ceil(env: &Env, x: &I256, y: &I256, z: &I256) -> I256 { #[cfg(test)] mod tests { + use soroban_sdk::Bytes; + use super::*; - /********** fixed_mul_floor **********/ + /// Helper to create I256::MAX (0x7F followed by 31 0xFF bytes) + fn i256_max(env: &Env) -> I256 { + let mut bytes = [0xFFu8; 32]; + bytes[0] = 0x7F; + I256::from_be_bytes(env, &Bytes::from_array(env, &bytes)) + } + + /// Helper to create I256::MIN (0x80 followed by 31 0x00 bytes) + fn i256_min(env: &Env) -> I256 { + let mut bytes = [0x00u8; 32]; + bytes[0] = 0x80; + I256::from_be_bytes(env, &Bytes::from_array(env, &bytes)) + } + + /********** mul_div_floor **********/ #[test] - fn test_fixed_mul_floor_rounds_down() { + fn test_mul_div_floor_rounds_down() { + // Real result = 483_5313675.8 let env = Env::default(); - let x: I256 = I256::from_i128(&env, 1_5391283); - let y: I256 = I256::from_i128(&env, 314_1592653); - let denominator: I256 = I256::from_i128(&env, 1_0000001); + let x = I256::from_i128(&env, 1_5391283); + let y = I256::from_i128(&env, 314_1592653); + let z = I256::from_i128(&env, 1_0000001); - let result = x.fixed_mul_floor(&env, &y, &denominator); + let result = mul_div_floor(&env, &x, &y, &z); assert_eq!(result, I256::from_i128(&env, 483_5313675)); } #[test] - fn test_fixed_mul_floor_negative_rounds_down() { + fn test_mul_div_floor_exact() { + // Real result = 12 let env = Env::default(); - let x: I256 = I256::from_i128(&env, -1_5391283); - let y: I256 = I256::from_i128(&env, 314_1592653); - let denominator: I256 = I256::from_i128(&env, 1_0000001); + let x = I256::from_i32(&env, 8); + let y = I256::from_i32(&env, 3); + let z = I256::from_i32(&env, 2); - let result = x.fixed_mul_floor(&env, &y, &denominator); + let result = mul_div_floor(&env, &x, &y, &z); + + assert_eq!(result, I256::from_i32(&env, 12)); + } + + #[test] + fn test_mul_div_floor_negative_exact() { + // Real result = -12 + let env = Env::default(); + let x = I256::from_i32(&env, 8); + let y = I256::from_i32(&env, -3); + let z = I256::from_i32(&env, 2); + + let result = mul_div_floor(&env, &x, &y, &z); + + assert_eq!(result, I256::from_i32(&env, -12)); + } + + #[test] + fn test_mul_div_floor_mul_negative_rounds_down() { + // Real result = -483_5313675.8 + let env = Env::default(); + let x = I256::from_i128(&env, -1_5391283); + let y = I256::from_i128(&env, 314_1592653); + let z = I256::from_i128(&env, 1_0000001); + + let result = mul_div_floor(&env, &x, &y, &z); assert_eq!(result, I256::from_i128(&env, -483_5313676)); } #[test] - fn test_fixed_mul_floor_large_number() { + fn test_mul_div_floor_div_negative_rounds_down() { + // Real result = -483_5313675.8 let env = Env::default(); - let x: I256 = I256::from_i128(&env, i128::MAX); - let y: I256 = I256::from_i128(&env, 10i128.pow(38)); - let denominator: I256 = I256::from_i128(&env, 10i128.pow(18)); + let x = I256::from_i128(&env, 1_5391283); + let y = I256::from_i128(&env, 314_1592653); + let z = I256::from_i128(&env, -1_0000001); - let result = x.clone().fixed_mul_floor(&env, &y, &denominator); + let result = mul_div_floor(&env, &x, &y, &z); - let expected_result = x.mul(&I256::from_i128(&env, 10i128.pow(20))); - assert_eq!(result, expected_result); + assert_eq!(result, I256::from_i128(&env, -483_5313676)); } #[test] - #[should_panic(expected = "attempt to multiply with overflow")] - fn test_fixed_mul_floor_phantom_overflow() { + fn test_mul_div_floor_x_y_negative_rounds_down() { + // Real result = 483_5313675.8 let env = Env::default(); - let x: I256 = I256::from_i128(&env, i128::MAX); - // 256 bit max ~= 5.8e76, 128 bit max ~= 1.7e38, need to multiply by at least 10^39 - let y: I256 = I256::from_i128(&env, 10i128.pow(39)); - let denominator: I256 = I256::from_i128(&env, 10i128.pow(18)); + let x = I256::from_i128(&env, -1_5391283); + let y = I256::from_i128(&env, -314_1592653); + let z = I256::from_i128(&env, 1_0000001); + + let result = mul_div_floor(&env, &x, &y, &z); - x.fixed_mul_floor(&env, &y, &denominator); + assert_eq!(result, I256::from_i128(&env, 483_5313675)); } - /********** fixed_mul_ceil **********/ + #[test] + fn test_mul_div_floor_y_z_negative_rounds_down() { + // Real result = 483_5313675.8 + let env = Env::default(); + let x = I256::from_i128(&env, 1_5391283); + let y = I256::from_i128(&env, -314_1592653); + let z = I256::from_i128(&env, -1_0000001); + + let result = mul_div_floor(&env, &x, &y, &z); + + assert_eq!(result, I256::from_i128(&env, 483_5313675)); + } #[test] - fn test_fixed_mul_ceil_rounds_up() { + fn test_mul_div_floor_all_negative_rounds_down() { + // Real result = -483_5313675.8 let env = Env::default(); - let x: I256 = I256::from_i128(&env, 1_5391283); - let y: I256 = I256::from_i128(&env, 314_1592653); - let denominator: I256 = I256::from_i128(&env, 1_0000001); + let x = I256::from_i128(&env, -1_5391283); + let y = I256::from_i128(&env, -314_1592653); + let z = I256::from_i128(&env, -1_0000001); - let result = x.fixed_mul_ceil(&env, &y, &denominator); + let result = mul_div_floor(&env, &x, &y, &z); + + assert_eq!(result, I256::from_i128(&env, -483_5313676)); + } + + #[test] + fn test_mul_div_floor_mul_zero() { + let env = Env::default(); + let x = I256::from_i128(&env, 1_5391283); + let y = I256::from_i128(&env, 0); + let z = I256::from_i128(&env, 1_0000001); + + let result = mul_div_floor(&env, &x, &y, &z); + + assert_eq!(result, I256::from_i128(&env, 0)); + } + + #[test] + #[should_panic] + fn test_mul_div_floor_div_zero() { + let env = Env::default(); + let x = I256::from_i128(&env, 1_5391283); + let y = I256::from_i128(&env, 314_1592653); + let z = I256::from_i128(&env, 0); + + mul_div_floor(&env, &x, &y, &z); + } + + #[test] + fn test_mul_div_floor_large_number() { + let env = Env::default(); + let x = i256_max(&env).div(&I256::from_i128(&env, 10i128.pow(36))); + let y = I256::from_i128(&env, 10i128.pow(36)); + let z = I256::from_i128(&env, 10i128.pow(36)); + + let result = mul_div_floor(&env, &x, &y, &z); + + assert_eq!(result, x); + } + + #[test] + fn test_mul_div_floor_negative_large_number() { + let env = Env::default(); + let x = i256_min(&env).div(&I256::from_i128(&env, 10i128.pow(36))); + let y = I256::from_i128(&env, 10i128.pow(36)); + let z = I256::from_i128(&env, 10i128.pow(36)); + + let result = mul_div_floor(&env, &x, &y, &z); + + assert_eq!(result, x); + } + + #[test] + fn test_mul_div_floor_small_number() { + let env = Env::default(); + let x = I256::from_i32(&env, 1); + let y = I256::from_i32(&env, 2); + let z = I256::from_i32(&env, 3); + + let result = mul_div_floor(&env, &x, &y, &z); + + assert_eq!(result, I256::from_i32(&env, 0)); + } + + #[test] + fn test_mul_div_floor_negative_small_number() { + let env = Env::default(); + let x = I256::from_i32(&env, -1); + let y = I256::from_i32(&env, 2); + let z = I256::from_i32(&env, 3); + + let result = mul_div_floor(&env, &x, &y, &z); + + assert_eq!(result, I256::from_i32(&env, -1)); + } + + #[test] + #[should_panic] + fn test_mul_div_floor_overflow() { + let env = Env::default(); + let x = i256_max(&env) + .div(&I256::from_i128(&env, 10i128.pow(36))) + .add(&I256::from_i32(&env, 1)); + let y = I256::from_i128(&env, 10i128.pow(36)); + let z = I256::from_i128(&env, 10i128.pow(36)); + + mul_div_floor(&env, &x, &y, &z); + } + + #[test] + #[should_panic] + fn test_mul_div_floor_negative_overflow() { + let env = Env::default(); + let x = i256_min(&env) + .div(&I256::from_i128(&env, 10i128.pow(36))) + .add(&I256::from_i32(&env, -1)); + let y = I256::from_i128(&env, 10i128.pow(36)); + let z = I256::from_i128(&env, 10i128.pow(36)); + + mul_div_floor(&env, &x, &y, &z); + } + + /********** mul_div_ceil **********/ + + #[test] + fn test_mul_div_ceil_rounds_up() { + // Real result = 483_5313675.8 + let env = Env::default(); + let x = I256::from_i128(&env, 1_5391283); + let y = I256::from_i128(&env, 314_1592653); + let z = I256::from_i128(&env, 1_0000001); + + let result = mul_div_ceil(&env, &x, &y, &z); assert_eq!(result, I256::from_i128(&env, 483_5313676)); } #[test] - fn test_fixed_mul_ceil_negative_rounds_up() { + fn test_mul_div_ceil_exact() { + // Real result = 12 let env = Env::default(); - let x: I256 = I256::from_i128(&env, -1_5391283); - let y: I256 = I256::from_i128(&env, 314_1592653); - let denominator: I256 = I256::from_i128(&env, 1_0000001); + let x = I256::from_i32(&env, 8); + let y = I256::from_i32(&env, 3); + let z = I256::from_i32(&env, 2); - let result = x.fixed_mul_ceil(&env, &y, &denominator); + let result = mul_div_ceil(&env, &x, &y, &z); + + assert_eq!(result, I256::from_i32(&env, 12)); + } + + #[test] + fn test_mul_div_ceil_negative_exact() { + // Real result = -12 + let env = Env::default(); + let x = I256::from_i32(&env, 8); + let y = I256::from_i32(&env, -3); + let z = I256::from_i32(&env, 2); + + let result = mul_div_ceil(&env, &x, &y, &z); + + assert_eq!(result, I256::from_i32(&env, -12)); + } + + #[test] + fn test_mul_div_ceil_mul_negative_rounds_up() { + // Real result = -483_5313675.8 + let env = Env::default(); + let x = I256::from_i128(&env, -1_5391283); + let y = I256::from_i128(&env, 314_1592653); + let z = I256::from_i128(&env, 1_0000001); + + let result = mul_div_ceil(&env, &x, &y, &z); assert_eq!(result, I256::from_i128(&env, -483_5313675)); } #[test] - fn test_fixed_mul_ceil_large_number() { + fn test_mul_div_ceil_div_negative_rounds_up() { + // Real result = -483_5313675.8 let env = Env::default(); - let x: I256 = I256::from_i128(&env, i128::MAX); - let y: I256 = I256::from_i128(&env, 10i128.pow(38)); - let denominator: I256 = I256::from_i128(&env, 10i128.pow(18)); + let x = I256::from_i128(&env, 1_5391283); + let y = I256::from_i128(&env, 314_1592653); + let z = I256::from_i128(&env, -1_0000001); - let result = x.clone().fixed_mul_ceil(&env, &y, &denominator); + let result = mul_div_ceil(&env, &x, &y, &z); - let expected_result = x.mul(&I256::from_i128(&env, 10i128.pow(20))); - assert_eq!(result, expected_result); + assert_eq!(result, I256::from_i128(&env, -483_5313675)); } #[test] - #[should_panic(expected = "attempt to multiply with overflow")] - fn test_fixed_mul_ceil_phantom_overflow() { + fn test_mul_div_ceil_x_y_negative_rounds_up() { + // Real result = 483_5313675.8 let env = Env::default(); - let x: I256 = I256::from_i128(&env, i128::MAX); - // 256 bit max ~= 5.8e76, 128 bit max ~= 1.7e38, need to multiply by at least 10^39 - let y: I256 = I256::from_i128(&env, 10i128.pow(39)); - let denominator: I256 = I256::from_i128(&env, 10i128.pow(18)); + let x = I256::from_i128(&env, -1_5391283); + let y = I256::from_i128(&env, -314_1592653); + let z = I256::from_i128(&env, 1_0000001); + + let result = mul_div_ceil(&env, &x, &y, &z); - x.fixed_mul_ceil(&env, &y, &denominator); + assert_eq!(result, I256::from_i128(&env, 483_5313676)); } - /********** fixed_div_floor **********/ + #[test] + fn test_mul_div_ceil_y_z_negative_rounds_up() { + // Real result = 483_5313675.8 + let env = Env::default(); + let x = I256::from_i128(&env, 1_5391283); + let y = I256::from_i128(&env, -314_1592653); + let z = I256::from_i128(&env, -1_0000001); + + let result = mul_div_ceil(&env, &x, &y, &z); + + assert_eq!(result, I256::from_i128(&env, 483_5313676)); + } #[test] - fn test_fixed_div_floor_rounds_down() { + fn test_mul_div_ceil_all_negative_rounds_up() { + // Real result = -483_5313675.8 let env = Env::default(); - let x: I256 = I256::from_i128(&env, 314_1592653); - let y: I256 = I256::from_i128(&env, 1_5391280); - let denominator: I256 = I256::from_i128(&env, 1_0000000); + let x = I256::from_i128(&env, -1_5391283); + let y = I256::from_i128(&env, -314_1592653); + let z = I256::from_i128(&env, -1_0000001); - let result = x.fixed_div_floor(&env, &y, &denominator); + let result = mul_div_ceil(&env, &x, &y, &z); - assert_eq!(result, I256::from_i128(&env, 204_1150997)); + assert_eq!(result, I256::from_i128(&env, -483_5313675)); } #[test] - fn test_fixed_div_floor_negative_rounds_down() { + fn test_mul_div_ceil_mul_zero() { let env = Env::default(); - let x: I256 = I256::from_i128(&env, 314_1592653); - let y: I256 = I256::from_i128(&env, -1_5391280); - let denominator: I256 = I256::from_i128(&env, 1_0000000); + let x = I256::from_i128(&env, 1_5391283); + let y = I256::from_i32(&env, 0); + let z = I256::from_i128(&env, 1_0000001); - let result = x.fixed_div_floor(&env, &y, &denominator); + let result = mul_div_ceil(&env, &x, &y, &z); - assert_eq!(result, I256::from_i128(&env, -204_1150998)); + assert_eq!(result, I256::from_i32(&env, 0)); } #[test] - fn test_fixed_div_floor_large_number() { + #[should_panic] + fn test_mul_div_ceil_div_zero() { + let env = Env::default(); + let x = I256::from_i128(&env, 1_5391283); + let y = I256::from_i128(&env, 314_1592653); + let z = I256::from_i32(&env, 0); + + mul_div_ceil(&env, &x, &y, &z); + } + + #[test] + fn test_mul_div_ceil_large_number() { + let env = Env::default(); + let x = i256_max(&env).div(&I256::from_i128(&env, 10i128.pow(36))); + let y = I256::from_i128(&env, 10i128.pow(36)); + let z = I256::from_i128(&env, 10i128.pow(36)); + + let result = mul_div_ceil(&env, &x, &y, &z); + + assert_eq!(result, x); + } + + #[test] + fn test_mul_div_ceil_negative_large_number() { + let env = Env::default(); + let x = i256_min(&env).div(&I256::from_i128(&env, 10i128.pow(36))); + let y = I256::from_i128(&env, 10i128.pow(36)); + let z = I256::from_i128(&env, 10i128.pow(36)); + + let result = mul_div_ceil(&env, &x, &y, &z); + + assert_eq!(result, x); + } + + #[test] + fn test_mul_div_ceil_small_number() { + let env = Env::default(); + let x = I256::from_i32(&env, 1); + let y = I256::from_i32(&env, 2); + let z = I256::from_i32(&env, 3); + + let result = mul_div_ceil(&env, &x, &y, &z); + + assert_eq!(result, I256::from_i32(&env, 1)); + } + + #[test] + fn test_mul_div_ceil_negative_small_number() { + let env = Env::default(); + let x = I256::from_i32(&env, -1); + let y = I256::from_i32(&env, 2); + let z = I256::from_i32(&env, 3); + + let result = mul_div_ceil(&env, &x, &y, &z); + + assert_eq!(result, I256::from_i32(&env, 0)); + } + + #[test] + #[should_panic] + fn test_mul_div_ceil_overflow() { + let env = Env::default(); + let x = i256_max(&env) + .div(&I256::from_i128(&env, 10i128.pow(36))) + .add(&I256::from_i32(&env, 1)); + let y = I256::from_i128(&env, 10i128.pow(36)); + let z = I256::from_i128(&env, 10i128.pow(36)); + + mul_div_ceil(&env, &x, &y, &z); + } + + #[test] + #[should_panic] + fn test_mul_div_ceil_negative_overflow() { + let env = Env::default(); + let x = i256_min(&env) + .div(&I256::from_i128(&env, 10i128.pow(36))) + .add(&I256::from_i32(&env, -1)); + let y = I256::from_i128(&env, 10i128.pow(36)); + let z = I256::from_i128(&env, 10i128.pow(36)); + + mul_div_ceil(&env, &x, &y, &z); + } + + /********** fixed_mul_floor **********/ + + #[test] + fn test_fixed_mul_floor() { + // Real result = 104_9522835.2 + let env = Env::default(); + let x = I256::from_i128(&env, 3_1423141); + let y = I256::from_i128(&env, 4_1234142); + let denominator = I256::from_i128(&env, 0_1234567); + + let result = x.fixed_mul_floor(&env, &y, &denominator); + assert_eq!(result, I256::from_i128(&env, 104_9522835)); + + let result = x + .mul(&I256::from_i32(&env, -1)) + .fixed_mul_floor(&env, &y, &denominator); + assert_eq!(result, I256::from_i128(&env, -104_9522836)); + + let result = x.mul(&I256::from_i32(&env, -1)).fixed_mul_floor( + &env, + &y.mul(&I256::from_i32(&env, -1)), + &denominator, + ); + assert_eq!(result, I256::from_i128(&env, 104_9522835)); + } + + #[test] + fn test_fixed_mul_floor_large_number() { let env = Env::default(); - let x: I256 = I256::from_i128(&env, i128::MAX); - let y: I256 = I256::from_i128(&env, 10i128.pow(27)); - let denominator: I256 = I256::from_i128(&env, 10i128.pow(38)); + let x = I256::from_i128(&env, i128::MAX); + let y = I256::from_i128(&env, 10i128.pow(38)); + let denominator = I256::from_i128(&env, 10i128.pow(18)); - let result = x.clone().fixed_div_floor(&env, &y, &denominator); + let result = x.fixed_mul_floor(&env, &y, &denominator); - let expected_result = x.mul(&I256::from_i128(&env, 10i128.pow(11))); + let expected_result = + I256::from_i128(&env, i128::MAX).mul(&I256::from_i128(&env, 10i128.pow(20))); assert_eq!(result, expected_result); } #[test] - #[should_panic(expected = "attempt to multiply with overflow")] - fn test_fixed_div_floor_phantom_overflow() { + #[should_panic] + fn test_fixed_mul_floor_panics() { let env = Env::default(); - let x: I256 = I256::from_i128(&env, i128::MAX); - let y: I256 = I256::from_i128(&env, 10i128.pow(27)); - // 256 bit max ~= 5.8e76, 128 bit max ~= 1.7e38, need to multiply by at least 10^39 - let denominator: I256 = I256::from_i128(&env, 10i128.pow(39)); + let x = I256::from_i128(&env, 10i128.pow(7)); + let zero = I256::from_i32(&env, 0); - x.fixed_div_floor(&env, &y, &denominator); + x.fixed_mul_floor(&env, &x, &zero); } - /********** fixed_div_ceil **********/ + /********** fixed_mul_ceil **********/ #[test] - fn test_fixed_div_ceil_rounds_down() { + fn test_fixed_mul_ceil() { + // Real result = 104_9522835.2 let env = Env::default(); - let x: I256 = I256::from_i128(&env, 314_1592653); - let y: I256 = I256::from_i128(&env, 1_5391280); - let denominator: I256 = I256::from_i128(&env, 1_0000000); + let x = I256::from_i128(&env, 3_1423141); + let y = I256::from_i128(&env, 4_1234142); + let denominator = I256::from_i128(&env, 0_1234567); - let result = x.fixed_div_ceil(&env, &y, &denominator); + let result = x.fixed_mul_ceil(&env, &y, &denominator); + assert_eq!(result, I256::from_i128(&env, 104_9522836)); + + let result = x + .mul(&I256::from_i32(&env, -1)) + .fixed_mul_ceil(&env, &y, &denominator); + assert_eq!(result, I256::from_i128(&env, -104_9522835)); + + let result = x.mul(&I256::from_i32(&env, -1)).fixed_mul_ceil( + &env, + &y.mul(&I256::from_i32(&env, -1)), + &denominator, + ); + assert_eq!(result, I256::from_i128(&env, 104_9522836)); + } - assert_eq!(result, I256::from_i128(&env, 204_1150998)); + #[test] + fn test_fixed_mul_ceil_large_number() { + let env = Env::default(); + let x = I256::from_i128(&env, i128::MAX); + let y = I256::from_i128(&env, 10i128.pow(38)); + let denominator = I256::from_i128(&env, 10i128.pow(18)); + + let result = x.fixed_mul_ceil(&env, &y, &denominator); + + let expected_result = + I256::from_i128(&env, i128::MAX).mul(&I256::from_i128(&env, 10i128.pow(20))); + assert_eq!(result, expected_result); + } + + #[test] + #[should_panic] + fn test_fixed_mul_ceil_panics() { + let env = Env::default(); + let x = I256::from_i128(&env, 10i128.pow(7)); + let zero = I256::from_i32(&env, 0); + + x.fixed_mul_ceil(&env, &x, &zero); } + /********** fixed_div_floor **********/ + #[test] - fn test_fixed_div_ceil_negative_rounds_down() { + fn test_fixed_div_floor() { + // Real result = 204_1150997.8 let env = Env::default(); - let x: I256 = I256::from_i128(&env, 314_1592653); - let y: I256 = I256::from_i128(&env, -1_5391280); - let denominator: I256 = I256::from_i128(&env, 1_0000000); + let x = I256::from_i128(&env, 314_1592653); + let y = I256::from_i128(&env, 1_5391280); + let denominator = I256::from_i128(&env, 1_0000000); + + let result = x.fixed_div_floor(&env, &y, &denominator); + assert_eq!(result, I256::from_i128(&env, 204_1150997)); + + let result = x + .mul(&I256::from_i32(&env, -1)) + .fixed_div_floor(&env, &y, &denominator); + assert_eq!(result, I256::from_i128(&env, -204_1150998)); + + let result = x.mul(&I256::from_i32(&env, -1)).fixed_div_floor( + &env, + &y.mul(&I256::from_i32(&env, -1)), + &denominator, + ); + assert_eq!(result, I256::from_i128(&env, 204_1150997)); + } + + #[test] + fn test_fixed_div_floor_large_number() { + let env = Env::default(); + let x = I256::from_i128(&env, i128::MAX); + let y = I256::from_i128(&env, 10i128.pow(27)); + let denominator = I256::from_i128(&env, 10i128.pow(38)); + + let result = x.fixed_div_floor(&env, &y, &denominator); + + let expected_result = + I256::from_i128(&env, i128::MAX).mul(&I256::from_i128(&env, 10i128.pow(11))); + assert_eq!(result, expected_result); + } + + #[test] + #[should_panic] + fn test_fixed_div_floor_panics() { + let env = Env::default(); + let x = I256::from_i128(&env, 10i128.pow(7)); + let zero = I256::from_i32(&env, 0); + + x.fixed_div_floor(&env, &zero, &x); + } + + /********** fixed_div_ceil **********/ + + #[test] + fn test_fixed_div_ceil() { + // Real result = 204_1150997.8 + let env = Env::default(); + let x = I256::from_i128(&env, 314_1592653); + let y = I256::from_i128(&env, 1_5391280); + let denominator = I256::from_i128(&env, 1_0000000); let result = x.fixed_div_ceil(&env, &y, &denominator); + assert_eq!(result, I256::from_i128(&env, 204_1150998)); + let result = x + .mul(&I256::from_i32(&env, -1)) + .fixed_div_ceil(&env, &y, &denominator); assert_eq!(result, I256::from_i128(&env, -204_1150997)); + + let result = x.mul(&I256::from_i32(&env, -1)).fixed_div_ceil( + &env, + &y.mul(&I256::from_i32(&env, -1)), + &denominator, + ); + assert_eq!(result, I256::from_i128(&env, 204_1150998)); } #[test] fn test_fixed_div_ceil_large_number() { let env = Env::default(); - let x: I256 = I256::from_i128(&env, i128::MAX); - let y: I256 = I256::from_i128(&env, 10i128.pow(27)); - let denominator: I256 = I256::from_i128(&env, 10i128.pow(38)); + let x = I256::from_i128(&env, i128::MAX); + let y = I256::from_i128(&env, 10i128.pow(27)); + let denominator = I256::from_i128(&env, 10i128.pow(38)); - let result = x.clone().fixed_div_ceil(&env, &y, &denominator); + let result = x.fixed_div_ceil(&env, &y, &denominator); - let expected_result = x.mul(&I256::from_i128(&env, 10i128.pow(11))); + let expected_result = + I256::from_i128(&env, i128::MAX).mul(&I256::from_i128(&env, 10i128.pow(11))); assert_eq!(result, expected_result); } #[test] - #[should_panic(expected = "attempt to multiply with overflow")] - fn test_fixed_div_ceil_phantom_overflow() { + #[should_panic] + fn test_fixed_div_ceil_panics() { let env = Env::default(); - let x: I256 = I256::from_i128(&env, i128::MAX); - let y: I256 = I256::from_i128(&env, 10i128.pow(27)); - // 256 bit max ~= 5.8e76, 128 bit max ~= 1.7e38, need to multiply by at least 10^39 - let denominator: I256 = I256::from_i128(&env, 10i128.pow(39)); + let x = I256::from_i128(&env, 10i128.pow(7)); + let zero = I256::from_i32(&env, 0); - x.fixed_div_ceil(&env, &y, &denominator); + x.fixed_div_ceil(&env, &zero, &x); } }
src/i64.rs+451 −95 modified@@ -22,21 +22,24 @@ impl FixedPoint for i64 { fn mul_div_floor(x: i64, y: i64, z: i64) -> Option<i64> { return match x.checked_mul(y) { Some(r) => { - if r < 0 || (r > 0 && z < 0) { + if (r < 0 && z > 0) || (r > 0 && z < 0) { // ceiling is taken by default for a negative result + // if there is any remainder, sub 1 to round floor let remainder = r.checked_rem_euclid(z)?; - (r / z).checked_sub(if remainder > 0 { 1 } else { 0 }) + r.checked_div(z)? + .checked_sub(if remainder > 0 { 1 } else { 0 }) } else { // floor taken by default for a positive or zero result r.checked_div(z) } } None => { let res_i128 = crate::i128::mul_div_floor(x as i128, y as i128, z as i128)?; - if res_i128 > i64::MAX as i128 { - return None; + if let Ok(res_i64) = i64::try_from(res_i128) { + Some(res_i64) + } else { + None } - Some(res_i128 as i64) } }; } @@ -45,210 +48,563 @@ fn mul_div_floor(x: i64, y: i64, z: i64) -> Option<i64> { fn mul_div_ceil(x: i64, y: i64, z: i64) -> Option<i64> { return match x.checked_mul(y) { Some(r) => { - if r <= 0 || (r > 0 && z < 0) { + if r == 0 || (r < 0 && z > 0) || (r > 0 && z < 0) { // ceiling is taken by default for a negative or zero result r.checked_div(z) } else { // floor taken by default for a positive result + // if there is any remainder, add 1 to round ceiling let remainder = r.checked_rem_euclid(z)?; - (r / z).checked_add(if remainder > 0 { 1 } else { 0 }) + r.checked_div(z)? + .checked_add(if remainder > 0 { 1 } else { 0 }) } } None => { let res_i128 = crate::i128::mul_div_ceil(x as i128, y as i128, z as i128)?; - if res_i128 > i64::MAX as i128 { - return None; + if let Ok(res_i64) = i64::try_from(res_i128) { + Some(res_i64) + } else { + None } - Some(res_i128 as i64) } }; } #[cfg(test)] mod tests { + use core::i64; + use super::*; - /********** fixed_mul_floor **********/ + /********** mul_div_floor **********/ #[test] - fn test_fixed_mul_floor_rounds_down() { + fn test_mul_div_floor_rounds_down() { + // Real result = 483_5313675.8 let x: i64 = 1_5391283; let y: i64 = 314_1592653; - let denominator: i64 = 1_0000001; + let z: i64 = 1_0000001; - let result = x.fixed_mul_floor(y, denominator).unwrap(); + let result = mul_div_floor(x, y, z).unwrap(); - assert_eq!(result, 483_5313675) + assert_eq!(result, 483_5313675); } #[test] - fn test_fixed_mul_floor_large_number() { - let x: i64 = 9_223_372_036; - let y: i64 = 1_000_000_000; - let denominator: i64 = 1_000_000_000; + fn test_mul_div_floor_exact() { + // Real result = 12 + let x: i64 = 8; + let y: i64 = 3; + let z: i64 = 2; - let result = x.fixed_mul_floor(y, denominator).unwrap(); + let result = mul_div_floor(x, y, z).unwrap(); - assert_eq!(result, 9_223_372_036) + assert_eq!(result, 12); } #[test] - fn test_fixed_mul_floor_phantom_overflow_uses_i128() { - let x: i64 = 9_223_372_036; - let y: i64 = 2_000_000_000; - let denominator: i64 = 1_000_000_000; + fn test_mul_div_floor_negative_exact() { + // Real result = -12 + let x: i64 = 8; + let y: i64 = -3; + let z: i64 = 2; - let result = x.fixed_mul_floor(y, denominator).unwrap(); + let result = mul_div_floor(x, y, z).unwrap(); - assert_eq!(result, 18_446_744_072); + assert_eq!(result, -12); } #[test] - fn test_fixed_mul_floor_result_overflow() { - let x: i64 = 9_223_372_036_000_000_000; - let y: i64 = 2_000_000_000; - let denominator: i64 = 1_000_000_000; + fn test_mul_div_floor_mul_negative_rounds_down() { + // Real result = -483_5313675.8 + let x: i64 = -1_5391283; + let y: i64 = 314_1592653; + let z: i64 = 1_0000001; - let result = x.fixed_mul_floor(y, denominator); + let result = mul_div_floor(x, y, z).unwrap(); - assert_eq!(result, None); + assert_eq!(result, -483_5313676); } - /********** fixed_mul_ceil **********/ + #[test] + fn test_mul_div_floor_div_negative_rounds_down() { + // Real result = -483_5313675.8 + let x: i64 = 1_5391283; + let y: i64 = 314_1592653; + let z: i64 = -1_0000001; + + let result = mul_div_floor(x, y, z).unwrap(); + + assert_eq!(result, -483_5313676); + } + + #[test] + fn test_mul_div_floor_x_y_negative_rounds_down() { + // Real result = 483_5313675.8 + let x: i64 = -1_5391283; + let y: i64 = -314_1592653; + let z: i64 = 1_0000001; + + let result = mul_div_floor(x, y, z).unwrap(); + + assert_eq!(result, 483_5313675); + } + + #[test] + fn test_mul_div_floor_y_z_negative_rounds_down() { + // Real result = 483_5313675.8 + let x: i64 = 1_5391283; + let y: i64 = -314_1592653; + let z: i64 = -1_0000001; + + let result = mul_div_floor(x, y, z).unwrap(); + + assert_eq!(result, 483_5313675); + } + + #[test] + fn test_mul_div_floor_all_negative_rounds_down() { + // Real result = -483_5313675.8 + let x: i64 = -1_5391283; + let y: i64 = -314_1592653; + let z: i64 = -1_0000001; + + let result = mul_div_floor(x, y, z).unwrap(); + + assert_eq!(result, -483_5313676); + } + + #[test] + fn test_mul_div_floor_mul_zero() { + let x: i64 = 1_5391283; + let y: i64 = 0; + let z: i64 = 1_0000001; + + let result = mul_div_floor(x, y, z).unwrap(); + + assert_eq!(result, 0); + } #[test] - fn test_fixed_mul_ceil_rounds_up() { + fn test_mul_div_floor_div_zero() { let x: i64 = 1_5391283; let y: i64 = 314_1592653; - let denominator: i64 = 1_0000001; + let z: i64 = 0; - let result = x.fixed_mul_ceil(y, denominator).unwrap(); + let result = mul_div_floor(x, y, z); - assert_eq!(result, 483_5313676) + assert_eq!(result, None); } #[test] - fn test_fixed_mul_ceil_large_number() { + fn test_mul_div_floor_large_number() { let x: i64 = 9_223_372_036; let y: i64 = 1_000_000_000; - let denominator: i64 = 1_000_000_000; + let z: i64 = 1_000_000_000; - let result = x.fixed_mul_ceil(y, denominator).unwrap(); + let result = mul_div_floor(x, y, z).unwrap(); assert_eq!(result, 9_223_372_036) } #[test] - fn test_fixed_mul_ceil_phantom_overflow_uses_i128() { - let x: i64 = 9_223_372_036; + fn test_mul_div_floor_negative_large_number() { + let x: i64 = -9_223_372_036; + let y: i64 = 1_000_000_000; + let z: i64 = 1_000_000_000; + + let result = mul_div_floor(x, y, z).unwrap(); + + assert_eq!(result, -9_223_372_036) + } + + #[test] + fn test_mul_div_floor_small_number() { + let x: i64 = 1; + let y: i64 = 2; + let z: i64 = 3; + + let result = mul_div_floor(x, y, z).unwrap(); + + assert_eq!(result, 0) + } + + #[test] + fn test_mul_div_floor_negative_small_number() { + let x: i64 = -1; + let y: i64 = 2; + let z: i64 = 3; + + let result = mul_div_floor(x, y, z).unwrap(); + + assert_eq!(result, -1) + } + + #[test] + fn test_mul_div_floor_phantom_overflow_uses_i128() { + // i64::MAX is odd + let x: i64 = (i64::MAX - 1) / 2; let y: i64 = 2_000_000_000; - let denominator: i64 = 1_000_000_000; + let z: i64 = 1_000_000_000; - let result = x.fixed_mul_ceil(y, denominator).unwrap(); + let result = mul_div_floor(x, y, z).unwrap(); + + assert_eq!(result, i64::MAX - 1); + } + + #[test] + fn test_mul_div_floor_negative_phantom_overflow_uses_i128() { + let x: i64 = i64::MIN / 2; + let y: i64 = 2_000_000_000; + let z: i64 = 1_000_000_000; - assert_eq!(result, 18446744072); + let result = mul_div_floor(x, y, z).unwrap(); + + assert_eq!(result, i64::MIN); } #[test] - fn test_fixed_mul_ceil_result_overflow() { - let x: i64 = 9_223_372_036_000_000_000; + fn test_mul_div_floor_phantom_overflow_rounds_down() { + // Real Result = 333_333_333_333.3.. + let x: i64 = 100 * 10i64.pow(9); + let y: i64 = 10 * 10i64.pow(9); + let z: i64 = 3 * 10i64.pow(9); + + let result = mul_div_floor(x, y, z).unwrap(); + + assert_eq!(result, 333_333_333_333); + } + + #[test] + fn test_mul_div_floor_result_overflow() { + // i64::MAX is odd + let x: i64 = (i64::MAX - 1) / 2 + 1; let y: i64 = 2_000_000_000; - let denominator: i64 = 1_000_000_000; + let z: i64 = 1_000_000_000; - let result = x.fixed_mul_ceil(y, denominator); + let result = mul_div_floor(x, y, z); assert_eq!(result, None); } - /********** fixed_div_floor **********/ + #[test] + fn test_mul_div_floor_result_negative_overflow() { + let x: i64 = i64::MIN / 2 - 1; + let y: i64 = 2_000_000_000; + let z: i64 = 1_000_000_000; + + let result = mul_div_floor(x, y, z); + + assert_eq!(result, None); + } + + /********** mul_div_ceil **********/ #[test] - fn test_fixed_div_floor_rounds_down() { - let x: i64 = 314_1592653; - let y: i64 = 1_5391280; - let denominator: i64 = 1_0000000; + fn test_mul_div_ceil_rounds_up() { + // Real result = 483_5313675.8 + let x: i64 = 1_5391283; + let y: i64 = 314_1592653; + let z: i64 = 1_0000001; - let result = x.fixed_div_floor(y, denominator).unwrap(); + let result = mul_div_ceil(x, y, z).unwrap(); - assert_eq!(result, 204_1150997) + assert_eq!(result, 483_5313676); } #[test] - fn test_fixed_div_floor_large_number() { - let x: i64 = 9_223_372_036; - let y: i64 = 1_000_000_000; - let denominator: i64 = 1_000_000_000; + fn test_mul_div_ceil_exact() { + // Real result = 12 + let x: i64 = 8; + let y: i64 = 3; + let z: i64 = 2; - let result = x.fixed_div_floor(y, denominator).unwrap(); + let result = mul_div_ceil(x, y, z).unwrap(); - assert_eq!(result, 9_223_372_036) + assert_eq!(result, 12); } #[test] - fn test_fixed_div_floor_phantom_overflow_uses_i128() { - let x: i64 = 9_223_372_036; - let y: i64 = 1_000_000_000; - let denominator: i64 = 2_000_000_000; + fn test_mul_div_ceil_negative_exact() { + // Real result = -12 + let x: i64 = 8; + let y: i64 = -3; + let z: i64 = 2; - let result = x.fixed_div_floor(y, denominator).unwrap(); + let result = mul_div_ceil(x, y, z).unwrap(); - assert_eq!(result, 18_446_744_072); + assert_eq!(result, -12); } #[test] - fn test_fixed_div_floor_result_overflow() { - let x: i64 = 9_223_372_036_000_000_000; - let y: i64 = 1_000_000_000; - let denominator: i64 = 2_000_000_000; + fn test_mul_div_ceil_mul_negative_rounds_up() { + // Real result = -483_5313675.8 + let x: i64 = -1_5391283; + let y: i64 = 314_1592653; + let z: i64 = 1_0000001; - let result = x.fixed_div_floor(y, denominator); + let result = mul_div_ceil(x, y, z).unwrap(); - assert_eq!(result, None); + assert_eq!(result, -483_5313675); } - /********** fixed_div_ceil **********/ + #[test] + fn test_mul_div_ceil_div_negative_rounds_up() { + // Real result = -483_5313675.8 + let x: i64 = 1_5391283; + let y: i64 = 314_1592653; + let z: i64 = -1_0000001; + + let result = mul_div_ceil(x, y, z).unwrap(); + + assert_eq!(result, -483_5313675); + } #[test] - fn test_fixed_div_ceil_rounds_up() { - let x: i64 = 314_1592653; - let y: i64 = 1_5391280; - let denominator: i64 = 1_0000000; + fn test_mul_div_ceil_x_y_negative_rounds_up() { + // Real result = 483_5313675.8 + let x: i64 = -1_5391283; + let y: i64 = -314_1592653; + let z: i64 = 1_0000001; - let result = x.fixed_div_ceil(y, denominator).unwrap(); + let result = mul_div_ceil(x, y, z).unwrap(); - assert_eq!(result, 204_1150998) + assert_eq!(result, 483_5313676); } #[test] - fn test_fixed_div_ceil_large_number() { + fn test_mul_div_ceil_y_z_negative_rounds_up() { + // Real result = 483_5313675.8 + let x: i64 = 1_5391283; + let y: i64 = -314_1592653; + let z: i64 = -1_0000001; + + let result = mul_div_ceil(x, y, z).unwrap(); + + assert_eq!(result, 483_5313676); + } + + #[test] + fn test_mul_div_ceil_all_negative_rounds_up() { + // Real result = -483_5313675.8 + let x: i64 = -1_5391283; + let y: i64 = -314_1592653; + let z: i64 = -1_0000001; + + let result = mul_div_ceil(x, y, z).unwrap(); + + assert_eq!(result, -483_5313675); + } + + #[test] + fn test_mul_div_ceil_mul_zero() { + let x: i64 = 1_5391283; + let y: i64 = 0; + let z: i64 = 1_0000001; + + let result = mul_div_ceil(x, y, z).unwrap(); + + assert_eq!(result, 0); + } + + #[test] + fn test_mul_div_ceil_div_zero() { + let x: i64 = 1_5391283; + let y: i64 = 314_1592653; + let z: i64 = 0; + + let result = mul_div_ceil(x, y, z); + + assert_eq!(result, None); + } + + #[test] + fn test_mul_div_ceil_large_number() { let x: i64 = 9_223_372_036; let y: i64 = 1_000_000_000; - let denominator: i64 = 1_000_000_000; + let z: i64 = 1_000_000_000; - let result = x.fixed_div_ceil(y, denominator).unwrap(); + let result = mul_div_ceil(x, y, z).unwrap(); assert_eq!(result, 9_223_372_036) } #[test] - fn test_fixed_div_ceil_phantom_overflow_uses_i128() { - let x: i64 = 9_223_372_036; + fn test_mul_div_ceil_negative_large_number() { + let x: i64 = -9_223_372_036; let y: i64 = 1_000_000_000; - let denominator: i64 = 2_000_000_000; + let z: i64 = 1_000_000_000; - let result = x.fixed_div_ceil(y, denominator).unwrap(); + let result = mul_div_ceil(x, y, z).unwrap(); - assert_eq!(result, 18_446_744_072); + assert_eq!(result, -9_223_372_036) } #[test] - fn test_fixed_div_ceil_result_overflow() { - let x: i64 = 9_223_372_036_000_000_000; - let y: i64 = 1_000_000_000; - let denominator: i64 = 2_000_000_000; + fn test_mul_div_ceil_small_number() { + let x: i64 = 1; + let y: i64 = 2; + let z: i64 = 3; + + let result = mul_div_ceil(x, y, z).unwrap(); + + assert_eq!(result, 1) + } + + #[test] + fn test_mul_div_ceil_negative_small_number() { + let x: i64 = -1; + let y: i64 = 2; + let z: i64 = 3; + + let result = mul_div_ceil(x, y, z).unwrap(); + + assert_eq!(result, 0) + } + + #[test] + fn test_mul_div_ceil_phantom_overflow_uses_i128() { + // i64::MAX is odd + let x: i64 = (i64::MAX - 1) / 2; + let y: i64 = 2_000_000_000; + let z: i64 = 1_000_000_000; + + let result = mul_div_ceil(x, y, z).unwrap(); + + assert_eq!(result, i64::MAX - 1); + } + + #[test] + fn test_mul_div_ceil_negative_phantom_overflow_uses_i128() { + let x: i64 = i64::MIN / 2; + let y: i64 = 2_000_000_000; + let z: i64 = 1_000_000_000; + + let result = mul_div_ceil(x, y, z).unwrap(); + + assert_eq!(result, i64::MIN); + } + + #[test] + fn test_mul_div_ceil_phantom_overflow_rounds_up() { + // Real Result = 333_333_333_333.3.. + let x: i64 = 100 * 10i64.pow(9); + let y: i64 = 10 * 10i64.pow(9); + let z: i64 = 3 * 10i64.pow(9); + + let result = mul_div_ceil(x, y, z).unwrap(); + + assert_eq!(result, 333_333_333_334); + } + + #[test] + fn test_mul_div_ceil_result_overflow() { + // i64::MAX is odd + let x: i64 = (i64::MAX - 1) / 2 + 1; + let y: i64 = 2_000_000_000; + let z: i64 = 1_000_000_000; + + let result = mul_div_ceil(x, y, z); + + assert_eq!(result, None); + } + + #[test] + fn test_mul_div_ceil_result_negative_overflow() { + let x: i64 = i64::MIN / 2 - 1; + let y: i64 = 2_000_000_000; + let z: i64 = 1_000_000_000; - let result = x.fixed_div_ceil(y, denominator); + let result = mul_div_ceil(x, y, z); assert_eq!(result, None); } + + /********** fixed_mul_floor **********/ + + #[test] + fn test_fixed_mul_floor() { + // Real result = 104_9522835.2 + let x: i64 = 3_1423141; + let y: i64 = 4_1234142; + let denominator: i64 = 0_1234567; + + let result = x.fixed_mul_floor(y, denominator).unwrap(); + assert_eq!(result, 104_9522835); + + let result = (-x).fixed_mul_floor(y, denominator).unwrap(); + assert_eq!(result, -104_9522836); + + let result = (-x).fixed_mul_floor(-y, denominator).unwrap(); + assert_eq!(result, 104_9522835); + + let invalid = x.fixed_mul_floor(y, 0); + assert_eq!(invalid, None); + } + + /********** fixed_mul_ceil **********/ + + #[test] + fn test_fixed_mul_ceil() { + // Real result = 104_9522835.2 + let x: i64 = 3_1423141; + let y: i64 = 4_1234142; + let denominator: i64 = 0_1234567; + + let result = x.fixed_mul_ceil(y, denominator).unwrap(); + assert_eq!(result, 104_9522836); + + let result = (-x).fixed_mul_ceil(y, denominator).unwrap(); + assert_eq!(result, -104_9522835); + + let result = (-x).fixed_mul_ceil(-y, denominator).unwrap(); + assert_eq!(result, 104_9522836); + + let invalid = x.fixed_mul_ceil(y, 0); + assert_eq!(invalid, None); + } + + /********** fixed_div_floor **********/ + + #[test] + fn test_fixed_div_floor() { + // Real result = 204_1150997.8 + let x: i64 = 314_1592653; + let y: i64 = 1_5391280; + let denominator: i64 = 1_0000000; + + let result = x.fixed_div_floor(y, denominator).unwrap(); + assert_eq!(result, 204_1150997); + + let result = (-x).fixed_div_floor(y, denominator).unwrap(); + assert_eq!(result, -204_1150998); + + let result = (-x).fixed_div_floor(-y, denominator).unwrap(); + assert_eq!(result, 204_1150997); + + let invalid = x.fixed_div_floor(0, denominator); + assert_eq!(invalid, None); + } + + /********** fixed_div_ceil **********/ + + #[test] + fn test_fixed_div_ceil() { + // Real result = 204_1150997.8 + let x: i64 = 314_1592653; + let y: i64 = 1_5391280; + let denominator: i64 = 1_0000000; + + let result = x.fixed_div_ceil(y, denominator).unwrap(); + assert_eq!(result, 204_1150998); + + let result = (-x).fixed_div_ceil(y, denominator).unwrap(); + assert_eq!(result, -204_1150997); + + let result = (-x).fixed_div_ceil(-y, denominator).unwrap(); + assert_eq!(result, 204_1150998); + + let invalid = x.fixed_div_ceil(0, denominator); + assert_eq!(invalid, None); + } }
src/u128.rs+440 −113 modified@@ -35,8 +35,10 @@ pub(crate) fn mul_div_ceil(x: u128, y: u128, z: u128) -> Option<u128> { /// Performs ceil(r / z) fn div_ceil(r: u128, z: u128) -> Option<u128> { // floor taken by default for a positive result + // if there is any remainder, add 1 to round ceil let remainder = r.checked_rem_euclid(z)?; - (r / z).checked_add(if remainder > 0 { 1 } else { 0 }) + r.checked_div(z)? + .checked_add(if remainder > 0 { 1 } else { 0 }) } impl SorobanFixedPoint for u128 { @@ -94,280 +96,605 @@ fn scaled_mul_div_ceil(x: &u128, env: &Env, y: &u128, z: &u128) -> u128 { #[cfg(test)] mod test_fixed_point { + use super::{mul_div_ceil, mul_div_floor}; + use crate::FixedPoint; - /********** fixed_mul_floor **********/ + /********** mul_div_floor **********/ - use crate::FixedPoint; + #[test] + fn test_mul_div_floor_rounds_down() { + // Real result = 483_5313675.8 + let x: u128 = 1_5391283; + let y: u128 = 314_1592653; + let z: u128 = 1_0000001; + + let result = mul_div_floor(x, y, z).unwrap(); + + assert_eq!(result, 483_5313675); + } + + #[test] + fn test_mul_div_floor_exact() { + // Real result = 12 + let x: u128 = 8; + let y: u128 = 3; + let z: u128 = 2; + + let result = mul_div_floor(x, y, z).unwrap(); + + assert_eq!(result, 12); + } #[test] - fn test_fixed_mul_floor_rounds_down() { + fn test_mul_div_floor_mul_zero() { + let x: u128 = 1_5391283; + let y: u128 = 0; + let z: u128 = 1_0000001; + + let result = mul_div_floor(x, y, z).unwrap(); + + assert_eq!(result, 0); + } + + #[test] + fn test_mul_div_floor_div_zero() { let x: u128 = 1_5391283; let y: u128 = 314_1592653; - let denominator: u128 = 1_0000001; + let z: u128 = 0; - let result = x.fixed_mul_floor(y, denominator).unwrap(); + let result = mul_div_floor(x, y, z); - assert_eq!(result, 483_5313675) + assert_eq!(result, None); } #[test] - fn test_fixed_mul_floor_large_number() { + fn test_mul_div_floor_large_number() { let x: u128 = 340_282_366_920_938_463_463; let y: u128 = 1_000_000_000_000_000_000; - let denominator: u128 = 1_000_000_000_000_000_000; + let z: u128 = 1_000_000_000_000_000_000; - let result = x.fixed_mul_floor(y, denominator).unwrap(); + let result = mul_div_floor(x, y, z).unwrap(); - assert_eq!(result, 340_282_366_920_938_463_463) + assert_eq!(result, 340_282_366_920_938_463_463); } #[test] - fn test_fixed_mul_floor_phantom_overflow() { + fn test_mul_div_floor_small_number() { + let x: u128 = 1; + let y: u128 = 2; + let z: u128 = 3; + + let result = mul_div_floor(x, y, z).unwrap(); + + assert_eq!(result, 0); + } + + #[test] + fn test_mul_div_floor_phantom_overflow() { let x: u128 = 340_282_366_920_938_463_463; let y: u128 = 1_000_000_000_000_000_001; - let denominator: u128 = 1_000_000_000_000_000_000; + let z: u128 = 1_000_000_000_000_000_000; - let result = x.fixed_mul_floor(y, denominator); + let result = mul_div_floor(x, y, z); - assert_eq!(None, result); + assert_eq!(result, None); } - /********** fixed_mul_ceil **********/ + /********** mul_div_ceil **********/ #[test] - fn test_fixed_mul_ceil_rounds_up() { + fn test_mul_div_ceil_rounds_up() { + // Real result = 483_5313675.8 let x: u128 = 1_5391283; let y: u128 = 314_1592653; - let denominator: u128 = 1_0000001; + let z: u128 = 1_0000001; - let result = x.fixed_mul_ceil(y, denominator).unwrap(); + let result = mul_div_ceil(x, y, z).unwrap(); + + assert_eq!(result, 483_5313676); + } + + #[test] + fn test_mul_div_ceil_exact() { + // Real result = 12 + let x: u128 = 8; + let y: u128 = 3; + let z: u128 = 2; + + let result = mul_div_ceil(x, y, z).unwrap(); + + assert_eq!(result, 12); + } + + #[test] + fn test_mul_div_ceil_mul_zero() { + let x: u128 = 1_5391283; + let y: u128 = 0; + let z: u128 = 1_0000001; + + let result = mul_div_ceil(x, y, z).unwrap(); + + assert_eq!(result, 0); + } - assert_eq!(result, 483_5313676) + #[test] + fn test_mul_div_ceil_div_zero() { + let x: u128 = 1_5391283; + let y: u128 = 314_1592653; + let z: u128 = 0; + + let result = mul_div_ceil(x, y, z); + + assert_eq!(result, None); } #[test] - fn test_fixed_mul_ceil_large_number() { + fn test_mul_div_ceil_large_number() { let x: u128 = 340_282_366_920_938_463_463; let y: u128 = 1_000_000_000_000_000_000; - let denominator: u128 = 1_000_000_000_000_000_000; + let z: u128 = 1_000_000_000_000_000_000; - let result = x.fixed_mul_ceil(y, denominator).unwrap(); + let result = mul_div_ceil(x, y, z).unwrap(); + + assert_eq!(result, 340_282_366_920_938_463_463); + } + + #[test] + fn test_mul_div_ceil_small_number() { + let x: u128 = 1; + let y: u128 = 2; + let z: u128 = 3; + + let result = mul_div_ceil(x, y, z).unwrap(); - assert_eq!(result, 340_282_366_920_938_463_463) + assert_eq!(result, 1); } #[test] - fn test_fixed_mul_ceil_phantom_overflow() { + fn test_mul_div_ceil_phantom_overflow() { let x: u128 = 340_282_366_920_938_463_463; let y: u128 = 1_000_000_000_000_000_001; - let denominator: u128 = 1_000_000_000_000_000_000; + let z: u128 = 1_000_000_000_000_000_000; - let result = x.fixed_mul_ceil(y, denominator); + let result = mul_div_ceil(x, y, z); - assert_eq!(None, result); + assert_eq!(result, None); } - /********** fixed_div_floor **********/ + /********** fixed_mul_floor **********/ #[test] - fn test_fixed_div_floor_rounds_down() { - let x: u128 = 314_1592653; - let y: u128 = 1_5391280; - let denominator: u128 = 1_0000000; + fn test_fixed_mul_floor() { + // Real result = 104_9522835.2 + let x: u128 = 3_1423141; + let y: u128 = 4_1234142; + let denominator: u128 = 0_1234567; - let result = x.fixed_div_floor(y, denominator).unwrap(); + let result = x.fixed_mul_floor(y, denominator).unwrap(); + assert_eq!(result, 104_9522835); - assert_eq!(result, 204_1150997) + let invalid = x.fixed_mul_floor(y, 0); + assert_eq!(invalid, None); } + /********** fixed_mul_ceil **********/ + #[test] - fn test_fixed_div_floor_large_number() { - let x: u128 = 340_282_366_920_938_463_463; - let y: u128 = 1_000_000_000_000_000_000; - let denominator: u128 = 1_000_000_000_000_000_000; + fn test_fixed_mul_ceil() { + // Real result = 104_9522835.2 + let x: u128 = 3_1423141; + let y: u128 = 4_1234142; + let denominator: u128 = 0_1234567; - let result = x.fixed_div_floor(y, denominator).unwrap(); + let result = x.fixed_mul_ceil(y, denominator).unwrap(); + assert_eq!(result, 104_9522836); - assert_eq!(result, 340_282_366_920_938_463_463) + let invalid = x.fixed_mul_ceil(y, 0); + assert_eq!(invalid, None); } + /********** fixed_div_floor **********/ + #[test] - fn test_fixed_div_floor_phantom_overflow() { - let x: u128 = 340_282_366_920_938_463_463; - let y: u128 = 1_000_000_000_000_000_000; - let denominator: u128 = 1_000_000_000_000_000_001; + fn test_fixed_div_floor() { + // Real result = 204_1150997.8 + let x: u128 = 314_1592653; + let y: u128 = 1_5391280; + let denominator: u128 = 1_0000000; - let result = x.fixed_div_floor(y, denominator); + let result = x.fixed_div_floor(y, denominator).unwrap(); + assert_eq!(result, 204_1150997); - assert_eq!(None, result); + let invalid = x.fixed_div_floor(0, denominator); + assert_eq!(invalid, None); } /********** fixed_div_ceil **********/ #[test] - fn test_fixed_div_ceil_rounds_down() { + fn test_fixed_div_ceil() { + // Real result = 204_1150997.8 let x: u128 = 314_1592653; let y: u128 = 1_5391280; let denominator: u128 = 1_0000000; let result = x.fixed_div_ceil(y, denominator).unwrap(); + assert_eq!(result, 204_1150998); - assert_eq!(result, 204_1150998) + let invalid = x.fixed_div_ceil(0, denominator); + assert_eq!(invalid, None); } +} + +#[cfg(test)] +mod test_soroban_fixed_point { + use super::{scaled_mul_div_ceil, scaled_mul_div_floor}; + use crate::SorobanFixedPoint; + use soroban_sdk::Env; + + /********** scaled_mul_div_floor **********/ #[test] - fn test_fixed_div_ceil_large_number() { - let x: u128 = 340_282_366_920_938_463_463; - let y: u128 = 1_000_000_000_000_000_000; - let denominator: u128 = 1_000_000_000_000_000_000; + fn test_scaled_mul_div_floor_rounds_down() { + // Real result = 483_5313675.8 + let env = Env::default(); + let x: u128 = 1_5391283; + let y: u128 = 314_1592653; + let z: u128 = 1_0000001; - let result = x.fixed_div_ceil(y, denominator).unwrap(); + let result = scaled_mul_div_floor(&x, &env, &y, &z); + + assert_eq!(result, 483_5313675); + } + + #[test] + fn test_scaled_mul_div_floor_exact() { + // Real result = 12 + let env = Env::default(); + let x: u128 = 8; + let y: u128 = 3; + let z: u128 = 2; + + let result = scaled_mul_div_floor(&x, &env, &y, &z); - assert_eq!(result, 340_282_366_920_938_463_463) + assert_eq!(result, 12); } #[test] - fn test_fixed_div_ceil_phantom_overflow() { + fn test_scaled_mul_div_floor_mul_zero() { + let env = Env::default(); + let x: u128 = 1_5391283; + let y: u128 = 0; + let z: u128 = 1_0000001; + + let result = scaled_mul_div_floor(&x, &env, &y, &z); + + assert_eq!(result, 0); + } + + #[test] + #[should_panic] + fn test_scaled_mul_div_floor_div_zero() { + let env = Env::default(); + let x: u128 = 1_5391283; + let y: u128 = 314_1592653; + let z: u128 = 0; + + scaled_mul_div_floor(&x, &env, &y, &z); + } + + #[test] + fn test_scaled_mul_div_floor_large_number() { + let env = Env::default(); let x: u128 = 340_282_366_920_938_463_463; let y: u128 = 1_000_000_000_000_000_000; - let denominator: u128 = 1_000_000_000_000_000_001; + let z: u128 = 1_000_000_000_000_000_000; - let result = x.fixed_div_ceil(y, denominator); + let result = scaled_mul_div_floor(&x, &env, &y, &z); - assert_eq!(None, result); + assert_eq!(result, 340_282_366_920_938_463_463); } -} -#[cfg(test)] -mod test_soroban_fixed_point { - use crate::SorobanFixedPoint; - use soroban_sdk::Env; + #[test] + fn test_scaled_mul_div_floor_small_number() { + let env = Env::default(); + let x: u128 = 1; + let y: u128 = 2; + let z: u128 = 3; - /********** fixed_mul_floor **********/ + let result = scaled_mul_div_floor(&x, &env, &y, &z); + + assert_eq!(result, 0); + } + + #[test] + fn test_scaled_mul_div_floor_phantom_overflow_uses_u256() { + // u128::MAX is odd + let env = Env::default(); + let x: u128 = (u128::MAX - 1) / 2; + let y: u128 = 2 * 10u128.pow(18); + let z: u128 = 10u128.pow(18); + + let result = scaled_mul_div_floor(&x, &env, &y, &z); + + assert_eq!(result, u128::MAX - 1); + } + + #[test] + fn test_scaled_mul_div_floor_phantom_overflow_rounds_down() { + // Real Result = 333_3x18.3.. + let env = Env::default(); + let x: u128 = 100 * 10u128.pow(18); + let y: u128 = 10 * 10u128.pow(18); + let z: u128 = 3 * 10u128.pow(18); + + let result = scaled_mul_div_floor(&x, &env, &y, &z); + + assert_eq!(result, 333_333_333_333_333_333_333); + } + + #[test] + #[should_panic] + fn test_scaled_mul_div_floor_result_overflow() { + // u128::MAX is odd + let env = Env::default(); + let x: u128 = (u128::MAX - 1) / 2 + 1; + let y: u128 = 2 * 10u128.pow(18); + let z: u128 = 10u128.pow(18); + + scaled_mul_div_floor(&x, &env, &y, &z); + } + + /********** scaled_mul_div_ceil **********/ #[test] - fn test_fixed_mul_floor_rounds_down() { + fn test_scaled_mul_div_ceil_rounds_up() { + // Real result = 483_5313675.8 let env = Env::default(); let x: u128 = 1_5391283; let y: u128 = 314_1592653; - let denominator: u128 = 1_0000001; + let z: u128 = 1_0000001; - let result = x.fixed_mul_floor(&env, &y, &denominator); + let result = scaled_mul_div_ceil(&x, &env, &y, &z); - assert_eq!(result, 483_5313675) + assert_eq!(result, 483_5313676); } #[test] - fn test_fixed_mul_floor_phantom_overflow_scales() { + fn test_scaled_mul_div_ceil_exact() { + // Real result = 12 let env = Env::default(); - let x: u128 = 340_282_366_920_938_463_463; - let y: u128 = 10u128.pow(27); - let denominator: u128 = 10u128.pow(18); + let x: u128 = 8; + let y: u128 = 3; + let z: u128 = 2; - let result = x.fixed_mul_floor(&env, &y, &denominator); + let result = scaled_mul_div_ceil(&x, &env, &y, &z); - assert_eq!(result, 340_282_366_920_938_463_463 * 10u128.pow(9)); + assert_eq!(result, 12); } - /********** fixed_mul_ceil **********/ + #[test] + fn test_scaled_mul_div_ceil_mul_zero() { + let env = Env::default(); + let x: u128 = 1_5391283; + let y: u128 = 0; + let z: u128 = 1_0000001; + + let result = scaled_mul_div_ceil(&x, &env, &y, &z); + + assert_eq!(result, 0); + } #[test] - fn test_fixed_mul_ceil_rounds_up() { + #[should_panic] + fn test_scaled_mul_div_ceil_div_zero() { let env = Env::default(); let x: u128 = 1_5391283; let y: u128 = 314_1592653; - let denominator: u128 = 1_0000001; - - let result = x.fixed_mul_ceil(&env, &y, &denominator); + let z: u128 = 0; - assert_eq!(result, 483_5313676) + scaled_mul_div_ceil(&x, &env, &y, &z); } #[test] - fn test_fixed_mul_ceil_large_number() { + fn test_scaled_mul_div_ceil_large_number() { let env = Env::default(); let x: u128 = 340_282_366_920_938_463_463; let y: u128 = 1_000_000_000_000_000_000; - let denominator: u128 = 1_000_000_000_000_000_000; + let z: u128 = 1_000_000_000_000_000_000; - let result = x.fixed_mul_ceil(&env, &y, &denominator); + let result = scaled_mul_div_ceil(&x, &env, &y, &z); - assert_eq!(result, 340_282_366_920_938_463_463) + assert_eq!(result, 340_282_366_920_938_463_463); } #[test] - fn test_fixed_mul_ceil_phantom_overflow_scales() { + fn test_scaled_mul_div_ceil_small_number() { let env = Env::default(); - let x: u128 = 340_282_366_920_938_463_463; - let y: u128 = 10u128.pow(27); + let x: u128 = 1; + let y: u128 = 2; + let z: u128 = 3; + + let result = scaled_mul_div_ceil(&x, &env, &y, &z); + + assert_eq!(result, 1); + } + + #[test] + fn test_scaled_mul_div_ceil_phantom_overflow_uses_u256() { + // u128::MAX is odd + let env = Env::default(); + let x: u128 = (u128::MAX - 1) / 2; + let y: u128 = 2 * 10u128.pow(18); + let z: u128 = 10u128.pow(18); + + let result = scaled_mul_div_ceil(&x, &env, &y, &z); + + assert_eq!(result, u128::MAX - 1); + } + + #[test] + fn test_scaled_mul_div_ceil_phantom_overflow_rounds_up() { + // Real Result = 333_3x18.3.. + let env = Env::default(); + let x: u128 = 100 * 10u128.pow(18); + let y: u128 = 10 * 10u128.pow(18); + let z: u128 = 3 * 10u128.pow(18); + + let result = scaled_mul_div_ceil(&x, &env, &y, &z); + + assert_eq!(result, 333_333_333_333_333_333_334); + } + + #[test] + #[should_panic] + fn test_scaled_mul_div_ceil_result_overflow() { + // u128::MAX is odd + let env = Env::default(); + let x: u128 = (u128::MAX - 1) / 2 + 1; + let y: u128 = 2 * 10u128.pow(18); + let z: u128 = 10u128.pow(18); + + scaled_mul_div_ceil(&x, &env, &y, &z); + } + + /********** fixed_mul_floor **********/ + + #[test] + fn test_fixed_mul_floor() { + // Real result = 104_9522835.2 + let env = Env::default(); + let x: u128 = 3_1423141; + let y: u128 = 4_1234142; + let denominator: u128 = 0_1234567; + + let result = x.fixed_mul_floor(&env, &y, &denominator); + assert_eq!(result, 104_9522835); + } + + #[test] + fn test_fixed_mul_floor_uses_u256() { + // Real result = 246800e18 + let env = Env::default(); + let x: u128 = 1234 * 10u128.pow(18); + let y: u128 = 200 * 10u128.pow(18); + let denominator: u128 = 10u128.pow(18); + + let result = x.fixed_mul_floor(&env, &y, &denominator); + assert_eq!(result, 246800 * 10u128.pow(18)); + } + + #[test] + #[should_panic] + fn test_fixed_mul_floor_panics() { + let env = Env::default(); + let x: u128 = 10u128.pow(7); + + x.fixed_mul_floor(&env, &x, &0); + } + + /********** fixed_mul_ceil **********/ + + #[test] + fn test_fixed_mul_ceil() { + // Real result = 104_9522835.2 + let env = Env::default(); + let x: u128 = 3_1423141; + let y: u128 = 4_1234142; + let denominator: u128 = 0_1234567; + + let result = x.fixed_mul_ceil(&env, &y, &denominator); + assert_eq!(result, 104_9522836); + } + + #[test] + fn test_fixed_mul_ceil_uses_u256() { + // Real result = 246800e18 + let env = Env::default(); + let x: u128 = 1234 * 10u128.pow(18); + let y: u128 = 200 * 10u128.pow(18); let denominator: u128 = 10u128.pow(18); let result = x.fixed_mul_ceil(&env, &y, &denominator); + assert_eq!(result, 246800 * 10u128.pow(18)); + } - assert_eq!(result, 340_282_366_920_938_463_463 * 10u128.pow(9)); + #[test] + #[should_panic] + fn test_fixed_mul_ceil_panics() { + let env = Env::default(); + let x: u128 = 10u128.pow(7); + + x.fixed_mul_ceil(&env, &x, &0); } /********** fixed_div_floor **********/ #[test] - fn test_fixed_div_floor_rounds_down() { + fn test_fixed_div_floor() { + // Real result = 204_1150997.8 let env = Env::default(); let x: u128 = 314_1592653; let y: u128 = 1_5391280; let denominator: u128 = 1_0000000; let result = x.fixed_div_floor(&env, &y, &denominator); - - assert_eq!(result, 204_1150997) + assert_eq!(result, 204_1150997); } #[test] - fn test_fixed_div_floor_phantom_overflow_scales() { + fn test_fixed_div_floor_uses_u256() { + // Real result = 5000e18 let env = Env::default(); - let x: u128 = 340_282_366_920_938_463_463; - let y: u128 = 10u128.pow(18); - let denominator: u128 = 10u128.pow(27); + let x: u128 = 1000 * 10u128.pow(18); + let y: u128 = 2 * 10u128.pow(17); + let denominator: u128 = 10u128.pow(18); let result = x.fixed_div_floor(&env, &y, &denominator); + assert_eq!(result, 5000 * 10u128.pow(18)); + } - assert_eq!(result, 340_282_366_920_938_463_463 * 10u128.pow(9)); + #[test] + #[should_panic] + fn test_fixed_div_floor_panics() { + let env = Env::default(); + let x: u128 = 10u128.pow(7); + + x.fixed_div_floor(&env, &0, &x); } /********** fixed_div_ceil **********/ #[test] - fn test_fixed_div_ceil_rounds_down() { + fn test_fixed_div_ceil() { + // Real result = 204_1150997.8 let env = Env::default(); let x: u128 = 314_1592653; let y: u128 = 1_5391280; let denominator: u128 = 1_0000000; let result = x.fixed_div_ceil(&env, &y, &denominator); - - assert_eq!(result, 204_1150998) + assert_eq!(result, 204_1150998); } #[test] - fn test_fixed_div_ceil_large_number() { + fn test_fixed_div_ceil_uses_u256() { + // Real result = 5000e18 let env = Env::default(); - let x: u128 = 340_282_366_920_938_463_463; - let y: u128 = 1_000_000_000_000_000_000; - let denominator: u128 = 1_000_000_000_000_000_000; + let x: u128 = 1000 * 10u128.pow(18); + let y: u128 = 2 * 10u128.pow(17); + let denominator: u128 = 10u128.pow(18); let result = x.fixed_div_ceil(&env, &y, &denominator); - - assert_eq!(result, 340_282_366_920_938_463_463) + assert_eq!(result, 5000 * 10u128.pow(18)); } #[test] - fn test_fixed_div_ceil_phantom_overflow_scales() { + #[should_panic] + fn test_fixed_div_ceil_panics() { let env = Env::default(); - let x: u128 = 340_282_366_920_938_463_463; - let y: u128 = 10u128.pow(18); - let denominator: u128 = 10u128.pow(27); - - let result = x.fixed_div_floor(&env, &y, &denominator); + let x: u128 = 10u128.pow(7); - assert_eq!(result, 340_282_366_920_938_463_463 * 10u128.pow(9)); + x.fixed_div_ceil(&env, &0, &x); } }
src/u256.rs+251 −70 modified@@ -28,6 +28,8 @@ pub(crate) fn mul_div_floor(x: &U256, y: &U256, z: &U256) -> U256 { /// Performs ceil(x * y / z) pub(crate) fn mul_div_ceil(env: &Env, x: &U256, y: &U256, z: &U256) -> U256 { + // floor taken by default + // if there is any remainder, add 1 to round ceil let r = x.mul(&y); let remainder = r.rem_euclid(&z); let zero = U256::from_u32(env, 0); @@ -37,161 +39,340 @@ pub(crate) fn mul_div_ceil(env: &Env, x: &U256, y: &U256, z: &U256) -> U256 { #[cfg(test)] mod tests { + use soroban_sdk::Bytes; + use super::*; - /********** fixed_mul_floor **********/ + /// Helper to create U256::MAX (all bits set to 1) + fn u256_max(env: &Env) -> U256 { + U256::from_be_bytes(env, &Bytes::from_array(env, &[0xFF; 32])) + } + + /********** mul_div_floor **********/ #[test] - fn test_fixed_mul_floor_rounds_down() { + fn test_mul_div_floor_rounds_down() { + // Real result = 483_5313675.8 let env = Env::default(); - let x: U256 = U256::from_u128(&env, 1_5391283); - let y: U256 = U256::from_u128(&env, 314_1592653); - let denominator: U256 = U256::from_u128(&env, 1_0000001); + let x = U256::from_u128(&env, 1_5391283); + let y = U256::from_u128(&env, 314_1592653); + let z = U256::from_u128(&env, 1_0000001); - let result = x.fixed_mul_floor(&env, &y, &denominator); + let result = mul_div_floor(&x, &y, &z); assert_eq!(result, U256::from_u128(&env, 483_5313675)); } + #[test] + fn test_mul_div_floor_exact() { + // Real result = 12 + let env = Env::default(); + let x = U256::from_u32(&env, 8); + let y = U256::from_u32(&env, 3); + let z = U256::from_u32(&env, 2); + + let result = mul_div_floor(&x, &y, &z); + + assert_eq!(result, U256::from_u32(&env, 12)); + } + + #[test] + fn test_mul_div_floor_mul_zero() { + let env = Env::default(); + let x = U256::from_u128(&env, 1_5391283); + let y = U256::from_u128(&env, 0); + let z = U256::from_u128(&env, 1_0000001); + + let result = mul_div_floor(&x, &y, &z); + + assert_eq!(result, U256::from_u128(&env, 0)); + } + + #[test] + #[should_panic] + fn test_mul_div_floor_div_zero() { + let env = Env::default(); + let x = U256::from_u128(&env, 1_5391283); + let y = U256::from_u128(&env, 314_1592653); + let z = U256::from_u128(&env, 0); + + mul_div_floor(&x, &y, &z); + } + + #[test] + fn test_mul_div_floor_large_number() { + let env = Env::default(); + let x = u256_max(&env).div(&U256::from_u128(&env, 10u128.pow(36))); + let y = U256::from_u128(&env, 10u128.pow(36)); + let z = U256::from_u128(&env, 10u128.pow(36)); + + let result = mul_div_floor(&x, &y, &z); + + assert_eq!(result, x); + } + + #[test] + fn test_mul_div_floor_small_number() { + let env = Env::default(); + let x = U256::from_u32(&env, 1); + let y = U256::from_u32(&env, 2); + let z = U256::from_u32(&env, 3); + + let result = mul_div_floor(&x, &y, &z); + + assert_eq!(result, U256::from_u32(&env, 0)); + } + + #[test] + #[should_panic] + fn test_mul_div_floor_overflow() { + let env = Env::default(); + let x = u256_max(&env) + .div(&U256::from_u128(&env, 10u128.pow(36))) + .add(&U256::from_u32(&env, 1)); + let y = U256::from_u128(&env, 10u128.pow(36)); + let z = U256::from_u128(&env, 10u128.pow(36)); + + mul_div_floor(&x, &y, &z); + } + + /********** mul_div_ceil **********/ + + #[test] + fn test_mul_div_ceil_rounds_up() { + // Real result = 483_5313675.8 + let env = Env::default(); + let x = U256::from_u128(&env, 1_5391283); + let y = U256::from_u128(&env, 314_1592653); + let z = U256::from_u128(&env, 1_0000001); + + let result = mul_div_ceil(&env, &x, &y, &z); + + assert_eq!(result, U256::from_u128(&env, 483_5313676)); + } + + #[test] + fn test_mul_div_ceil_exact() { + // Real result = 12 + let env = Env::default(); + let x = U256::from_u32(&env, 8); + let y = U256::from_u32(&env, 3); + let z = U256::from_u32(&env, 2); + + let result = mul_div_ceil(&env, &x, &y, &z); + + assert_eq!(result, U256::from_u32(&env, 12)); + } + + #[test] + fn test_mul_div_ceil_mul_zero() { + let env = Env::default(); + let x = U256::from_u128(&env, 1_5391283); + let y = U256::from_u32(&env, 0); + let z = U256::from_u128(&env, 1_0000001); + + let result = mul_div_ceil(&env, &x, &y, &z); + + assert_eq!(result, U256::from_u32(&env, 0)); + } + + #[test] + #[should_panic] + fn test_mul_div_ceil_div_zero() { + let env = Env::default(); + let x = U256::from_u128(&env, 1_5391283); + let y = U256::from_u128(&env, 314_1592653); + let z = U256::from_u32(&env, 0); + + mul_div_ceil(&env, &x, &y, &z); + } + + #[test] + fn test_mul_div_ceil_large_number() { + let env = Env::default(); + let x = u256_max(&env).div(&U256::from_u128(&env, 10u128.pow(36))); + let y = U256::from_u128(&env, 10u128.pow(36)); + let z = U256::from_u128(&env, 10u128.pow(36)); + + let result = mul_div_ceil(&env, &x, &y, &z); + + assert_eq!(result, x); + } + + #[test] + fn test_mul_div_ceil_small_number() { + let env = Env::default(); + let x = U256::from_u32(&env, 1); + let y = U256::from_u32(&env, 2); + let z = U256::from_u32(&env, 3); + + let result = mul_div_ceil(&env, &x, &y, &z); + + assert_eq!(result, U256::from_u32(&env, 1)); + } + + #[test] + #[should_panic] + fn test_mul_div_ceil_overflow() { + let env = Env::default(); + let x = u256_max(&env) + .div(&U256::from_u128(&env, 10u128.pow(36))) + .add(&U256::from_u32(&env, 1)); + let y = U256::from_u128(&env, 10u128.pow(36)); + let z = U256::from_u128(&env, 10u128.pow(36)); + + mul_div_ceil(&env, &x, &y, &z); + } + + /********** fixed_mul_floor **********/ + + #[test] + fn test_fixed_mul_floor() { + // Real result = 104_9522835.2 + let env = Env::default(); + let x = U256::from_u128(&env, 3_1423141); + let y = U256::from_u128(&env, 4_1234142); + let denominator = U256::from_u128(&env, 0_1234567); + + let result = x.fixed_mul_floor(&env, &y, &denominator); + assert_eq!(result, U256::from_u128(&env, 104_9522835)); + } + #[test] fn test_fixed_mul_floor_large_number() { let env = Env::default(); - let x: U256 = U256::from_u128(&env, u128::MAX); - let y: U256 = U256::from_u128(&env, 10u128.pow(38)); - let denominator: U256 = U256::from_u128(&env, 10u128.pow(18)); + let x = U256::from_u128(&env, u128::MAX); + let y = U256::from_u128(&env, 10u128.pow(38)); + let denominator = U256::from_u128(&env, 10u128.pow(18)); - let result = x.clone().fixed_mul_floor(&env, &y, &denominator); + let result = x.fixed_mul_floor(&env, &y, &denominator); - let expected_result = x.mul(&U256::from_u128(&env, 10u128.pow(20))); + let expected_result = + U256::from_u128(&env, u128::MAX).mul(&U256::from_u128(&env, 10u128.pow(20))); assert_eq!(result, expected_result); } #[test] - #[should_panic(expected = "attempt to multiply with overflow")] - fn test_fixed_mul_floor_phantom_overflow() { + #[should_panic] + fn test_fixed_mul_floor_panics() { let env = Env::default(); - let x: U256 = U256::from_u128(&env, u128::MAX); - // 256 bit max ~= 1.2e77, 128 bit max ~= 3.4e38, need to multiply by at least 10^39 - let y: U256 = U256::from_u128(&env, 10u128.pow(39)); - let denominator: U256 = U256::from_u128(&env, 10u128.pow(18)); + let x = U256::from_u128(&env, 10u128.pow(7)); + let zero = U256::from_u32(&env, 0); - x.fixed_mul_floor(&env, &y, &denominator); + x.fixed_mul_floor(&env, &x, &zero); } /********** fixed_mul_ceil **********/ #[test] - fn test_fixed_mul_ceil_rounds_up() { + fn test_fixed_mul_ceil() { + // Real result = 104_9522835.2 let env = Env::default(); - let x: U256 = U256::from_u128(&env, 1_5391283); - let y: U256 = U256::from_u128(&env, 314_1592653); - let denominator: U256 = U256::from_u128(&env, 1_0000001); + let x = U256::from_u128(&env, 3_1423141); + let y = U256::from_u128(&env, 4_1234142); + let denominator = U256::from_u128(&env, 0_1234567); let result = x.fixed_mul_ceil(&env, &y, &denominator); - - assert_eq!(result, U256::from_u128(&env, 483_5313676)); + assert_eq!(result, U256::from_u128(&env, 104_9522836)); } #[test] fn test_fixed_mul_ceil_large_number() { let env = Env::default(); - let x: U256 = U256::from_u128(&env, u128::MAX); - let y: U256 = U256::from_u128(&env, 10u128.pow(38)); - let denominator: U256 = U256::from_u128(&env, 10u128.pow(18)); + let x = U256::from_u128(&env, u128::MAX); + let y = U256::from_u128(&env, 10u128.pow(38)); + let denominator = U256::from_u128(&env, 10u128.pow(18)); - let result = x.clone().fixed_mul_ceil(&env, &y, &denominator); + let result = x.fixed_mul_ceil(&env, &y, &denominator); - let expected_result = x.mul(&U256::from_u128(&env, 10u128.pow(20))); + let expected_result = + U256::from_u128(&env, u128::MAX).mul(&U256::from_u128(&env, 10u128.pow(20))); assert_eq!(result, expected_result); } #[test] - #[should_panic(expected = "attempt to multiply with overflow")] - fn test_fixed_mul_ceil_phantom_overflow() { + #[should_panic] + fn test_fixed_mul_ceil_panics() { let env = Env::default(); - let x: U256 = U256::from_u128(&env, u128::MAX); - // 256 bit max ~= 1.2e77, 128 bit max ~= 3.4e38, need to multiply by at least 10^39 - let y: U256 = U256::from_u128(&env, 10u128.pow(39)); - let denominator: U256 = U256::from_u128(&env, 10u128.pow(18)); + let x = U256::from_u128(&env, 10u128.pow(7)); + let zero = U256::from_u32(&env, 0); - x.fixed_mul_ceil(&env, &y, &denominator); + x.fixed_mul_ceil(&env, &x, &zero); } /********** fixed_div_floor **********/ #[test] - fn test_fixed_div_floor_rounds_down() { + fn test_fixed_div_floor() { + // Real result = 204_1150997.8 let env = Env::default(); - let x: U256 = U256::from_u128(&env, 314_1592653); - let y: U256 = U256::from_u128(&env, 1_5391280); - let denominator: U256 = U256::from_u128(&env, 1_0000000); + let x = U256::from_u128(&env, 314_1592653); + let y = U256::from_u128(&env, 1_5391280); + let denominator = U256::from_u128(&env, 1_0000000); let result = x.fixed_div_floor(&env, &y, &denominator); - assert_eq!(result, U256::from_u128(&env, 204_1150997)); } #[test] fn test_fixed_div_floor_large_number() { let env = Env::default(); - let x: U256 = U256::from_u128(&env, u128::MAX); - let y: U256 = U256::from_u128(&env, 10u128.pow(27)); - let denominator: U256 = U256::from_u128(&env, 10u128.pow(38)); + let x = U256::from_u128(&env, u128::MAX); + let y = U256::from_u128(&env, 10u128.pow(27)); + let denominator = U256::from_u128(&env, 10u128.pow(38)); - let result = x.clone().fixed_div_floor(&env, &y, &denominator); + let result = x.fixed_div_floor(&env, &y, &denominator); - let expected_result = x.mul(&U256::from_u128(&env, 10u128.pow(11))); + let expected_result = + U256::from_u128(&env, u128::MAX).mul(&U256::from_u128(&env, 10u128.pow(11))); assert_eq!(result, expected_result); } #[test] - #[should_panic(expected = "attempt to multiply with overflow")] - fn test_fixed_div_floor_phantom_overflow() { + #[should_panic] + fn test_fixed_div_floor_panics() { let env = Env::default(); - let x: U256 = U256::from_u128(&env, u128::MAX); - let y: U256 = U256::from_u128(&env, 10u128.pow(27)); - // 256 bit max ~= 1.2e77, 128 bit max ~= 3.4e38, need to multiply by at least 10^39 - let denominator: U256 = U256::from_u128(&env, 10u128.pow(39)); + let x = U256::from_u128(&env, 10u128.pow(7)); + let zero = U256::from_u32(&env, 0); - x.fixed_div_floor(&env, &y, &denominator); + x.fixed_div_floor(&env, &zero, &x); } /********** fixed_div_ceil **********/ #[test] - fn test_fixed_div_ceil_rounds_down() { + fn test_fixed_div_ceil() { + // Real result = 204_1150997.8 let env = Env::default(); - let x: U256 = U256::from_u128(&env, 314_1592653); - let y: U256 = U256::from_u128(&env, 1_5391280); - let denominator: U256 = U256::from_u128(&env, 1_0000000); + let x = U256::from_u128(&env, 314_1592653); + let y = U256::from_u128(&env, 1_5391280); + let denominator = U256::from_u128(&env, 1_0000000); let result = x.fixed_div_ceil(&env, &y, &denominator); - assert_eq!(result, U256::from_u128(&env, 204_1150998)); } #[test] fn test_fixed_div_ceil_large_number() { let env = Env::default(); - let x: U256 = U256::from_u128(&env, u128::MAX); - let y: U256 = U256::from_u128(&env, 10u128.pow(27)); - let denominator: U256 = U256::from_u128(&env, 10u128.pow(38)); + let x = U256::from_u128(&env, u128::MAX); + let y = U256::from_u128(&env, 10u128.pow(27)); + let denominator = U256::from_u128(&env, 10u128.pow(38)); - let result = x.clone().fixed_div_ceil(&env, &y, &denominator); + let result = x.fixed_div_ceil(&env, &y, &denominator); - let expected_result = x.mul(&U256::from_u128(&env, 10u128.pow(11))); + let expected_result = + U256::from_u128(&env, u128::MAX).mul(&U256::from_u128(&env, 10u128.pow(11))); assert_eq!(result, expected_result); } #[test] - #[should_panic(expected = "attempt to multiply with overflow")] - fn test_fixed_div_ceil_phantom_overflow() { + #[should_panic] + fn test_fixed_div_ceil_panics() { let env = Env::default(); - let x: U256 = U256::from_u128(&env, u128::MAX); - let y: U256 = U256::from_u128(&env, 10u128.pow(27)); - // 256 bit max ~= 1.2e77, 128 bit max ~= 3.4e38, need to multiply by at least 10^39 - let denominator: U256 = U256::from_u128(&env, 10u128.pow(39)); + let x = U256::from_u128(&env, 10u128.pow(7)); + let zero = U256::from_u32(&env, 0); - x.fixed_div_ceil(&env, &y, &denominator); + x.fixed_div_ceil(&env, &zero, &x); } }
src/u64.rs+190 −96 modified@@ -24,10 +24,11 @@ fn mul_div_floor(x: u64, y: u64, z: u64) -> Option<u64> { Some(r) => r.checked_div(z), None => { let res_u128 = crate::u128::mul_div_floor(x as u128, y as u128, z as u128)?; - if res_u128 > u64::MAX as u128 { - return None; + if let Ok(res_u64) = u64::try_from(res_u128) { + Some(res_u64) + } else { + None } - Some(res_u128 as u64) } }; } @@ -37,15 +38,16 @@ fn mul_div_ceil(x: u64, y: u64, z: u64) -> Option<u64> { return match x.checked_mul(y) { Some(r) => { let remainder = r.checked_rem_euclid(z)?; - // div overflow will be caught by checked_rem_euclid - (r / z).checked_add(if remainder > 0 { 1 } else { 0 }) + r.checked_div(z)? + .checked_add(if remainder > 0 { 1 } else { 0 }) } None => { let res_u128 = crate::u128::mul_div_ceil(x as u128, y as u128, z as u128)?; - if res_u128 > u64::MAX as u128 { - return None; + if let Ok(res_u64) = u64::try_from(res_u128) { + Some(res_u64) + } else { + None } - Some(res_u128 as u64) } }; } @@ -54,187 +56,279 @@ fn mul_div_ceil(x: u64, y: u64, z: u64) -> Option<u64> { mod tests { use super::*; - /********** fixed_mul_floor **********/ + /********** mul_div_floor **********/ #[test] - fn test_fixed_mul_floor_rounds_down() { + fn test_mul_div_floor_rounds_down() { + // Real result = 483_5313675.8 let x: u64 = 1_5391283; let y: u64 = 314_1592653; - let denominator: u64 = 1_0000001; + let z: u64 = 1_0000001; - let result = x.fixed_mul_floor(y, denominator).unwrap(); + let result = mul_div_floor(x, y, z).unwrap(); + + assert_eq!(result, 483_5313675); + } + + #[test] + fn test_mul_div_floor_exact() { + // Real result = 12 + let x: u64 = 8; + let y: u64 = 3; + let z: u64 = 2; + + let result = mul_div_floor(x, y, z).unwrap(); + + assert_eq!(result, 12); + } + + #[test] + fn test_mul_div_floor_mul_zero() { + let x: u64 = 1_5391283; + let y: u64 = 0; + let z: u64 = 1_0000001; - assert_eq!(result, 483_5313675) + let result = mul_div_floor(x, y, z).unwrap(); + + assert_eq!(result, 0); + } + + #[test] + fn test_mul_div_floor_div_zero() { + let x: u64 = 1_5391283; + let y: u64 = 314_1592653; + let z: u64 = 0; + + let result = mul_div_floor(x, y, z); + + assert_eq!(result, None); } #[test] - fn test_fixed_mul_floor_large_number() { + fn test_mul_div_floor_large_number() { let x: u64 = 18_446_744_073; let y: u64 = 1_000_000_000; - let denominator: u64 = 1_000_000_000; + let z: u64 = 1_000_000_000; - let result = x.fixed_mul_floor(y, denominator).unwrap(); + let result = mul_div_floor(x, y, z).unwrap(); - assert_eq!(result, 18_446_744_073) + assert_eq!(result, 18_446_744_073); } #[test] - fn test_fixed_mul_floor_phantom_overflow_uses_u128() { - let x: u64 = 18_446_744_073; + fn test_mul_div_floor_small_number() { + let x: u64 = 1; + let y: u64 = 2; + let z: u64 = 3; + + let result = mul_div_floor(x, y, z).unwrap(); + + assert_eq!(result, 0); + } + + #[test] + fn test_mul_div_floor_phantom_overflow_uses_u128() { + // u64::MAX is odd + let x: u64 = (u64::MAX - 1) / 2; let y: u64 = 2_000_000_000; - let denominator: u64 = 1_000_000_000; + let z: u64 = 1_000_000_000; - let result = x.fixed_mul_floor(y, denominator).unwrap(); + let result = mul_div_floor(x, y, z).unwrap(); + + assert_eq!(result, u64::MAX - 1); + } + + #[test] + fn test_mul_div_floor_phantom_overflow_rounds_up() { + // Real Result = 333_333_333_333.3.. + let x: u64 = 100 * 10u64.pow(9); + let y: u64 = 10 * 10u64.pow(9); + let z: u64 = 3 * 10u64.pow(9); - assert_eq!(result, 36_893_488_146); + let result = mul_div_floor(x, y, z).unwrap(); + + assert_eq!(result, 333_333_333_333); } #[test] - fn test_fixed_mul_floor_result_overflow() { - let x: u64 = 18_446_744_073_000_000_000; + fn test_mul_div_floor_result_overflow() { + // u64::MAX is odd + let x: u64 = (u64::MAX - 1) / 2 + 1; let y: u64 = 2_000_000_000; - let denominator: u64 = 1_000_000_000; + let z: u64 = 1_000_000_000; - let result = x.fixed_mul_floor(y, denominator); + let result = mul_div_floor(x, y, z); assert_eq!(result, None); } - /********** fixed_mul_ceil **********/ + /********** mul_div_ceil **********/ #[test] - fn test_fixed_mul_ceil_rounds_up() { + fn test_mul_div_ceil_rounds_up() { + // Real result = 483_5313675.8 let x: u64 = 1_5391283; let y: u64 = 314_1592653; - let denominator: u64 = 1_0000001; + let z: u64 = 1_0000001; - let result = x.fixed_mul_ceil(y, denominator).unwrap(); + let result = mul_div_ceil(x, y, z).unwrap(); - assert_eq!(result, 483_5313676) + assert_eq!(result, 483_5313676); } #[test] - fn test_fixed_mul_ceil_large_number() { - let x: u64 = 18_446_744_073; - let y: u64 = 1_000_000_000; - let denominator: u64 = 1_000_000_000; + fn test_mul_div_ceil_exact() { + // Real result = 12 + let x: u64 = 8; + let y: u64 = 3; + let z: u64 = 2; - let result = x.fixed_mul_ceil(y, denominator).unwrap(); + let result = mul_div_ceil(x, y, z).unwrap(); - assert_eq!(result, 18_446_744_073) + assert_eq!(result, 12); } #[test] - fn test_fixed_mul_ceil_phantom_overflow_uses_u128() { - let x: u64 = 18_446_744_073; - let y: u64 = 2_000_000_000; - let denominator: u64 = 1_000_000_000; + fn test_mul_div_ceil_mul_zero() { + let x: u64 = 1_5391283; + let y: u64 = 0; + let z: u64 = 1_0000001; - let result = x.fixed_mul_ceil(y, denominator).unwrap(); + let result = mul_div_ceil(x, y, z).unwrap(); - assert_eq!(result, 36_893_488_146); + assert_eq!(result, 0); } #[test] - fn test_fixed_mul_ceil_result_overflow() { - let x: u64 = 18_446_744_073_000_000_000; - let y: u64 = 2_000_000_000; - let denominator: u64 = 1_000_000_000; + fn test_mul_div_ceil_div_zero() { + let x: u64 = 1_5391283; + let y: u64 = 314_1592653; + let z: u64 = 0; - let result = x.fixed_mul_ceil(y, denominator); + let result = mul_div_ceil(x, y, z); assert_eq!(result, None); } - /********** fixed_div_floor **********/ - #[test] - fn test_fixed_div_floor_rounds_down() { - let x: u64 = 314_1592653; - let y: u64 = 1_5391280; - let denominator: u64 = 1_0000000; + fn test_mul_div_ceil_large_number() { + let x: u64 = 18_446_744_073; + let y: u64 = 1_000_000_000; + let z: u64 = 1_000_000_000; - let result = x.fixed_div_floor(y, denominator).unwrap(); + let result = mul_div_ceil(x, y, z).unwrap(); - assert_eq!(result, 204_1150997) + assert_eq!(result, 18_446_744_073); } #[test] - fn test_fixed_div_floor_large_number() { - let x: u64 = 18_446_744_073; - let y: u64 = 1_000_000_000; - let denominator: u64 = 1_000_000_000; + fn test_mul_div_ceil_small_number() { + let x: u64 = 1; + let y: u64 = 2; + let z: u64 = 3; - let result = x.fixed_div_floor(y, denominator).unwrap(); + let result = mul_div_ceil(x, y, z).unwrap(); - assert_eq!(result, 18_446_744_073) + assert_eq!(result, 1); } #[test] - fn test_fixed_div_floor_phantom_overflow_uses_u128() { - let x: u64 = 18_446_744_073; + fn test_mul_div_ceil_phantom_overflow_uses_u128() { + // u64::MAX is odd + let x: u64 = (u64::MAX - 1) / 2; let y: u64 = 2_000_000_000; - let denominator: u64 = 1_000_000_000; + let z: u64 = 1_000_000_000; - let result = x.fixed_div_floor(y, denominator).unwrap(); + let result = mul_div_ceil(x, y, z).unwrap(); - assert_eq!(result, 9_223_372_036); + assert_eq!(result, u64::MAX - 1); } #[test] - fn test_fixed_div_floor_result_overflow() { - let x: u64 = 18_446_744_073_000_000_000; + fn test_mul_div_ceil_phantom_overflow_rounds_up() { + // Real Result = 333_333_333_333.3.. + let x: u64 = 100 * 10u64.pow(9); + let y: u64 = 10 * 10u64.pow(9); + let z: u64 = 3 * 10u64.pow(9); + + let result = mul_div_ceil(x, y, z).unwrap(); + + assert_eq!(result, 333_333_333_334); + } + + #[test] + fn test_mul_div_ceil_result_overflow() { + // u64::MAX is odd + let x: u64 = (u64::MAX - 1) / 2 + 1; let y: u64 = 2_000_000_000; - let denominator: u64 = 4_000_000_000; + let z: u64 = 1_000_000_000; - let result = x.fixed_div_floor(y, denominator); + let result = mul_div_ceil(x, y, z); assert_eq!(result, None); } - /********** fixed_div_ceil **********/ + /********** fixed_mul_floor **********/ #[test] - fn test_fixed_div_ceil_rounds_up() { - let x: u64 = 314_1592653; - let y: u64 = 1_5391280; - let denominator: u64 = 1_0000000; + fn test_fixed_mul_floor() { + // Real result = 104_9522835.2 + let x: u64 = 3_1423141; + let y: u64 = 4_1234142; + let denominator: u64 = 0_1234567; - let result = x.fixed_div_ceil(y, denominator).unwrap(); + let result = x.fixed_mul_floor(y, denominator).unwrap(); + assert_eq!(result, 104_9522835); - assert_eq!(result, 204_1150998) + let invalid = x.fixed_mul_floor(y, 0); + assert_eq!(invalid, None); } + /********** fixed_mul_ceil **********/ + #[test] - fn test_fixed_div_ceil_large_number() { - let x: u64 = 18_446_744_073; - let y: u64 = 1_000_000_000; - let denominator: u64 = 1_000_000_000; + fn test_fixed_mul_ceil() { + // Real result = 104_9522835.2 + let x: u64 = 3_1423141; + let y: u64 = 4_1234142; + let denominator: u64 = 0_1234567; - let result = x.fixed_div_ceil(y, denominator).unwrap(); + let result = x.fixed_mul_ceil(y, denominator).unwrap(); + assert_eq!(result, 104_9522836); - assert_eq!(result, 18_446_744_073) + let invalid = x.fixed_mul_ceil(y, 0); + assert_eq!(invalid, None); } + /********** fixed_div_floor **********/ + #[test] - fn test_fixed_div_ceil_phantom_overflow_uses_u128() { - let x: u64 = 18_446_744_073; - let y: u64 = 2_000_000_000; - let denominator: u64 = 1_000_000_000; + fn test_fixed_div_floor() { + // Real result = 204_1150997.8 + let x: u64 = 314_1592653; + let y: u64 = 1_5391280; + let denominator: u64 = 1_0000000; - let result = x.fixed_div_ceil(y, denominator).unwrap(); + let result = x.fixed_div_floor(y, denominator).unwrap(); + assert_eq!(result, 204_1150997); - assert_eq!(result, 9_223_372_037); + let invalid = x.fixed_div_floor(0, denominator); + assert_eq!(invalid, None); } + /********** fixed_div_ceil **********/ + #[test] - fn test_fixed_div_ceil_result_overflow() { - let x: u64 = 18_446_744_073_000_000_000; - let y: u64 = 2_000_000_000; - let denominator: u64 = 4_000_000_000; + fn test_fixed_div_ceil() { + // Real result = 204_1150997.8 + let x: u64 = 314_1592653; + let y: u64 = 1_5391280; + let denominator: u64 = 1_0000000; - let result = x.fixed_div_ceil(y, denominator); + let result = x.fixed_div_ceil(y, denominator).unwrap(); + assert_eq!(result, 204_1150998); - assert_eq!(result, None); + let invalid = x.fixed_div_ceil(0, denominator); + assert_eq!(invalid, None); } }
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-x5m4-43jf-hh65ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-24783ghsaADVISORY
- github.com/script3/soroban-fixed-point-math/commit/c9233f7094198a49ed66a4d75786a8a3755c936aghsax_refsource_MISCWEB
- github.com/script3/soroban-fixed-point-math/releases/tag/v1.3.1ghsax_refsource_MISCWEB
- github.com/script3/soroban-fixed-point-math/releases/tag/v1.4.1ghsax_refsource_MISCWEB
- github.com/script3/soroban-fixed-point-math/security/advisories/GHSA-x5m4-43jf-hh65ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.