1use crate::constants::*;
2use crate::double_word::DoubleWord;
3use crate::error::SolMathError;
4use crate::overflow::{checked_mul_div_i, checked_mul_div_u, checked_mul_div_rem_u};
5
6#[inline]
21pub fn fp_mul(a: u128, b: u128) -> Result<u128, SolMathError> {
22 match a.checked_mul(b) {
23 Some(p) => Ok(p / SCALE),
24 None => checked_mul_div_u(a, b, SCALE).ok_or(SolMathError::Overflow),
25 }
26}
27
28#[inline]
42pub fn fp_mul_i(a: i128, b: i128) -> Result<i128, SolMathError> {
43 match a.checked_mul(b) {
44 Some(p) => Ok(p / SCALE_I),
45 None => checked_mul_div_i(a, b, SCALE_I),
46 }
47}
48
49#[allow(dead_code)]
53#[inline]
54pub(crate) fn fp_mul_i_fast(a: i128, b: i128) -> i128 {
55 a * b / SCALE_I
56}
57
58#[inline]
61pub fn fp_mul_i_round(a: i128, b: i128) -> Result<i128, SolMathError> {
62 match a.checked_mul(b) {
63 Some(p) => {
64 if p >= 0 {
65 match p.checked_add(SCALE_I / 2) {
66 Some(v) => Ok(v / SCALE_I),
67 None => Ok(p / SCALE_I + 1),
68 }
69 } else {
70 match p.checked_sub(SCALE_I / 2) {
71 Some(v) => Ok(v / SCALE_I),
72 None => Ok(p / SCALE_I - 1),
73 }
74 }
75 }
76 None => {
77 let neg = (a < 0) != (b < 0);
79 let (q, rem) = checked_mul_div_rem_u(a.unsigned_abs(), b.unsigned_abs(), SCALE)
80 .ok_or(SolMathError::Overflow)?;
81 let rounded = if rem >= SCALE / 2 {
82 q.checked_add(1).ok_or(SolMathError::Overflow)?
83 } else {
84 q
85 };
86 if neg {
87 if rounded == (1u128 << 127) { Ok(i128::MIN) }
88 else if rounded < (1u128 << 127) { Ok(-(rounded as i128)) }
89 else { Err(SolMathError::Overflow) }
90 } else if rounded <= i128::MAX as u128 { Ok(rounded as i128) }
91 else { Err(SolMathError::Overflow) }
92 }
93 }
94}
95
96#[inline]
111pub fn fp_div(a: u128, b: u128) -> Result<u128, SolMathError> {
112 if b == 0 {
113 return Err(SolMathError::DivisionByZero);
114 }
115 fp_div_rem_experimental_u(a, b)
116 .map(|(q, _)| q)
117 .ok_or(SolMathError::Overflow)
118}
119
120#[inline]
123pub fn fp_div_i(a: i128, b: i128) -> Result<i128, SolMathError> {
124 if b == 0 {
125 return Err(SolMathError::DivisionByZero);
126 }
127 if a == 0 {
128 return Ok(0);
129 }
130
131 let neg = (a < 0) ^ (b < 0);
132 let mag = fp_div_rem_experimental_u(a.unsigned_abs(), b.unsigned_abs());
133
134 match mag {
135 Some((q, _)) => {
136 if neg {
137 if q == (1u128 << 127) {
138 Ok(i128::MIN)
139 } else if q < (1u128 << 127) {
140 Ok(-(q as i128))
141 } else {
142 Err(SolMathError::Overflow)
143 }
144 } else if q <= i128::MAX as u128 {
145 Ok(q as i128)
146 } else {
147 Err(SolMathError::Overflow)
148 }
149 }
150 None => Err(SolMathError::Overflow),
151 }
152}
153
154#[inline]
157pub fn fp_div_floor(a: u128, b: u128) -> Result<u128, SolMathError> {
158 fp_div(a, b) }
160
161#[inline]
164pub fn fp_div_ceil(a: u128, b: u128) -> Result<u128, SolMathError> {
165 if b == 0 {
166 return Err(SolMathError::DivisionByZero);
167 }
168 match fp_div_rem_experimental_u(a, b) {
169 Some((q, rem)) => {
170 if rem > 0 { Ok(q.checked_add(1).ok_or(SolMathError::Overflow)?) } else { Ok(q) }
171 }
172 None => Err(SolMathError::Overflow),
173 }
174}
175
176#[allow(dead_code)]
180#[inline]
181pub(crate) fn fp_div_i_round(a: i128, b: i128) -> Result<i128, SolMathError> {
182 if b == 0 { return Err(SolMathError::DivisionByZero); }
183 if a == 0 { return Ok(0); }
184
185 let neg = (a < 0) ^ (b < 0);
186 let (q, r) = fp_div_rem_experimental_u(a.unsigned_abs(), b.unsigned_abs())
187 .ok_or(SolMathError::Overflow)?;
188 let round_up = r >= b.unsigned_abs() / 2;
189 let q = if round_up { q.checked_add(1).ok_or(SolMathError::Overflow)? } else { q };
190 if neg {
191 if q == (1u128 << 127) { Ok(i128::MIN) }
192 else if q < (1u128 << 127) { Ok(-(q as i128)) }
193 else { Err(SolMathError::Overflow) }
194 } else if q <= i128::MAX as u128 { Ok(q as i128) }
195 else { Err(SolMathError::Overflow) }
196}
197
198#[inline]
200pub(crate) fn fp_div_fractional_tail_u(a: u128, b: u128) -> Option<(u128, u128)> {
201 debug_assert!(b != 0);
202 if a == 0 {
203 return Some((0, 0));
204 }
205 if a <= FP_DIV_THIN_MAX {
206 let scaled = a * SCALE;
208 return Some((scaled / b, scaled % b));
209 }
210 checked_mul_div_rem_u(a, SCALE, b)
211}
212
213#[inline]
215pub(crate) fn fp_div_rem_experimental_u(a: u128, b: u128) -> Option<(u128, u128)> {
216 if b == 0 {
217 return None;
218 }
219 if a == 0 {
220 return Some((0, 0));
221 }
222
223 if a <= FP_DIV_THIN_MAX {
225 let scaled = a * SCALE;
227 return Some((scaled / b, scaled % b));
228 }
229
230 let q = a / b;
234 if q == 0 {
235 return checked_mul_div_rem_u(a, SCALE, b);
236 }
237 if q > FP_DIV_THIN_MAX {
238 return None;
239 }
240
241 let r = a - q * b;
243 let base = q * SCALE;
245 if r == 0 {
246 return Some((base, 0));
247 }
248
249 let (frac, rem) = fp_div_fractional_tail_u(r, b)?;
250 Some((base.checked_add(frac)?, rem))
251}
252
253#[allow(dead_code)]
255#[inline]
256pub(crate) fn isqrt_u128(n: u128) -> u128 {
257 if n == 0 {
258 return 0;
259 }
260 let mut x = 1u128 << ((128 - n.leading_zeros() + 1) / 2);
261 loop {
262 let x1 = (x + n / x) / 2;
263 if x1 >= x {
264 return x;
265 }
266 x = x1;
267 }
268}
269
270pub(crate) fn sqrt_scaled_newton(scaled: u128) -> u128 {
272 let bit_len = 128 - scaled.leading_zeros();
273 let mut guess: u128 = 1u128 << ((bit_len + 1) / 2);
274 for _ in 0..8 {
275 if guess == 0 {
276 break;
277 }
278 let new_guess = (guess + scaled / guess) / 2;
279 if new_guess == guess || new_guess + 1 == guess {
280 guess = guess.min(new_guess);
281 break;
282 }
283 guess = new_guess;
284 }
285 debug_assert!(
286 guess.checked_mul(guess).map_or(false, |sq| sq <= scaled)
287 && (guess + 1).checked_mul(guess + 1).map_or(true, |sq| sq > scaled),
288 "sqrt_scaled_newton: post-check failed for scaled={}, guess={}",
289 scaled, guess
290 );
291 guess
292}
293
294pub(crate) fn wide_mul_u128(a: u128, b: u128) -> (u128, u128) {
296 let mask = u128::from(u64::MAX);
297 let a0 = a & mask;
298 let a1 = a >> 64;
299 let b0 = b & mask;
300 let b1 = b >> 64;
301
302 let p0 = a0 * b0;
303 let p1 = a0 * b1;
304 let p2 = a1 * b0;
305 let p3 = a1 * b1;
306
307 let carry0 = p0 >> 64;
308 let mid = (p1 & mask) + (p2 & mask) + carry0;
309 let lo = (p0 & mask) | ((mid & mask) << 64);
310 let hi = p3 + (p1 >> 64) + (p2 >> 64) + (mid >> 64);
311 (hi, lo)
312}
313
314pub(crate) fn cmp_wide(lhs: (u128, u128), rhs: (u128, u128)) -> core::cmp::Ordering {
316 if lhs.0 != rhs.0 {
317 lhs.0.cmp(&rhs.0)
318 } else {
319 lhs.1.cmp(&rhs.1)
320 }
321}
322
323pub(crate) fn cmp_sqrt_candidate(candidate: u128, x: u128) -> core::cmp::Ordering {
325 cmp_wide(wide_mul_u128(candidate, candidate), wide_mul_u128(x, SCALE))
326}
327
328pub fn fp_sqrt(x: u128) -> Result<u128, SolMathError> {
342 if x == 0 {
343 return Ok(0);
344 }
345
346 if let Some(scaled) = x.checked_mul(SCALE) {
347 return Ok(sqrt_scaled_newton(scaled));
348 }
349
350 let mut reduced = x;
351 let mut scale_back = 1u128;
352 while reduced > u128::MAX / SCALE {
353 reduced = (reduced + 2) / 4;
355 scale_back = scale_back.checked_mul(2).ok_or(SolMathError::Overflow)?;
356 }
357
358 let approx = sqrt_scaled_newton(reduced * SCALE)
359 .checked_mul(scale_back).ok_or(SolMathError::Overflow)?;
360
361 let mut low = approx.checked_sub(scale_back).ok_or(SolMathError::Overflow)?;
362 while cmp_sqrt_candidate(low, x).is_gt() {
363 if low == 0 {
364 break;
365 }
366 low = low.checked_sub(scale_back).ok_or(SolMathError::Overflow)?;
367 }
368
369 let mut high = approx.checked_add(scale_back).ok_or(SolMathError::Overflow)?;
370 while !cmp_sqrt_candidate(high, x).is_gt() {
371 low = high;
372 high = high.checked_add(scale_back).ok_or(SolMathError::Overflow)?;
373 }
374
375 while low + 1 < high {
376 let mid = low + (high - low) / 2;
377 if cmp_sqrt_candidate(mid, x).is_gt() {
378 high = mid;
379 } else {
380 low = mid;
381 }
382 }
383
384 Ok(low)
385}
386
387#[inline]
390pub fn fp_mul_round(a: u128, b: u128) -> Result<u128, SolMathError> {
391 match checked_mul_div_rem_u(a, b, SCALE) {
392 Some((q, r)) => {
393 if r >= SCALE / 2 { q.checked_add(1).ok_or(SolMathError::Overflow) } else { Ok(q) }
394 }
395 None => Err(SolMathError::Overflow),
396 }
397}
398
399#[inline]
409pub fn fp_mul_i_round_dw(a: i128, b: i128) -> Result<DoubleWord, SolMathError> {
410 if a == 0 || b == 0 {
411 return Ok(DoubleWord::new_raw(0, 0));
412 }
413
414 let neg = (a < 0) != (b < 0);
415 let aa = a.unsigned_abs();
416 let bb = b.unsigned_abs();
417
418 let (q_trunc, r_trunc) = checked_mul_div_rem_u(aa, bb, SCALE)
419 .ok_or(SolMathError::Overflow)?;
420 let half = SCALE / 2;
423 let (q, lo) = if r_trunc >= half {
424 (q_trunc + 1, r_trunc as i128 - SCALE_I) } else {
426 (q_trunc, r_trunc as i128) };
428
429 if neg {
430 if q == (1u128 << 127) {
431 if lo != 0 {
432 return Err(SolMathError::Overflow);
433 }
434 Ok(DoubleWord::new_raw(i128::MIN, 0))
435 } else if q < (1u128 << 127) {
436 Ok(DoubleWord::new_raw(-(q as i128), -lo))
437 } else {
438 Err(SolMathError::Overflow)
439 }
440 } else if q <= i128::MAX as u128 {
441 Ok(DoubleWord::new_raw(q as i128, lo))
442 } else {
443 Err(SolMathError::Overflow)
444 }
445}
446
447#[inline]
450pub fn fp_div_round(a: u128, b: u128) -> Result<u128, SolMathError> {
451 if b == 0 {
452 return Err(SolMathError::DivisionByZero);
453 }
454 let (q, r) = fp_div_rem_experimental_u(a, b).ok_or(SolMathError::Overflow)?;
455 Ok(if r >= b / 2 {
456 q.checked_add(1).ok_or(SolMathError::Overflow)?
457 } else {
458 q
459 })
460}
461
462#[cfg(test)]
463mod tests {
464 use super::*;
465 use crate::constants::{SCALE, SCALE_I};
466 use crate::error::SolMathError;
467
468 #[test]
471 fn test_fp_mul_round_exact() {
472 assert_eq!(fp_mul_round(2 * SCALE, 3 * SCALE).unwrap(), 6 * SCALE);
473 assert_eq!(fp_mul_round(SCALE, SCALE).unwrap(), SCALE);
474 assert_eq!(fp_mul_round(0, SCALE).unwrap(), 0);
475 assert_eq!(fp_mul_round(SCALE, 0).unwrap(), 0);
476 }
477
478 #[test]
479 fn test_fp_mul_round_vs_truncating() {
480 let cases: &[(u128, u128)] = &[
481 (SCALE / 3, SCALE), (SCALE, SCALE / 3),
482 (SCALE / 7, SCALE / 11), (2 * SCALE, SCALE / 3),
483 (SCALE + 1, SCALE + 1), (SCALE * 1000, SCALE / 997),
484 ];
485 for &(a, b) in cases {
486 let trunc = fp_mul(a, b).unwrap();
487 let round = fp_mul_round(a, b).unwrap();
488 assert!(round == trunc || round == trunc + 1,
489 "a={}, b={}: trunc={}, round={}", a, b, trunc, round);
490 }
491 }
492
493 #[test]
494 fn test_fp_mul_round_large_no_panic() {
495 let large = u128::MAX / SCALE;
496 let _ = fp_mul_round(large, SCALE);
497 let _ = fp_mul_round(large, 2);
498 let _ = fp_mul_round(SCALE, large);
499 }
500
501 #[test]
502 fn test_fp_mul_round_overflow_is_error() {
503 assert!(matches!(fp_mul_round(u128::MAX, u128::MAX), Err(SolMathError::Overflow)));
504 }
505
506 #[test]
509 fn test_dw_mul_quotient_matches_fp_mul_i_round() {
510 let values: &[i128] = &[
511 0, 1, -1, 2, -2,
512 SCALE_I / 2, -SCALE_I / 2,
513 SCALE_I, -SCALE_I,
514 SCALE_I + 1, -(SCALE_I + 1),
515 SCALE_I - 1, -(SCALE_I - 1),
516 SCALE_I * 2, -(SCALE_I * 2),
517 SCALE_I * 50, -(SCALE_I * 50),
518 SCALE_I / 3, -(SCALE_I / 3),
519 SCALE_I / 7, -(SCALE_I / 7),
520 999_999_999_999, -999_999_999_999,
521 500_000_000_001, -500_000_000_001,
522 ];
523 for &a in values {
524 for &b in values {
525 if a.checked_mul(b).is_some() {
526 let expected = fp_mul_i_round(a, b).unwrap();
527 let dw = fp_mul_i_round_dw(a, b).unwrap();
528 assert_eq!(dw.hi(), expected,
529 "Quotient mismatch: a={}, b={}, expected={}, got={}", a, b, expected, dw.hi());
530 }
531 }
532 }
533 }
534
535 #[test]
536 fn test_dw_mul_remainder_bounded() {
537 let values: &[i128] = &[
538 0, 1, -1, SCALE_I, -SCALE_I, SCALE_I / 3, -SCALE_I / 7,
539 SCALE_I * 50, -SCALE_I * 50, 999_999_999_999,
540 ];
541 for &a in values {
542 for &b in values {
543 let dw = fp_mul_i_round_dw(a, b).unwrap();
544 assert!(dw.lo().abs() < SCALE_I,
545 "Remainder out of bounds: a={}, b={}, lo={}", a, b, dw.lo());
546 }
547 }
548 }
549
550 #[test]
551 fn test_dw_mul_remainder_sign_convention() {
552 let dw = fp_mul_i_round_dw(SCALE_I / 3, SCALE_I).unwrap();
553 assert!(dw.lo() >= 0, "Expected non-negative lo for 1/3: lo={}", dw.lo());
554
555 let dw2 = fp_mul_i_round_dw(2 * SCALE_I / 3, SCALE_I).unwrap();
556 assert!(dw2.lo().abs() < SCALE_I);
557 }
558
559 #[test]
560 fn test_dw_mul_zero() {
561 let dw = fp_mul_i_round_dw(0, SCALE_I).unwrap();
562 assert_eq!(dw.hi(), 0);
563 assert_eq!(dw.lo(), 0);
564 let dw2 = fp_mul_i_round_dw(SCALE_I, 0).unwrap();
565 assert_eq!(dw2.hi(), 0);
566 assert_eq!(dw2.lo(), 0);
567 }
568
569 #[test]
570 fn test_dw_mul_symmetry() {
571 let pos = fp_mul_i_round_dw(SCALE_I / 3, SCALE_I / 7).unwrap();
572 let neg = fp_mul_i_round_dw(-SCALE_I / 3, SCALE_I / 7).unwrap();
573 assert_eq!(pos.hi(), -neg.hi(), "hi not symmetric");
574 assert_eq!(pos.lo(), -neg.lo(), "lo not symmetric");
575 }
576
577 #[test]
578 fn test_dw_mul_allows_exact_i128_min() {
579 let dw = fp_mul_i_round_dw(i128::MIN, SCALE_I).unwrap();
580 assert_eq!(dw.hi(), i128::MIN);
581 assert_eq!(dw.lo(), 0);
582 }
583
584 #[test]
585 fn test_dw_mul_to_i128_matches_fp_mul_i_round() {
586 let values: &[i128] = &[
587 SCALE_I / 3, SCALE_I / 7, SCALE_I * 2, -SCALE_I / 3, -SCALE_I * 5,
588 ];
589 for &a in values {
590 for &b in values {
591 if a.checked_mul(b).is_some() {
592 let direct = fp_mul_i_round(a, b).unwrap();
593 let via_dw = fp_mul_i_round_dw(a, b).unwrap().to_i128();
594 assert_eq!(direct, via_dw,
595 "Roundtrip mismatch: a={}, b={}", a, b);
596 }
597 }
598 }
599 }
600
601 #[test]
604 fn test_fp_div_round_exact() {
605 assert_eq!(fp_div_round(10 * SCALE, 5 * SCALE).unwrap(), 2 * SCALE);
606 assert_eq!(fp_div_round(SCALE, SCALE).unwrap(), SCALE);
607 assert_eq!(fp_div_round(0, SCALE).unwrap(), 0);
608 }
609
610 #[test]
611 fn test_fp_div_round_rounds_up() {
612 let t = fp_div(2 * SCALE, 3 * SCALE).unwrap();
613 let r = fp_div_round(2 * SCALE, 3 * SCALE).unwrap();
614 assert_eq!(r, t + 1, "2/3 should round up");
615 }
616
617 #[test]
618 fn test_fp_div_round_no_round() {
619 let t = fp_div(SCALE, 3 * SCALE).unwrap();
620 let r = fp_div_round(SCALE, 3 * SCALE).unwrap();
621 assert_eq!(r, t, "1/3 should not round up");
622 }
623
624 #[test]
625 fn test_fp_div_round_agrees_with_signed() {
626 let cases: &[(u128, u128)] = &[
627 (SCALE, 3 * SCALE), (2 * SCALE, 3 * SCALE),
628 (7 * SCALE, 11 * SCALE), (SCALE, 7 * SCALE),
629 (100 * SCALE, 97 * SCALE),
630 ];
631 for &(a, b) in cases {
632 let unsigned_r = fp_div_round(a, b).unwrap();
633 let signed_r = fp_div_i_round(a as i128, b as i128).unwrap();
634 assert_eq!(unsigned_r as i128, signed_r,
635 "Disagree for a={}, b={}", a, b);
636 }
637 }
638
639 #[test]
640 fn test_fp_div_round_within_one_of_truncating() {
641 let values: &[u128] = &[1, 2, 3, 7, 10, 13, 100, 997, SCALE, SCALE + 1, SCALE * 1000];
642 for &a in values {
643 for &b in values {
644 let t = fp_div(a, b).unwrap();
645 let r = fp_div_round(a, b).unwrap();
646 assert!(r == t || r == t + 1,
647 "a={}, b={}: trunc={}, round={}", a, b, t, r);
648 }
649 }
650 }
651
652 #[test]
653 fn test_fp_div_round_zero_divisor() {
654 assert!(matches!(fp_div_round(SCALE, 0), Err(SolMathError::DivisionByZero)));
655 }
656
657 #[test]
658 fn test_fp_div_round_large_no_panic() {
659 let large = u128::MAX / (SCALE * 2);
660 assert!(fp_div_round(large, SCALE).is_ok());
661 assert!(fp_div_round(SCALE, large).is_ok());
662 }
663
664}