Skip to main content

oxinum_int/native/
bitwise.rs

1//! Two's-complement bitwise operations on [`BigInt`]:
2//! `BitAnd`/`BitOr`/`BitXor`/`Not`/`Shl`/`Shr`.
3//!
4//! # Two's-complement semantics
5//!
6//! `BigInt` represents an infinite-precision signed integer. For bitwise
7//! operations we use the mathematical two's-complement model: every value has
8//! an infinite conceptual bit string. Positive numbers are padded with infinite
9//! zeros on the left; negative numbers are padded with infinite ones (their
10//! mathematical two's-complement of their magnitude never terminates).
11//!
12//! ## Algorithm overview
13//!
14//! For `&`, `|`, `^` on `BigInt`:
15//! 1. Choose `nlimbs = max(|a|.limbs, |b|.limbs) + 1` (the extra limb absorbs
16//!    the sign-extension and prevents the result from crossing back through
17//!    the sign boundary for most cases).
18//! 2. Convert both operands to their two's-complement limb vectors of length
19//!    `nlimbs` via [`to_twos_complement`].
20//! 3. Apply the operation limb-wise.
21//! 4. Decode the result back to a `BigInt` via [`from_twos_complement`].
22//!
23//! ## `Not`
24//!
25//! The mathematical rule is `!x = -(x) - 1`:
26//! - `!0 = -1`
27//! - `!5 = -6`
28//! - `!(-1) = 0`
29//! - `!(-6) = 5`
30//!
31//! ## Arithmetic right shift
32//!
33//! `>>` is **arithmetic** (sign-extending) shift — it equals floor division by
34//! `2^k`. For `k >= 64*limbs` the result is `0` (positive) or `-1` (negative).
35//!
36//! For negative `self = -m` (m > 0):
37//! ```text
38//! floor(-m / 2^k) = -ceil(m / 2^k) = -(((m - 1) >> k) + 1)
39//! ```
40
41use super::int::BigInt;
42use super::uint::BigUint;
43use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not, Shl, Shr};
44use oxinum_core::Sign;
45
46// ---------------------------------------------------------------------------
47// Two's-complement helpers
48// ---------------------------------------------------------------------------
49
50/// Convert `n` to its two's-complement representation as a `Vec<u64>` of
51/// exactly `nlimbs` limbs (little-endian).
52///
53/// - `n >= 0`: copy `n.magnitude().as_limbs()`, zero-pad to `nlimbs`.
54/// - `n < 0` (value = `-m`, m > 0): pad `m` to `nlimbs`, then negate in
55///   two's complement (flip all bits, add 1 with carry).
56fn to_twos_complement(n: &BigInt, nlimbs: usize) -> Vec<u64> {
57    let mag = n.magnitude().as_limbs();
58    let mut out = vec![0u64; nlimbs];
59    // Copy the magnitude limbs (they fit because nlimbs >= mag.len()).
60    let copy_len = mag.len().min(nlimbs);
61    out[..copy_len].copy_from_slice(&mag[..copy_len]);
62
63    if n.is_negative() {
64        // Negate in two's complement: flip all bits then add 1.
65        for limb in out.iter_mut() {
66            *limb = !*limb;
67        }
68        // Add 1 with carry propagation.
69        let mut carry: u64 = 1;
70        for limb in out.iter_mut() {
71            let (v, c) = limb.overflowing_add(carry);
72            *limb = v;
73            carry = c as u64;
74            if carry == 0 {
75                break;
76            }
77        }
78        // Any remaining carry means we overflowed nlimbs; since the
79        // true mathematical value is exact, this can only happen if
80        // m == 0 (which is forbidden for negative BigInt by the canonical-zero
81        // invariant) or if nlimbs is absurdly small.
82    }
83    out
84}
85
86/// Decode a two's-complement limb vector (little-endian) back to a `BigInt`.
87///
88/// Sign is determined by the MSB of `limbs[n-1]`. For a positive result,
89/// the limbs are directly the magnitude. For a negative result, the magnitude
90/// is recovered by two's-complement negation of the limb vector.
91fn from_twos_complement(limbs: &[u64]) -> BigInt {
92    if limbs.is_empty() {
93        return BigInt::zero();
94    }
95    let top = limbs[limbs.len() - 1];
96    if top >> 63 == 0 {
97        // Non-negative: the limbs *are* the magnitude.
98        BigInt::from_parts(Sign::Positive, BigUint::from_le_limbs(limbs))
99    } else {
100        // Negative: recover magnitude via two's-complement negation.
101        let mut neg: Vec<u64> = limbs.to_vec();
102        for v in neg.iter_mut() {
103            *v = !*v;
104        }
105        let mut carry: u64 = 1;
106        for v in neg.iter_mut() {
107            let (nv, c) = v.overflowing_add(carry);
108            *v = nv;
109            carry = c as u64;
110            if carry == 0 {
111                break;
112            }
113        }
114        BigInt::from_parts(Sign::Negative, BigUint::from_le_limbs(&neg))
115    }
116}
117
118// ---------------------------------------------------------------------------
119// Core binary-op helper
120// ---------------------------------------------------------------------------
121
122/// Apply a bitwise binary op to two `BigInt` values under two's-complement.
123#[inline]
124fn bigint_binop<F>(lhs: &BigInt, rhs: &BigInt, op: F) -> BigInt
125where
126    F: Fn(u64, u64) -> u64,
127{
128    let llen = lhs.magnitude().as_limbs().len();
129    let rlen = rhs.magnitude().as_limbs().len();
130    // Extra limb absorbs sign-extension noise for all finite pairs.
131    let nlimbs = llen.max(rlen) + 1;
132    let ltc = to_twos_complement(lhs, nlimbs);
133    let rtc = to_twos_complement(rhs, nlimbs);
134    let result: Vec<u64> = ltc
135        .iter()
136        .zip(rtc.iter())
137        .map(|(&a, &b)| op(a, b))
138        .collect();
139    from_twos_complement(&result)
140}
141
142// ---------------------------------------------------------------------------
143// BitAnd for BigInt
144// ---------------------------------------------------------------------------
145
146impl BitAnd<&BigInt> for &BigInt {
147    type Output = BigInt;
148    #[inline]
149    fn bitand(self, rhs: &BigInt) -> BigInt {
150        bigint_binop(self, rhs, |a, b| a & b)
151    }
152}
153
154impl BitAnd<BigInt> for BigInt {
155    type Output = BigInt;
156    #[inline]
157    fn bitand(self, rhs: BigInt) -> BigInt {
158        bigint_binop(&self, &rhs, |a, b| a & b)
159    }
160}
161
162impl BitAnd<&BigInt> for BigInt {
163    type Output = BigInt;
164    #[inline]
165    fn bitand(self, rhs: &BigInt) -> BigInt {
166        bigint_binop(&self, rhs, |a, b| a & b)
167    }
168}
169
170impl BitAnd<BigInt> for &BigInt {
171    type Output = BigInt;
172    #[inline]
173    fn bitand(self, rhs: BigInt) -> BigInt {
174        bigint_binop(self, &rhs, |a, b| a & b)
175    }
176}
177
178// ---------------------------------------------------------------------------
179// BitOr for BigInt
180// ---------------------------------------------------------------------------
181
182impl BitOr<&BigInt> for &BigInt {
183    type Output = BigInt;
184    #[inline]
185    fn bitor(self, rhs: &BigInt) -> BigInt {
186        bigint_binop(self, rhs, |a, b| a | b)
187    }
188}
189
190impl BitOr<BigInt> for BigInt {
191    type Output = BigInt;
192    #[inline]
193    fn bitor(self, rhs: BigInt) -> BigInt {
194        bigint_binop(&self, &rhs, |a, b| a | b)
195    }
196}
197
198impl BitOr<&BigInt> for BigInt {
199    type Output = BigInt;
200    #[inline]
201    fn bitor(self, rhs: &BigInt) -> BigInt {
202        bigint_binop(&self, rhs, |a, b| a | b)
203    }
204}
205
206impl BitOr<BigInt> for &BigInt {
207    type Output = BigInt;
208    #[inline]
209    fn bitor(self, rhs: BigInt) -> BigInt {
210        bigint_binop(self, &rhs, |a, b| a | b)
211    }
212}
213
214// ---------------------------------------------------------------------------
215// BitXor for BigInt
216// ---------------------------------------------------------------------------
217
218impl BitXor<&BigInt> for &BigInt {
219    type Output = BigInt;
220    #[inline]
221    fn bitxor(self, rhs: &BigInt) -> BigInt {
222        bigint_binop(self, rhs, |a, b| a ^ b)
223    }
224}
225
226impl BitXor<BigInt> for BigInt {
227    type Output = BigInt;
228    #[inline]
229    fn bitxor(self, rhs: BigInt) -> BigInt {
230        bigint_binop(&self, &rhs, |a, b| a ^ b)
231    }
232}
233
234impl BitXor<&BigInt> for BigInt {
235    type Output = BigInt;
236    #[inline]
237    fn bitxor(self, rhs: &BigInt) -> BigInt {
238        bigint_binop(&self, rhs, |a, b| a ^ b)
239    }
240}
241
242impl BitXor<BigInt> for &BigInt {
243    type Output = BigInt;
244    #[inline]
245    fn bitxor(self, rhs: BigInt) -> BigInt {
246        bigint_binop(self, &rhs, |a, b| a ^ b)
247    }
248}
249
250// ---------------------------------------------------------------------------
251// Not for BigInt  — !x = -(x) - 1
252// ---------------------------------------------------------------------------
253
254impl Not for BigInt {
255    type Output = BigInt;
256
257    fn not(self) -> BigInt {
258        if self.is_negative() {
259            // self = -m (m > 0); !self = m - 1 (= -(-m) - 1 = m - 1 >= 0)
260            let m = self.magnitude().clone();
261            let one = BigUint::one();
262            match m.checked_sub(&one) {
263                Some(result) => BigInt::from_parts(Sign::Positive, result),
264                // m was 1 (i.e., self = -1), so m - 1 = 0.
265                None => BigInt::zero(),
266            }
267        } else {
268            // self >= 0; !self = -(self + 1) = -(self.mag + 1)
269            let mag_plus_one = self.magnitude() + &BigUint::one();
270            BigInt::from_parts(Sign::Negative, mag_plus_one)
271        }
272    }
273}
274
275impl Not for &BigInt {
276    type Output = BigInt;
277
278    #[inline]
279    fn not(self) -> BigInt {
280        self.clone().not()
281    }
282}
283
284// ---------------------------------------------------------------------------
285// Shl for BigInt — left shift: sign preserved, magnitude shifted left
286// ---------------------------------------------------------------------------
287
288impl Shl<u64> for BigInt {
289    type Output = BigInt;
290
291    fn shl(self, k: u64) -> BigInt {
292        if self.is_zero() || k == 0 {
293            return self;
294        }
295        let sign = self.sign();
296        let shifted_mag = self.magnitude().shl_bits(k);
297        BigInt::from_parts(sign, shifted_mag)
298    }
299}
300
301impl Shl<u64> for &BigInt {
302    type Output = BigInt;
303
304    #[inline]
305    fn shl(self, k: u64) -> BigInt {
306        self.clone().shl(k)
307    }
308}
309
310// ---------------------------------------------------------------------------
311// Shr for BigInt — ARITHMETIC right shift (floor division by 2^k)
312//
313// For self >= 0: equivalent to BigUint shr (logical shift on magnitude).
314// For self = -m (m > 0): floor(-m / 2^k) = -(((m - 1) >> k) + 1)
315// ---------------------------------------------------------------------------
316
317impl Shr<u64> for BigInt {
318    type Output = BigInt;
319
320    fn shr(self, k: u64) -> BigInt {
321        if k == 0 {
322            return self;
323        }
324        if self.is_negative() {
325            let m = self.magnitude().clone();
326            // (m - 1) >> k; if k >= bit_length(m - 1) the result is 0.
327            let m_minus_one = m.checked_sub(&BigUint::one()).unwrap_or_else(BigUint::zero);
328            let shifted = m_minus_one.shr_bits(k);
329            // Result = -(shifted + 1)
330            let mag = &shifted + &BigUint::one();
331            BigInt::from_parts(Sign::Negative, mag)
332        } else {
333            // Non-negative: logical shift on magnitude.
334            BigInt::from_parts(Sign::Positive, self.magnitude().shr_bits(k))
335        }
336    }
337}
338
339impl Shr<u64> for &BigInt {
340    type Output = BigInt;
341
342    #[inline]
343    fn shr(self, k: u64) -> BigInt {
344        self.clone().shr(k)
345    }
346}
347
348// ---------------------------------------------------------------------------
349// Internal unit tests
350// ---------------------------------------------------------------------------
351
352#[cfg(test)]
353mod tests {
354    use super::*;
355
356    fn bi(v: i64) -> BigInt {
357        BigInt::from(v)
358    }
359
360    fn bu(v: u64) -> BigUint {
361        BigUint::from_u64(v)
362    }
363
364    #[test]
365    fn not_basic() {
366        assert_eq!(!bi(0), bi(-1));
367        assert_eq!(!bi(5), bi(-6));
368        assert_eq!(!bi(-1), bi(0));
369        assert_eq!(!bi(-6), bi(5));
370    }
371
372    #[test]
373    fn not_double_negation() {
374        for v in [-1000i64, -1, 0, 1, 1000] {
375            let n = bi(v);
376            assert_eq!(!!n.clone(), n, "!!n == n failed for {v}");
377        }
378    }
379
380    #[test]
381    fn and_neg_one_with_ff() {
382        // -1 in two's complement has all bits 1; -1 & x == x.
383        let neg1 = bi(-1);
384        let ff = bi(0xFF);
385        assert_eq!(&neg1 & &ff, ff);
386    }
387
388    #[test]
389    fn or_neg_one_is_neg_one() {
390        let neg1 = bi(-1);
391        let ff = bi(0xFF);
392        assert_eq!(&neg1 | &ff, neg1);
393    }
394
395    #[test]
396    fn xor_self_is_zero() {
397        let neg1 = bi(-1);
398        assert_eq!(&neg1 ^ &neg1, BigInt::zero());
399        let x = bi(-12345);
400        assert_eq!(&x ^ &x, BigInt::zero());
401    }
402
403    #[test]
404    fn shr_negative_floor_div() {
405        assert_eq!(bi(-8) >> 1u64, bi(-4));
406        assert_eq!(bi(-7) >> 1u64, bi(-4));
407        assert_eq!(bi(-1) >> 1u64, bi(-1));
408        assert_eq!(bi(-1) >> 100u64, bi(-1));
409        assert_eq!(bi(7) >> 1u64, bi(3));
410    }
411
412    #[test]
413    fn shl_signed() {
414        assert_eq!(bi(1) << 4u64, bi(16));
415        assert_eq!(bi(-1) << 4u64, bi(-16));
416        assert_eq!(bi(0) << 100u64, BigInt::zero());
417    }
418
419    #[test]
420    fn to_twos_complement_positive() {
421        let n = bi(5);
422        let tc = to_twos_complement(&n, 2);
423        assert_eq!(tc, vec![5, 0]);
424    }
425
426    #[test]
427    fn to_twos_complement_negative_one() {
428        let n = bi(-1);
429        let tc = to_twos_complement(&n, 2);
430        // -1 in two's complement: all bits 1.
431        assert_eq!(tc, vec![u64::MAX, u64::MAX]);
432    }
433
434    #[test]
435    fn from_twos_complement_roundtrip() {
436        for v in [-1i128, -2, -128, 0, 1, 127, 1000] {
437            let n = BigInt::from(v);
438            let tc = to_twos_complement(&n, 4);
439            let back = from_twos_complement(&tc);
440            assert_eq!(back, n, "roundtrip failed for {v}");
441        }
442    }
443
444    #[test]
445    fn de_morgan_and_to_or() {
446        // !(a & b) == !a | !b
447        let pairs: &[(i64, i64)] = &[
448            (0, 0),
449            (5, 3),
450            (-5, 3),
451            (5, -3),
452            (-5, -3),
453            (-1, 0xFF),
454            (i64::MAX, i64::MIN),
455        ];
456        for &(av, bv) in pairs {
457            let a = bi(av);
458            let b = bi(bv);
459            let lhs = !(&a & &b);
460            let rhs = !a.clone() | !b.clone();
461            assert_eq!(lhs, rhs, "De Morgan failed for ({av}, {bv})");
462        }
463    }
464
465    #[test]
466    fn de_morgan_or_to_and() {
467        // !(a | b) == !a & !b
468        let pairs: &[(i64, i64)] = &[(0, 0), (5, 3), (-5, 3), (5, -3), (-5, -3), (-1, 0xFF)];
469        for &(av, bv) in pairs {
470            let a = bi(av);
471            let b = bi(bv);
472            let lhs = !(&a | &b);
473            let rhs = !a.clone() & !b.clone();
474            assert_eq!(lhs, rhs, "De Morgan (or→and) failed for ({av}, {bv})");
475        }
476    }
477
478    #[test]
479    fn i128_cross_val_bitwise_and_shifts() {
480        let vals: &[i128] = &[
481            0,
482            1,
483            -1,
484            127,
485            -128,
486            1000,
487            -1000,
488            i64::MAX as i128,
489            i64::MIN as i128,
490        ];
491        for &i in vals {
492            let a = BigInt::from(i);
493            // NOT
494            assert_eq!(!a.clone(), BigInt::from(!i), "!{i} mismatch");
495            // Shifts with small non-negative j
496            for j in 0i128..20 {
497                let b = BigInt::from(j);
498                assert_eq!(&a & &b, BigInt::from(i & j), "{i} & {j} mismatch");
499                assert_eq!(&a | &b, BigInt::from(i | j), "{i} | {j} mismatch");
500                assert_eq!(&a ^ &b, BigInt::from(i ^ j), "{i} ^ {j} mismatch");
501                assert_eq!(
502                    a.clone() >> (j as u64),
503                    BigInt::from(i >> j),
504                    "{i} >> {j} mismatch"
505                );
506            }
507            for &j in vals {
508                assert_eq!(
509                    &a & &BigInt::from(j),
510                    BigInt::from(i & j),
511                    "{i} & {j} mismatch"
512                );
513                assert_eq!(
514                    &a | &BigInt::from(j),
515                    BigInt::from(i | j),
516                    "{i} | {j} mismatch"
517                );
518                assert_eq!(
519                    &a ^ &BigInt::from(j),
520                    BigInt::from(i ^ j),
521                    "{i} ^ {j} mismatch"
522                );
523            }
524        }
525    }
526
527    #[test]
528    fn xor_identity_and_complement() {
529        // x ^ 0 == x
530        for v in [-5i64, -1, 0, 1, 5] {
531            let n = bi(v);
532            assert_eq!(&n ^ &BigInt::zero(), n.clone());
533            // x ^ x == 0
534            assert_eq!(&n ^ &n, BigInt::zero());
535            // x ^ -1 == !x  (since -1 is all-ones in two's complement)
536            let all_ones = bi(-1);
537            assert_eq!(&n ^ &all_ones, !n.clone());
538        }
539    }
540
541    #[test]
542    fn shr_positive_matches_biguint_shr() {
543        let mag = BigUint::from_u64(1234567890);
544        let n = BigInt::from_parts(Sign::Positive, mag.clone());
545        for k in [0u64, 1, 7, 15, 31, 63, 64, 65] {
546            let expected = BigInt::from_parts(Sign::Positive, mag.shr_bits(k));
547            assert_eq!(n.clone() >> k, expected, "positive shr mismatch for k={k}");
548        }
549    }
550
551    #[test]
552    fn bu_unused() {
553        // Ensure the bu() helper doesn't trigger unused-function warnings by
554        // using it in at least one test.
555        assert_eq!(bu(42), BigUint::from_u64(42));
556    }
557}
558
559// ---------------------------------------------------------------------------
560// BitAndAssign / BitOrAssign / BitXorAssign for native::BigUint
561//
562// The core BitAnd/BitOr/BitXor impls (4 ref-combinations each) live in
563// `uint.rs` and are wired to call `simd_ops` kernels.  Here we add only the
564// assign variants, keeping bitwise-op code grouped in this file.
565// ---------------------------------------------------------------------------
566
567impl BitAndAssign<BigUint> for BigUint {
568    #[inline]
569    fn bitand_assign(&mut self, rhs: BigUint) {
570        *self = &*self & &rhs;
571    }
572}
573
574impl BitAndAssign<&BigUint> for BigUint {
575    #[inline]
576    fn bitand_assign(&mut self, rhs: &BigUint) {
577        *self = &*self & rhs;
578    }
579}
580
581impl BitOrAssign<BigUint> for BigUint {
582    #[inline]
583    fn bitor_assign(&mut self, rhs: BigUint) {
584        *self = &*self | &rhs;
585    }
586}
587
588impl BitOrAssign<&BigUint> for BigUint {
589    #[inline]
590    fn bitor_assign(&mut self, rhs: &BigUint) {
591        *self = &*self | rhs;
592    }
593}
594
595impl BitXorAssign<BigUint> for BigUint {
596    #[inline]
597    fn bitxor_assign(&mut self, rhs: BigUint) {
598        *self = &*self ^ &rhs;
599    }
600}
601
602impl BitXorAssign<&BigUint> for BigUint {
603    #[inline]
604    fn bitxor_assign(&mut self, rhs: &BigUint) {
605        *self = &*self ^ rhs;
606    }
607}
608
609// ---------------------------------------------------------------------------
610// Unit tests for BigUint bitwise ops
611// ---------------------------------------------------------------------------
612
613#[cfg(test)]
614mod biguint_bitwise_tests {
615    use super::*;
616
617    fn bu(v: u64) -> BigUint {
618        BigUint::from_u64(v)
619    }
620
621    // ------- AND -------
622
623    #[test]
624    fn and_basic() {
625        assert_eq!(&bu(0b1100) & &bu(0b1010), bu(0b1000));
626        assert_eq!(&bu(0xFF) & &bu(0x0F), bu(0x0F));
627        assert_eq!(&bu(0) & &bu(0xFF), bu(0));
628    }
629
630    #[test]
631    fn and_zero_identity() {
632        let x = bu(0xDEAD_BEEF);
633        assert_eq!(&x & &bu(0), bu(0), "a & 0 == 0");
634    }
635
636    #[test]
637    fn and_self_identity() {
638        let x = bu(0xDEAD_BEEF);
639        assert_eq!(&x & &x, x.clone(), "a & a == a");
640    }
641
642    #[test]
643    fn and_unequal_limbs() {
644        // a has 2 limbs, b has 1 limb: result should only be 1 limb (the AND of
645        // the overlap; higher limbs of a vanish because AND with 0 is 0).
646        let mut limbs_a = vec![0xFFFF_FFFF_FFFF_FFFFu64, 0xFFFF_FFFF_FFFF_FFFFu64];
647        let limbs_b = vec![0xAAAA_AAAA_AAAA_AAAAu64];
648        let a = BigUint::from_le_limbs(&limbs_a);
649        let b = BigUint::from_le_limbs(&limbs_b);
650        let result = &a & &b;
651        assert_eq!(result, BigUint::from_le_limbs(&limbs_b));
652        // Confirm normalization: a & 0 in higher limbs yields no trailing zeros
653        limbs_a[1] = 0;
654        let a2 = BigUint::from_le_limbs(&limbs_a);
655        let result2 = &a2 & &b;
656        assert_eq!(result2, BigUint::from_le_limbs(&limbs_b));
657    }
658
659    #[test]
660    fn and_assign() {
661        let mut x = bu(0b1111);
662        x &= bu(0b1010);
663        assert_eq!(x, bu(0b1010));
664    }
665
666    // ------- OR -------
667
668    #[test]
669    fn or_basic() {
670        assert_eq!(&bu(0b1100) | &bu(0b1010), bu(0b1110));
671        assert_eq!(&bu(0xF0) | &bu(0x0F), bu(0xFF));
672    }
673
674    #[test]
675    fn or_zero_identity() {
676        let x = bu(0xDEAD_BEEF);
677        assert_eq!(&x | &bu(0), x.clone(), "a | 0 == a");
678    }
679
680    #[test]
681    fn or_self_identity() {
682        let x = bu(0xDEAD_BEEF);
683        assert_eq!(&x | &x, x.clone(), "a | a == a");
684    }
685
686    #[test]
687    fn or_unequal_limbs() {
688        let limbs_a = vec![0x1111_1111_1111_1111u64, 0x2222_2222_2222_2222u64];
689        let limbs_b = vec![0x4444_4444_4444_4444u64];
690        let a = BigUint::from_le_limbs(&limbs_a);
691        let b = BigUint::from_le_limbs(&limbs_b);
692        let result = &a | &b;
693        // Lower limb: OR, upper limb: preserved from a
694        let expected = BigUint::from_le_limbs(&[
695            0x1111_1111_1111_1111u64 | 0x4444_4444_4444_4444u64,
696            0x2222_2222_2222_2222u64,
697        ]);
698        assert_eq!(result, expected);
699    }
700
701    #[test]
702    fn or_assign() {
703        let mut x = bu(0b1010);
704        x |= bu(0b0101);
705        assert_eq!(x, bu(0b1111));
706    }
707
708    // ------- XOR -------
709
710    #[test]
711    fn xor_basic() {
712        assert_eq!(&bu(0b1100) ^ &bu(0b1010), bu(0b0110));
713        assert_eq!(&bu(0xFF) ^ &bu(0x0F), bu(0xF0));
714    }
715
716    #[test]
717    fn xor_self_is_zero() {
718        let x = bu(0xDEAD_BEEF_CAFE_BABEu64);
719        assert_eq!(&x ^ &x, bu(0), "a ^ a == 0");
720    }
721
722    #[test]
723    fn xor_zero_identity() {
724        let x = bu(0xDEAD_BEEF);
725        assert_eq!(&x ^ &bu(0), x.clone(), "a ^ 0 == a");
726    }
727
728    #[test]
729    fn xor_unequal_limbs() {
730        let limbs_a = vec![0xFFFF_FFFF_FFFF_FFFFu64, 0xFFFF_FFFF_FFFF_FFFFu64];
731        let limbs_b = vec![0xFFFF_FFFF_FFFF_FFFFu64];
732        let a = BigUint::from_le_limbs(&limbs_a);
733        let b = BigUint::from_le_limbs(&limbs_b);
734        let result = &a ^ &b;
735        // Lower limb XOR cancels to 0; upper limb is 0xFFFF... ^ 0 = 0xFFFF...
736        let expected = BigUint::from_le_limbs(&[0u64, 0xFFFF_FFFF_FFFF_FFFFu64]);
737        assert_eq!(result, expected);
738    }
739
740    #[test]
741    fn xor_double_is_identity() {
742        // a ^ b ^ b == a
743        let a = BigUint::from_le_limbs(&[0xDEAD_BEEF, 0xCAFE_BABE, 0x1234_5678]);
744        let b = BigUint::from_le_limbs(&[0x1111_2222, 0x3333_4444]);
745        let c = &(&a ^ &b) ^ &b;
746        assert_eq!(c, a);
747    }
748
749    #[test]
750    fn xor_normalization() {
751        // XOR of equal numbers must normalize to zero (empty limbs).
752        let big = BigUint::from_le_limbs(&[
753            0xAAAA_BBBB_CCCC_DDDDu64,
754            0xEEEE_FFFF_0000_1111u64,
755            0x2222_3333_4444_5555u64,
756        ]);
757        let result: BigUint = &big ^ &big;
758        assert_eq!(result, BigUint::zero());
759        assert!(result.is_zero());
760    }
761
762    #[test]
763    fn xor_assign() {
764        let mut x = bu(0b1111);
765        x ^= bu(0b1010);
766        assert_eq!(x, bu(0b0101));
767    }
768
769    // ------- owned variants compile and produce correct results -------
770
771    #[test]
772    fn owned_ops() {
773        let a = bu(0b1110);
774        let b = bu(0b1011);
775        assert_eq!(a.clone() & b.clone(), bu(0b1010));
776        assert_eq!(a.clone() | b.clone(), bu(0b1111));
777        assert_eq!(a.clone() ^ b.clone(), bu(0b0101));
778    }
779}