ifc_lite_geometry/kernel/fixed_int/
mod.rs1use core::cmp::Ordering;
41use core::ops::{Add, Div, Mul, Neg, Rem, Sub};
42use num_traits::{
43 CheckedAdd, CheckedMul, CheckedSub, FromPrimitive, Num, One, Signed, ToPrimitive, Zero,
44};
45
46mod mul;
47use mul::{mul_full, mul_low};
48
49#[derive(Clone, Copy, Debug, PartialEq, Eq)]
53pub struct FixedInt<const K: usize>([u64; K]);
54
55impl<const K: usize> FixedInt<K> {
64 #[cfg(test)]
66 #[inline]
67 pub(crate) const fn from_limbs(limbs: [u64; K]) -> Self {
68 FixedInt(limbs)
69 }
70
71 #[inline]
72 pub fn is_zero(&self) -> bool {
73 self.0.iter().all(|&l| l == 0)
74 }
75
76 #[inline]
77 pub fn is_one(&self) -> bool {
78 self.0[0] == 1 && self.0[1..].iter().all(|&l| l == 0)
79 }
80
81 #[inline]
87 pub fn is_negative(self) -> bool {
88 self.0[K - 1] >> 63 == 1
89 }
90}
91
92#[inline]
95fn is_neg<const K: usize>(a: &[u64; K]) -> bool {
96 a[K - 1] >> 63 == 1
97}
98
99#[inline]
102fn negate<const K: usize>(a: &[u64; K]) -> [u64; K] {
103 let mut out = [0u64; K];
104 let mut carry = 1u64;
105 for i in 0..K {
106 let (v, c) = (!a[i]).overflowing_add(carry);
107 out[i] = v;
108 carry = c as u64;
109 }
110 out
111}
112
113#[inline]
115fn bit_length<const K: usize>(a: &[u64; K]) -> usize {
116 for i in (0..K).rev() {
117 if a[i] != 0 {
118 return i * 64 + (64 - a[i].leading_zeros() as usize);
119 }
120 }
121 0
122}
123
124#[inline]
127fn magnitude<const K: usize>(a: &FixedInt<K>) -> ([u64; K], bool) {
128 if is_neg(&a.0) {
129 (negate(&a.0), true)
130 } else {
131 (a.0, false)
132 }
133}
134
135
136#[inline]
140fn wrapping_add<const K: usize>(a: &[u64; K], b: &[u64; K]) -> [u64; K] {
141 let mut out = [0u64; K];
142 let mut carry = 0u64;
143 for i in 0..K {
144 let (s1, c1) = a[i].overflowing_add(b[i]);
145 let (s2, c2) = s1.overflowing_add(carry);
146 out[i] = s2;
147 carry = (c1 as u64) | (c2 as u64);
149 }
150 out
151}
152
153#[inline]
155fn wrapping_sub<const K: usize>(a: &[u64; K], b: &[u64; K]) -> [u64; K] {
156 let mut out = [0u64; K];
157 let mut borrow = 0u64;
158 for i in 0..K {
159 let (d1, b1) = a[i].overflowing_sub(b[i]);
160 let (d2, b2) = d1.overflowing_sub(borrow);
161 out[i] = d2;
162 borrow = (b1 as u64) | (b2 as u64);
163 }
164 out
165}
166
167#[inline]
169fn checked_add_limbs<const K: usize>(a: &FixedInt<K>, b: &FixedInt<K>) -> Option<FixedInt<K>> {
170 let out = wrapping_add(&a.0, &b.0);
171 let sa = a.0[K - 1] >> 63;
172 let sb = b.0[K - 1] >> 63;
173 let sr = out[K - 1] >> 63;
174 if sa == sb && sr != sa {
176 None
177 } else {
178 Some(FixedInt(out))
179 }
180}
181
182#[inline]
184fn checked_sub_limbs<const K: usize>(a: &FixedInt<K>, b: &FixedInt<K>) -> Option<FixedInt<K>> {
185 let out = wrapping_sub(&a.0, &b.0);
186 let sa = a.0[K - 1] >> 63;
187 let sb = b.0[K - 1] >> 63;
188 let sr = out[K - 1] >> 63;
189 if sa != sb && sr != sa {
191 None
192 } else {
193 Some(FixedInt(out))
194 }
195}
196
197#[inline]
210fn checked_mul_limbs<const K: usize>(a: &FixedInt<K>, b: &FixedInt<K>) -> Option<FixedInt<K>> {
211 let (ma, sa) = magnitude(a);
212 let (mb, sb) = magnitude(b);
213 let la = bit_length(&ma);
214 let lb = bit_length(&mb);
215 if la == 0 || lb == 0 {
216 return Some(FixedInt([0u64; K]));
217 }
218 let neg = sa ^ sb;
219 let w = K * 64;
220
221 if la + lb <= w - 1 {
222 let lo = mul_low(&ma, &mb);
224 let out = if neg { negate(&lo) } else { lo };
225 return Some(FixedInt(out));
226 }
227 if la + lb >= w + 2 {
228 return None;
230 }
231
232 debug_assert!(K <= 32, "FixedInt checked_mul scratch supports only K <= 32");
234 let n2 = 2 * K;
235 let mut full = [0u64; 64];
236 mul_full(&ma, &mb, &mut full);
237 if neg {
238 let mut c = 1u64;
240 for slot in full.iter_mut().take(n2) {
241 let (v, cc) = (!*slot).overflowing_add(c);
242 *slot = v;
243 c = cc as u64;
244 }
245 }
246 let ext = if full[K - 1] >> 63 == 1 { u64::MAX } else { 0 };
248 for &limb in &full[K..n2] {
249 if limb != ext {
250 return None;
251 }
252 }
253 let mut out = [0u64; K];
254 out.copy_from_slice(&full[..K]);
255 Some(FixedInt(out))
256}
257
258#[derive(Debug, PartialEq, Eq)]
263pub struct ParseFixedIntError;
264
265#[inline]
269fn to_le_scratch<const K: usize>(x: &FixedInt<K>) -> [u8; 256] {
270 debug_assert!(K <= 32, "FixedInt bnum bridge supports only K <= 32");
271 let mut buf = [0u8; 256];
272 for i in 0..K {
273 buf[i * 8..i * 8 + 8].copy_from_slice(&x.0[i].to_le_bytes());
274 }
275 buf
276}
277
278#[inline]
280fn from_le_scratch<const K: usize>(buf: &[u8]) -> FixedInt<K> {
281 let mut limbs = [0u64; K];
282 for i in 0..K {
283 let mut b = [0u8; 8];
284 b.copy_from_slice(&buf[i * 8..i * 8 + 8]);
285 limbs[i] = u64::from_le_bytes(b);
286 }
287 FixedInt(limbs)
288}
289
290macro_rules! bnum_binary {
296 ($K:expr, $ab:expr, $bb:expr, |$x:ident, $y:ident| $op:expr) => {{
297 let nb = $K * 8;
298 match $K {
299 4 => {
300 let $x = bnum::types::I256::from_le_slice(&$ab[..nb]).unwrap();
301 let $y = bnum::types::I256::from_le_slice(&$bb[..nb]).unwrap();
302 from_le_scratch::<$K>(&($op).to_le_bytes())
303 }
304 8 => {
305 let $x = bnum::types::I512::from_le_slice(&$ab[..nb]).unwrap();
306 let $y = bnum::types::I512::from_le_slice(&$bb[..nb]).unwrap();
307 from_le_scratch::<$K>(&($op).to_le_bytes())
308 }
309 16 => {
310 let $x = bnum::types::I1024::from_le_slice(&$ab[..nb]).unwrap();
311 let $y = bnum::types::I1024::from_le_slice(&$bb[..nb]).unwrap();
312 from_le_scratch::<$K>(&($op).to_le_bytes())
313 }
314 32 => {
315 let $x = bnum::types::I2048::from_le_slice(&$ab[..nb]).unwrap();
316 let $y = bnum::types::I2048::from_le_slice(&$bb[..nb]).unwrap();
317 from_le_scratch::<$K>(&($op).to_le_bytes())
318 }
319 _ => panic!("FixedInt<{}>: bnum bridge supports only K in {{4,8,16,32}}", $K),
320 }
321 }};
322}
323
324#[inline]
325fn bnum_to_f64<const K: usize>(buf: &[u8]) -> Option<f64> {
326 let nb = K * 8;
327 match K {
328 4 => bnum::types::I256::from_le_slice(&buf[..nb]).unwrap().to_f64(),
329 8 => bnum::types::I512::from_le_slice(&buf[..nb]).unwrap().to_f64(),
330 16 => bnum::types::I1024::from_le_slice(&buf[..nb]).unwrap().to_f64(),
331 32 => bnum::types::I2048::from_le_slice(&buf[..nb]).unwrap().to_f64(),
332 _ => panic!("FixedInt<{K}>: bnum bridge supports only K in {{4,8,16,32}}"),
333 }
334}
335
336#[inline]
337fn bnum_from_str_radix<const K: usize>(
338 s: &str,
339 radix: u32,
340) -> Result<FixedInt<K>, ParseFixedIntError> {
341 match K {
342 4 => bnum::types::I256::from_str_radix(s, radix)
343 .map(|v| from_le_scratch::<K>(&v.to_le_bytes()))
344 .map_err(|_| ParseFixedIntError),
345 8 => bnum::types::I512::from_str_radix(s, radix)
346 .map(|v| from_le_scratch::<K>(&v.to_le_bytes()))
347 .map_err(|_| ParseFixedIntError),
348 16 => bnum::types::I1024::from_str_radix(s, radix)
349 .map(|v| from_le_scratch::<K>(&v.to_le_bytes()))
350 .map_err(|_| ParseFixedIntError),
351 32 => bnum::types::I2048::from_str_radix(s, radix)
352 .map(|v| from_le_scratch::<K>(&v.to_le_bytes()))
353 .map_err(|_| ParseFixedIntError),
354 _ => panic!("FixedInt<{K}>: bnum bridge supports only K in {{4,8,16,32}}"),
355 }
356}
357
358impl<const K: usize> Add for FixedInt<K> {
364 type Output = Self;
365 #[inline]
366 fn add(self, rhs: Self) -> Self {
367 FixedInt(wrapping_add(&self.0, &rhs.0))
368 }
369}
370
371impl<const K: usize> Sub for FixedInt<K> {
372 type Output = Self;
373 #[inline]
374 fn sub(self, rhs: Self) -> Self {
375 FixedInt(wrapping_sub(&self.0, &rhs.0))
376 }
377}
378
379impl<const K: usize> Mul for FixedInt<K> {
380 type Output = Self;
381 #[inline]
382 fn mul(self, rhs: Self) -> Self {
383 FixedInt(mul_low(&self.0, &rhs.0))
386 }
387}
388
389impl<const K: usize> Neg for FixedInt<K> {
390 type Output = Self;
391 #[inline]
392 fn neg(self) -> Self {
393 FixedInt(negate(&self.0))
394 }
395}
396
397impl<const K: usize> Div for FixedInt<K> {
398 type Output = Self;
399 #[inline]
400 fn div(self, rhs: Self) -> Self {
401 let ab = to_le_scratch(&self);
402 let bb = to_le_scratch(&rhs);
403 bnum_binary!(K, ab, bb, |x, y| x / y)
404 }
405}
406
407impl<const K: usize> Rem for FixedInt<K> {
408 type Output = Self;
409 #[inline]
410 fn rem(self, rhs: Self) -> Self {
411 let ab = to_le_scratch(&self);
412 let bb = to_le_scratch(&rhs);
413 bnum_binary!(K, ab, bb, |x, y| x % y)
414 }
415}
416
417impl<const K: usize> Zero for FixedInt<K> {
420 #[inline]
421 fn zero() -> Self {
422 FixedInt([0u64; K])
423 }
424 #[inline]
425 fn is_zero(&self) -> bool {
426 self.0.iter().all(|&l| l == 0)
427 }
428}
429
430impl<const K: usize> One for FixedInt<K> {
431 #[inline]
432 fn one() -> Self {
433 let mut limbs = [0u64; K];
434 limbs[0] = 1;
435 FixedInt(limbs)
436 }
437 #[inline]
438 fn is_one(&self) -> bool {
439 self.0[0] == 1 && self.0[1..].iter().all(|&l| l == 0)
440 }
441}
442
443impl<const K: usize> Num for FixedInt<K> {
444 type FromStrRadixErr = ParseFixedIntError;
445 #[inline]
446 fn from_str_radix(s: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr> {
447 bnum_from_str_radix::<K>(s, radix)
448 }
449}
450
451impl<const K: usize> Signed for FixedInt<K> {
452 #[inline]
453 fn abs(&self) -> Self {
454 if is_neg(&self.0) {
455 FixedInt(negate(&self.0))
456 } else {
457 *self
458 }
459 }
460 #[inline]
461 fn abs_sub(&self, other: &Self) -> Self {
462 if self <= other {
463 FixedInt([0u64; K])
464 } else {
465 *self - *other
466 }
467 }
468 #[inline]
469 fn signum(&self) -> Self {
470 if is_neg(&self.0) {
471 FixedInt([u64::MAX; K]) } else if self.is_zero() {
473 FixedInt([0u64; K])
474 } else {
475 <Self as One>::one()
476 }
477 }
478 #[inline]
479 fn is_positive(&self) -> bool {
480 !is_neg(&self.0) && !self.is_zero()
481 }
482 #[inline]
483 fn is_negative(&self) -> bool {
484 is_neg(&self.0)
485 }
486}
487
488impl<const K: usize> FromPrimitive for FixedInt<K> {
489 #[inline]
490 fn from_i64(n: i64) -> Option<Self> {
491 let mut limbs = if n < 0 { [u64::MAX; K] } else { [0u64; K] };
492 limbs[0] = n as u64;
493 Some(FixedInt(limbs))
494 }
495 #[inline]
496 fn from_u64(n: u64) -> Option<Self> {
497 let mut limbs = [0u64; K];
498 limbs[0] = n;
499 Some(FixedInt(limbs))
500 }
501}
502
503impl<const K: usize> ToPrimitive for FixedInt<K> {
504 #[inline]
505 fn to_i64(&self) -> Option<i64> {
506 if is_neg(&self.0) {
507 for i in 1..K {
508 if self.0[i] != u64::MAX {
509 return None;
510 }
511 }
512 let v = self.0[0];
513 if v >> 63 == 1 {
514 Some(v as i64)
515 } else {
516 None
517 }
518 } else {
519 for i in 1..K {
520 if self.0[i] != 0 {
521 return None;
522 }
523 }
524 let v = self.0[0];
525 if v >> 63 == 0 {
526 Some(v as i64)
527 } else {
528 None
529 }
530 }
531 }
532 #[inline]
533 fn to_u64(&self) -> Option<u64> {
534 if is_neg(&self.0) {
535 return None;
536 }
537 for i in 1..K {
538 if self.0[i] != 0 {
539 return None;
540 }
541 }
542 Some(self.0[0])
543 }
544 #[inline]
545 fn to_f64(&self) -> Option<f64> {
546 let buf = to_le_scratch(self);
549 bnum_to_f64::<K>(&buf)
550 }
551}
552
553impl<const K: usize> CheckedAdd for FixedInt<K> {
554 #[inline]
555 fn checked_add(&self, v: &Self) -> Option<Self> {
556 checked_add_limbs(self, v)
557 }
558}
559
560impl<const K: usize> CheckedSub for FixedInt<K> {
561 #[inline]
562 fn checked_sub(&self, v: &Self) -> Option<Self> {
563 checked_sub_limbs(self, v)
564 }
565}
566
567impl<const K: usize> CheckedMul for FixedInt<K> {
568 #[inline]
569 fn checked_mul(&self, v: &Self) -> Option<Self> {
570 checked_mul_limbs(self, v)
571 }
572}
573
574impl<const K: usize> Ord for FixedInt<K> {
575 #[inline]
576 fn cmp(&self, other: &Self) -> Ordering {
577 match (is_neg(&self.0), is_neg(&other.0)) {
578 (true, false) => Ordering::Less,
579 (false, true) => Ordering::Greater,
580 _ => {
582 for i in (0..K).rev() {
583 match self.0[i].cmp(&other.0[i]) {
584 Ordering::Equal => {}
585 o => return o,
586 }
587 }
588 Ordering::Equal
589 }
590 }
591 }
592}
593
594impl<const K: usize> PartialOrd for FixedInt<K> {
595 #[inline]
596 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
597 Some(self.cmp(other))
598 }
599}
600
601#[cfg(test)]
604mod tests;