binius_core/
word.rs

1// Copyright 2025 Irreducible Inc.
2//! [`Word`] related definitions.
3
4use std::{
5	fmt,
6	ops::{BitAnd, BitOr, BitXor, Not, Shl, Shr},
7};
8
9use binius_utils::serialization::{DeserializeBytes, SerializationError, SerializeBytes};
10use bytes::{Buf, BufMut};
11
12/// [`Word`] is 64-bit value and is a fundamental unit of data in Binius64. All computation and
13/// constraints operate on it.
14#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
15pub struct Word(pub u64);
16
17impl Word {
18	/// All zero bit pattern, zero, nil, null.
19	pub const ZERO: Word = Word(0);
20	/// 1.
21	pub const ONE: Word = Word(1);
22	/// All bits set to one.
23	pub const ALL_ONE: Word = Word(u64::MAX);
24	/// 32 lower bits are set to one, all other bits are zero.
25	pub const MASK_32: Word = Word(0x00000000FFFFFFFF);
26	/// Most Significant Bit is set to one, all other bits are zero.
27	///
28	/// This is a canonical representation of true.
29	pub const MSB_ONE: Word = Word(0x8000000000000000);
30}
31
32impl fmt::Debug for Word {
33	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34		write!(f, "Word({:#018x})", self.0)
35	}
36}
37
38impl BitAnd for Word {
39	type Output = Self;
40
41	fn bitand(self, rhs: Self) -> Self::Output {
42		Word(self.0 & rhs.0)
43	}
44}
45
46impl BitOr for Word {
47	type Output = Self;
48
49	fn bitor(self, rhs: Self) -> Self::Output {
50		Word(self.0 | rhs.0)
51	}
52}
53
54impl BitXor for Word {
55	type Output = Self;
56
57	fn bitxor(self, rhs: Self) -> Self::Output {
58		Word(self.0 ^ rhs.0)
59	}
60}
61
62impl Shl<u32> for Word {
63	type Output = Self;
64
65	fn shl(self, rhs: u32) -> Self::Output {
66		Word(self.0 << rhs)
67	}
68}
69
70impl Shr<u32> for Word {
71	type Output = Self;
72
73	fn shr(self, rhs: u32) -> Self::Output {
74		Word(self.0 >> rhs)
75	}
76}
77
78impl Not for Word {
79	type Output = Self;
80
81	fn not(self) -> Self::Output {
82		Word(!self.0)
83	}
84}
85
86impl Word {
87	/// Creates a new `Word` from a 64-bit unsigned integer.
88	pub fn from_u64(value: u64) -> Word {
89		Word(value)
90	}
91
92	/// Performs 32-bit addition.
93	///
94	/// Returns (sum, carry_out) where ith carry_out bit is set to one if there is a carry out at
95	/// that bit position.
96	pub fn iadd_cout_32(self, rhs: Word) -> (Word, Word) {
97		let Word(lhs) = self;
98		let Word(rhs) = rhs;
99		let full_sum = lhs.wrapping_add(rhs);
100		let sum = full_sum & 0x00000000_FFFFFFFF;
101		let cout = (lhs & rhs) | ((lhs ^ rhs) & !full_sum);
102		(Word(sum), Word(cout))
103	}
104
105	/// Performs 64-bit addition with carry input bit.
106	///
107	/// cin is a carry-in from the previous addition. Since it can only affect the LSB only, the cin
108	/// could be 1 if there is carry over, or 0 otherwise.
109	///
110	/// Returns (sum, carry_out) where ith carry_out bit is set to one if there is a carry out at
111	/// that bit position.
112	pub fn iadd_cin_cout(self, rhs: Word, cin: Word) -> (Word, Word) {
113		debug_assert!(cin == Word::ZERO || cin == Word::ONE, "cin must be 0 or 1");
114		let Word(lhs) = self;
115		let Word(rhs) = rhs;
116		let Word(cin) = cin;
117		let sum = lhs.wrapping_add(rhs).wrapping_add(cin);
118		let cout = (lhs & rhs) | ((lhs ^ rhs) & !sum);
119		(Word(sum), Word(cout))
120	}
121
122	/// Performs 64-bit subtraction with borrow input bit.
123	///
124	/// bin is a borrow-in from the previous subtraction. Since it can only affect the LSB only, the
125	/// bin could be 1 if there is borrow over, or 0 otherwise.
126	///
127	/// Returns (diff, borrow_out) where ith borrow_out bit is set to one if there is a borrow out
128	/// at that bit position.
129	pub fn isub_bin_bout(self, rhs: Word, bin: Word) -> (Word, Word) {
130		debug_assert!(bin == Word::ZERO || bin == Word::ONE, "bin must be 0 or 1");
131		let Word(lhs) = self;
132		let Word(rhs) = rhs;
133		let Word(bin) = bin;
134		let diff = lhs.wrapping_sub(rhs).wrapping_sub(bin);
135		let bout = (!lhs & rhs) | (!(lhs ^ rhs) & diff);
136		(Word(diff), Word(bout))
137	}
138
139	/// Performs shift right by a given number of bits followed by masking with a 32-bit mask.
140	pub fn shr_32(self, n: u32) -> Word {
141		let Word(value) = self;
142		// Shift right logically by n bits and mask with 32-bit mask
143		let result = (value >> n) & 0x00000000_FFFFFFFF;
144		Word(result)
145	}
146
147	/// Shift Arithmetic Right by a given number of bits.
148	///
149	/// This is similar to a logical shift right, but it shifts the sign bit to the right.
150	pub fn sar(&self, n: u32) -> Word {
151		let Word(value) = self;
152		let value = *value as i64;
153		let result = value >> n;
154		Word(result as u64)
155	}
156
157	/// Rotate Right by a given number of bits followed by masking with a 32-bit mask.
158	pub fn rotr_32(self, n: u32) -> Word {
159		let Word(value) = self;
160		let value_32 = (value as u32).rotate_right(n);
161		Word(value_32 as u64)
162	}
163
164	/// Rotate Right by a given number of bits.
165	pub fn rotr(self, n: u32) -> Word {
166		let Word(value) = self;
167		Word(value.rotate_right(n))
168	}
169
170	/// Shift Left Logical on 32-bit halves.
171	///
172	/// Performs independent logical left shifts on the upper and lower 32-bit halves.
173	/// Only uses the lower 5 bits of the shift amount (0-31).
174	pub fn sll32(self, n: u32) -> Word {
175		let Word(value) = self;
176		let n = n & 0x1F; // Only use lower 5 bits
177
178		// Extract 32-bit halves
179		let lo = value as u32;
180		let hi = (value >> 32) as u32;
181
182		// Shift each half independently
183		let lo_shifted = (lo << n) as u64;
184		let hi_shifted = ((hi << n) as u64) << 32;
185
186		Word(lo_shifted | hi_shifted)
187	}
188
189	/// Shift Right Logical on 32-bit halves.
190	///
191	/// Performs independent logical right shifts on the upper and lower 32-bit halves.
192	/// Only uses the lower 5 bits of the shift amount (0-31).
193	pub fn srl32(self, n: u32) -> Word {
194		let Word(value) = self;
195		let n = n & 0x1F; // Only use lower 5 bits
196
197		// Extract 32-bit halves
198		let lo = value as u32;
199		let hi = (value >> 32) as u32;
200
201		// Shift each half independently
202		let lo_shifted = (lo >> n) as u64;
203		let hi_shifted = ((hi >> n) as u64) << 32;
204
205		Word(lo_shifted | hi_shifted)
206	}
207
208	/// Shift Right Arithmetic on 32-bit halves.
209	///
210	/// Performs independent arithmetic right shifts on the upper and lower 32-bit halves.
211	/// Sign extends each 32-bit half independently. Only uses the lower 5 bits of the shift amount
212	/// (0-31).
213	pub fn sra32(self, n: u32) -> Word {
214		let Word(value) = self;
215		let n = n & 0x1F; // Only use lower 5 bits
216
217		// Extract 32-bit halves as signed integers
218		let lo = value as u32 as i32;
219		let hi = (value >> 32) as u32 as i32;
220
221		// Arithmetic shift each half independently
222		let lo_shifted = ((lo >> n) as u32) as u64;
223		let hi_shifted = (((hi >> n) as u32) as u64) << 32;
224
225		Word(lo_shifted | hi_shifted)
226	}
227
228	/// Rotate Right on 32-bit halves.
229	///
230	/// Performs independent rotate right operations on the upper and lower 32-bit halves.
231	/// Bits shifted off the right end wrap around to the left within each 32-bit half.
232	/// Only uses the lower 5 bits of the shift amount (0-31).
233	pub fn rotr32(self, n: u32) -> Word {
234		let Word(value) = self;
235		let n = n & 0x1F; // Only use lower 5 bits
236
237		// Extract 32-bit halves
238		let lo = value as u32;
239		let hi = (value >> 32) as u32;
240
241		// Rotate each half independently
242		let lo_rotated = lo.rotate_right(n) as u64;
243		let hi_rotated = (hi.rotate_right(n) as u64) << 32;
244
245		Word(lo_rotated | hi_rotated)
246	}
247
248	/// Unsigned integer multiplication.
249	///
250	/// Multiplies two 64-bit unsigned integers and returns the 128-bit result split into high and
251	/// low 64-bit words, respectively.
252	pub fn imul(self, rhs: Word) -> (Word, Word) {
253		let Word(lhs) = self;
254		let Word(rhs) = rhs;
255		let result = (lhs as u128) * (rhs as u128);
256
257		let hi = (result >> 64) as u64;
258		let lo = result as u64;
259		(Word(hi), Word(lo))
260	}
261
262	/// Signed integer multiplication.
263	///
264	/// Multiplies two 64-bit signed integers and returns the 128-bit result split into high and
265	/// low 64-bit words, respectively.
266	pub fn smul(self, rhs: Word) -> (Word, Word) {
267		let Word(lhs) = self;
268		let Word(rhs) = rhs;
269		// Interpret as signed 64-bit integers
270		let a = lhs as i64;
271		let b = rhs as i64;
272		// Perform signed multiplication as 128-bit
273		let result = (a as i128) * (b as i128);
274		// Extract high and low 64-bit words
275		let hi = (result >> 64) as u64;
276		let lo = result as u64;
277		(Word(hi), Word(lo))
278	}
279
280	/// Integer addition.
281	///
282	/// Wraps around on overflow.
283	pub fn wrapping_add(self, rhs: Word) -> Word {
284		Word(self.0.wrapping_add(rhs.0))
285	}
286
287	/// Integer subtraction.
288	///
289	/// Wraps around on overflow.
290	pub fn wrapping_sub(self, rhs: Word) -> Word {
291		Word(self.0.wrapping_sub(rhs.0))
292	}
293
294	/// Returns the integer value as a 64-bit unsigned integer.
295	pub fn as_u64(self) -> u64 {
296		self.0
297	}
298
299	/// Tests if this Word represents true as an MSB-bool.
300	///
301	/// In MSB-bool representation, a value is true if its Most Significant Bit (bit 63) is set to
302	/// 1. All other bits are ignored for the boolean value.
303	///
304	/// Returns true if the MSB is 1, false otherwise.
305	pub fn is_msb_true(self) -> bool {
306		(self.0 & 0x8000000000000000) != 0
307	}
308
309	/// Tests if this Word represents false as an MSB-bool.
310	///
311	/// In MSB-bool representation, a value is false if its Most Significant Bit (bit 63) is 0.
312	/// All other bits are ignored for the boolean value.
313	///
314	/// Returns true if the MSB is 0, false otherwise.
315	pub fn is_msb_false(self) -> bool {
316		(self.0 & 0x8000000000000000) == 0
317	}
318}
319
320impl SerializeBytes for Word {
321	fn serialize(&self, write_buf: impl BufMut) -> Result<(), SerializationError> {
322		self.0.serialize(write_buf)
323	}
324}
325
326impl DeserializeBytes for Word {
327	fn deserialize(read_buf: impl Buf) -> Result<Self, SerializationError>
328	where
329		Self: Sized,
330	{
331		Ok(Word(u64::deserialize(read_buf)?))
332	}
333}
334
335#[cfg(test)]
336mod tests {
337	use proptest::prelude::*;
338
339	use super::*;
340
341	#[test]
342	fn test_constants() {
343		assert_eq!(Word::ZERO, Word(0));
344		assert_eq!(Word::ONE, Word(1));
345		assert_eq!(Word::ALL_ONE, Word(0xFFFFFFFFFFFFFFFF));
346		assert_eq!(Word::MASK_32, Word(0x00000000FFFFFFFF));
347		assert_eq!(Word::MSB_ONE, Word(0x8000000000000000));
348	}
349
350	#[test]
351	fn test_msb_bool() {
352		// Test MSB_ONE is true
353		assert!(Word::MSB_ONE.is_msb_true());
354		assert!(!Word::MSB_ONE.is_msb_false());
355
356		// Test ZERO is false
357		assert!(!Word::ZERO.is_msb_true());
358		assert!(Word::ZERO.is_msb_false());
359
360		// Test various values with MSB set
361		assert!(Word(0x8000000000000000).is_msb_true());
362		assert!(Word(0x8000000000000001).is_msb_true());
363		assert!(Word(0x80000000FFFFFFFF).is_msb_true());
364		assert!(Word(0xFFFFFFFFFFFFFFFF).is_msb_true());
365
366		// Test various values with MSB clear
367		assert!(Word(0x7FFFFFFFFFFFFFFF).is_msb_false());
368		assert!(Word(0x0000000000000001).is_msb_false());
369		assert!(Word(0x00000000FFFFFFFF).is_msb_false());
370		assert!(Word(0x7000000000000000).is_msb_false());
371
372		// Verify complementary behavior
373		let test_word = Word(0x8123456789ABCDEF);
374		assert!(test_word.is_msb_true());
375		assert!(!test_word.is_msb_false());
376
377		let test_word2 = Word(0x7123456789ABCDEF);
378		assert!(!test_word2.is_msb_true());
379		assert!(test_word2.is_msb_false());
380	}
381
382	proptest! {
383		#[test]
384		fn prop_msb_bool(val in any::<u64>()) {
385			let word = Word(val);
386
387			// is_msb_true and is_msb_false should be complementary
388			assert_eq!(word.is_msb_true(), !word.is_msb_false());
389			assert_eq!(word.is_msb_false(), !word.is_msb_true());
390
391			// Check against direct bit manipulation
392			let msb_set = (val & 0x8000000000000000) != 0;
393			assert_eq!(word.is_msb_true(), msb_set);
394			assert_eq!(word.is_msb_false(), !msb_set);
395
396			// MSB operations should ignore lower bits
397			let word_with_msb = Word(val | 0x8000000000000000);
398			let word_without_msb = Word(val & 0x7FFFFFFFFFFFFFFF);
399			assert!(word_with_msb.is_msb_true());
400			assert!(word_without_msb.is_msb_false());
401		}
402
403		#[test]
404		fn prop_bitwise_and(a in any::<u64>(), b in any::<u64>()) {
405			let wa = Word(a);
406			let wb = Word(b);
407
408			// Basic AND properties
409			assert_eq!((wa & wb).0, a & b);
410			assert_eq!(wa & Word::ALL_ONE, wa);
411			assert_eq!(wa & Word::ZERO, Word::ZERO);
412			assert_eq!(wa & wa, wa); // Idempotent
413
414			// Commutative
415			assert_eq!(wa & wb, wb & wa);
416		}
417
418		#[test]
419		fn prop_bitwise_or(a in any::<u64>(), b in any::<u64>()) {
420			let wa = Word(a);
421			let wb = Word(b);
422
423			// Basic OR properties
424			assert_eq!((wa | wb).0, a | b);
425			assert_eq!(wa | Word::ZERO, wa);
426			assert_eq!(wa | Word::ALL_ONE, Word::ALL_ONE);
427			assert_eq!(wa | wa, wa); // Idempotent
428
429			// Commutative
430			assert_eq!(wa | wb, wb | wa);
431		}
432
433		#[test]
434		fn prop_bitwise_xor(a in any::<u64>(), b in any::<u64>()) {
435			let wa = Word(a);
436			let wb = Word(b);
437
438			// Basic XOR properties
439			assert_eq!((wa ^ wb).0, a ^ b);
440			assert_eq!(wa ^ Word::ZERO, wa);
441			assert_eq!(wa ^ wa, Word::ZERO);
442			assert_eq!(wa ^ Word::ALL_ONE, !wa);
443
444			// Commutative
445			assert_eq!(wa ^ wb, wb ^ wa);
446
447			// Double XOR cancels
448			assert_eq!(wa ^ wb ^ wb, wa);
449		}
450
451		#[test]
452		fn prop_bitwise_not(a in any::<u64>()) {
453			let wa = Word(a);
454
455			// Basic NOT properties
456			assert_eq!((!wa).0, !a);
457			assert_eq!(!(!wa), wa); // Double negation
458			assert_eq!(!Word::ZERO, Word::ALL_ONE);
459			assert_eq!(!Word::ALL_ONE, Word::ZERO);
460
461			// De Morgan's laws
462			let wb = Word(a.wrapping_add(1));
463			assert_eq!(!(wa & wb), !wa | !wb);
464			assert_eq!(!(wa | wb), !wa & !wb);
465		}
466
467		#[test]
468		fn prop_shift_left(val in any::<u64>(), shift in 0u32..64) {
469			let w = Word(val);
470			assert_eq!((w << shift).0, val << shift);
471
472			// Shifting by 0 is identity
473			assert_eq!(w << 0, w);
474
475			// Shifting by 64 or more gives 0
476			if shift >= 64 {
477				assert_eq!((w << shift).0, 0);
478			}
479		}
480
481		#[test]
482		fn prop_shift_right(val in any::<u64>(), shift in 0u32..64) {
483			let w = Word(val);
484			assert_eq!((w >> shift).0, val >> shift);
485
486			// Shifting by 0 is identity
487			assert_eq!(w >> 0, w);
488
489			// Shifting by 64 or more gives 0
490			if shift >= 64 {
491				assert_eq!((w >> shift).0, 0);
492			}
493		}
494
495		#[test]
496		fn prop_shift_inverse(val in any::<u64>(), shift in 1u32..64) {
497			let w = Word(val);
498			// Left then right shift loses high bits
499			let mask = (1u64 << (64 - shift)) - 1;
500			assert_eq!(((w << shift) >> shift).0, val & mask);
501
502			// Right then left shift loses low bits
503			let high_mask = !((1u64 << shift) - 1);
504			assert_eq!(((w >> shift) << shift).0, val & high_mask);
505		}
506
507		#[test]
508		fn prop_sar(val in any::<u64>(), shift in 0u32..64) {
509			let w = Word(val);
510			let expected = ((val as i64) >> shift) as u64;
511			assert_eq!(w.sar(shift).0, expected);
512
513			// SAR by 0 is identity
514			assert_eq!(w.sar(0), w);
515
516			// SAR by 63 gives all 0s or all 1s depending on sign
517			let sign_extended = if (val as i64) < 0 {
518				Word(0xFFFFFFFFFFFFFFFF)
519			} else {
520				Word(0)
521			};
522			assert_eq!(w.sar(63), sign_extended);
523		}
524
525		#[test]
526		fn prop_sar_sign_extension(val in any::<u64>(), shift in 1u32..64) {
527			let w = Word(val);
528			let result = w.sar(shift);
529
530			// Check sign bit is extended
531			let is_negative = (val as i64) < 0;
532			if is_negative {
533				// High bits should all be 1
534				let mask = !((1u64 << (64 - shift)) - 1);
535				assert_eq!(result.0 & mask, mask);
536			} else {
537				// High bits should all be 0
538				let mask = !((1u64 << (64 - shift)) - 1);
539				assert_eq!(result.0 & mask, 0);
540			}
541		}
542
543		#[test]
544		fn prop_iadd_cout_32(a in any::<u32>(), b in any::<u32>()) {
545			let wa = Word(a as u64);
546			let wb = Word(b as u64);
547			let (sum, cout) = wa.iadd_cout_32(wb);
548
549			// Sum should be masked to 32 bits
550			assert_eq!(sum.0, (a as u64 + b as u64) & 0xFFFFFFFF);
551
552			// Carry computation: cout = (a & b) | ((a ^ b) & !sum)
553			let expected_cout = (a as u64 & b as u64) | ((a as u64 ^ b as u64) & !sum.0);
554			assert_eq!(cout.0, expected_cout);
555
556			// Identity: adding 0 produces no carries
557			let (sum0, cout0) = wa.iadd_cout_32(Word::ZERO);
558			assert_eq!(sum0.0, a as u64);
559			assert_eq!(cout0, Word::ZERO);
560		}
561
562		#[test]
563		fn prop_iadd_cin_cout(a in any::<u64>(), b in any::<u64>(), cin in 0u64..=1) {
564			let wa = Word(a);
565			let wb = Word(b);
566			let wcin = Word(cin);
567			let (sum, cout) = wa.iadd_cin_cout(wb, wcin);
568
569			// Basic addition with carry
570			let expected_sum = a.wrapping_add(b).wrapping_add(cin);
571			assert_eq!(sum.0, expected_sum);
572
573			// Carry computation: cout at each bit position
574			let expected_cout = (a & b) | ((a ^ b) & !expected_sum);
575			assert_eq!(cout.0, expected_cout);
576
577			// Without carry in, same as regular addition
578			let (sum0, cout0) = wa.iadd_cin_cout(wb, Word::ZERO);
579			let full_sum = a.wrapping_add(b);
580			assert_eq!(sum0.0, full_sum);
581			assert_eq!(cout0.0, (a & b) | ((a ^ b) & !full_sum));
582		}
583
584		#[test]
585		fn prop_isub_bin_bout(a in any::<u64>(), b in any::<u64>(), bin in 0u64..=1) {
586			let wa = Word(a);
587			let wb = Word(b);
588			let wbin = Word(bin);
589			let (diff, bout) = wa.isub_bin_bout(wb, wbin);
590
591			// Basic subtraction with borrow
592			let expected_diff = a.wrapping_sub(b).wrapping_sub(bin);
593			assert_eq!(diff.0, expected_diff);
594
595			// Borrow computation: bout = (!a & b) | (!(a ^ b) & diff)
596			let expected_bout = (!a & b) | (!(a ^ b) & expected_diff);
597			assert_eq!(bout.0, expected_bout);
598
599			// Without borrow in
600			let (diff0, bout0) = wa.isub_bin_bout(wb, Word::ZERO);
601			let expected = a.wrapping_sub(b);
602			assert_eq!(diff0.0, expected);
603			assert_eq!(bout0.0, (!a & b) | (!(a ^ b) & expected));
604		}
605
606		#[test]
607		fn prop_shr_32(val in any::<u64>(), shift in 0u32..64) {
608			let w = Word(val);
609			let result = w.shr_32(shift);
610
611			// Result should be the full value shifted right, then masked to 32 bits
612			let expected = (val >> shift) & 0xFFFFFFFF;
613			assert_eq!(result.0, expected);
614
615			// Shifting by 0 gives lower 32 bits
616			assert_eq!(w.shr_32(0).0, val & 0xFFFFFFFF);
617
618			// Shifting by 32 or more gives upper bits or zeros
619			if shift >= 32 {
620				assert_eq!(result.0, (val >> shift) & 0xFFFFFFFF);
621			}
622		}
623
624		#[test]
625		fn prop_rotr_32(val in any::<u32>(), rotate in 0u32..64) {
626			let w = Word(val as u64);
627			let result = w.rotr_32(rotate);
628
629			// Only lower 32 bits are rotated
630			let rotate_mod = rotate % 32;
631			let val32 = val as u64;
632			let expected = if rotate_mod == 0 {
633				val32
634			} else {
635				((val32 >> rotate_mod) | (val32 << (32 - rotate_mod))) & 0xFFFFFFFF
636			};
637			assert_eq!(result.0, expected);
638
639			// Rotation by 0 or 32 is identity
640			assert_eq!(w.rotr_32(0).0, val32);
641			assert_eq!(w.rotr_32(32).0, val32);
642		}
643
644		#[test]
645		fn prop_rotr(val in any::<u64>(), rotate in 0u32..128) {
646			let w = Word(val);
647			let result = w.rotr(rotate);
648
649			// Rotation is modulo 64
650			let rotate_mod = rotate % 64;
651			let expected = if rotate_mod == 0 {
652				val
653			} else {
654				(val >> rotate_mod) | (val << (64 - rotate_mod))
655			};
656			assert_eq!(result.0, expected);
657
658			// Rotation by 0 or 64 is identity
659			assert_eq!(w.rotr(0), w);
660			assert_eq!(w.rotr(64), w);
661
662			// Double rotation
663			let r1 = rotate % 64;
664			let r2 = (64 - r1) % 64;
665			if r1 != 0 {
666				assert_eq!(w.rotr(r1).rotr(r2), w);
667			}
668		}
669
670		#[test]
671		fn prop_imul(a in any::<u64>(), b in any::<u64>()) {
672			let wa = Word(a);
673			let wb = Word(b);
674			let (hi, lo) = wa.imul(wb);
675
676			// Check against native 128-bit multiplication
677			let result = (a as u128) * (b as u128);
678			assert_eq!(hi.0, (result >> 64) as u64);
679			assert_eq!(lo.0, result as u64);
680
681			// Multiplication by 0 gives 0
682			let (hi0, lo0) = wa.imul(Word::ZERO);
683			assert_eq!(hi0, Word::ZERO);
684			assert_eq!(lo0, Word::ZERO);
685
686			// Multiplication by 1 is identity
687			let (hi1, lo1) = wa.imul(Word::ONE);
688			assert_eq!(hi1, Word::ZERO);
689			assert_eq!(lo1, wa);
690
691			// Commutative
692			let (hi_ab, lo_ab) = wa.imul(wb);
693			let (hi_reversed, lo_reversed) = wb.imul(wa);
694			assert_eq!(hi_ab, hi_reversed);
695			assert_eq!(lo_ab, lo_reversed);
696		}
697
698		#[test]
699		fn prop_sll32(val in any::<u64>(), shift in 0u32..32) {
700			let w = Word(val);
701			let result = w.sll32(shift);
702
703			// Extract 32-bit halves
704			let lo = val as u32;
705			let hi = (val >> 32) as u32;
706
707			// Expected result: each half shifted independently
708			let expected_lo = ((lo << shift) as u64) & 0xFFFFFFFF;
709			let expected_hi = ((hi << shift) as u64) << 32;
710			let expected = expected_lo | expected_hi;
711
712			assert_eq!(result.0, expected);
713
714			// Shifting by 0 is identity
715			assert_eq!(w.sll32(0), w);
716
717			// Shifting by 31 should move MSB of each half to sign bit
718			let w_test = Word(0x40000001_40000001);
719			let result_31 = w_test.sll32(31);
720			assert_eq!(result_31.0, 0x80000000_80000000);
721
722			// Test that shift amount is masked to 5 bits
723			assert_eq!(w.sll32(shift), w.sll32(shift | 0x20));
724		}
725
726		#[test]
727		fn prop_srl32(val in any::<u64>(), shift in 0u32..32) {
728			let w = Word(val);
729			let result = w.srl32(shift);
730
731			// Extract 32-bit halves
732			let lo = val as u32;
733			let hi = (val >> 32) as u32;
734
735			// Expected result: each half shifted independently
736			let expected_lo = (lo >> shift) as u64;
737			let expected_hi = ((hi >> shift) as u64) << 32;
738			let expected = expected_lo | expected_hi;
739
740			assert_eq!(result.0, expected);
741
742			// Shifting by 0 is identity
743			assert_eq!(w.srl32(0), w);
744
745			// Shifting by 31 should move LSB to bit 0, clearing upper bits
746			let w_test = Word(0x80000000_80000000);
747			let result_31 = w_test.srl32(31);
748			assert_eq!(result_31.0, 0x00000001_00000001);
749
750			// Test that shift amount is masked to 5 bits
751			assert_eq!(w.srl32(shift), w.srl32(shift | 0x20));
752		}
753
754		#[test]
755		fn prop_sra32(val in any::<u64>(), shift in 0u32..32) {
756			let w = Word(val);
757			let result = w.sra32(shift);
758
759			// Extract 32-bit halves as signed
760			let lo = val as u32 as i32;
761			let hi = (val >> 32) as u32 as i32;
762
763			// Expected result: each half arithmetic shifted independently
764			let expected_lo = ((lo >> shift) as u32) as u64;
765			let expected_hi = (((hi >> shift) as u32) as u64) << 32;
766			let expected = expected_lo | expected_hi;
767
768			assert_eq!(result.0, expected);
769
770			// Shifting by 0 is identity
771			assert_eq!(w.sra32(0), w);
772
773			// Sign extension test: negative values extend sign bit
774			let w_neg = Word(0x80000000_80000000);
775			let result_1 = w_neg.sra32(1);
776			assert_eq!(result_1.0, 0xC0000000_C0000000);
777
778			// Sign extension test: positive values extend 0
779			let w_pos = Word(0x40000000_40000000);
780			let result_1_pos = w_pos.sra32(1);
781			assert_eq!(result_1_pos.0, 0x20000000_20000000);
782
783			// Shifting by 31 gives all 0s or all 1s in each half
784			let result_31 = w.sra32(31);
785			let expected_lo_31 = if lo < 0 { 0xFFFFFFFF } else { 0 };
786			let expected_hi_31 = if hi < 0 { 0xFFFFFFFF00000000 } else { 0 };
787			assert_eq!(result_31.0, expected_lo_31 | expected_hi_31);
788
789			// Test that shift amount is masked to 5 bits
790			assert_eq!(w.sra32(shift), w.sra32(shift | 0x20));
791		}
792
793		#[test]
794		fn prop_rotr32(val in any::<u64>(), rotate in 0u32..32) {
795			let w = Word(val);
796			let result = w.rotr32(rotate);
797
798			// Extract 32-bit halves
799			let lo = val as u32;
800			let hi = (val >> 32) as u32;
801
802			// Expected result: each half rotated independently
803			let expected_lo = lo.rotate_right(rotate) as u64;
804			let expected_hi = ((hi.rotate_right(rotate)) as u64) << 32;
805			let expected = expected_lo | expected_hi;
806
807			assert_eq!(result.0, expected);
808
809			// Rotating by 0 is identity
810			assert_eq!(w.rotr32(0), w);
811
812			// Rotating by 32 is identity (due to masking to 5 bits)
813			assert_eq!(w.rotr32(32), w.rotr32(0));
814
815			// Test that rotate amount is masked to 5 bits
816			assert_eq!(w.rotr32(rotate), w.rotr32(rotate | 0x20));
817
818			// Rotation is circular - rotating by n then 32-n gives identity
819			if rotate > 0 && rotate < 32 {
820				let w_test = Word(0x12345678_9ABCDEF0);
821				let rotated = w_test.rotr32(rotate);
822				let back = rotated.rotr32(32 - rotate);
823				assert_eq!(back, w_test);
824			}
825		}
826
827		#[test]
828		fn prop_smul(a in any::<u64>(), b in any::<u64>()) {
829			let wa = Word(a);
830			let wb = Word(b);
831			let (hi, lo) = wa.smul(wb);
832
833			// Check against native 128-bit signed multiplication
834			let result = (a as i64 as i128) * (b as i64 as i128);
835			assert_eq!(hi.0, (result >> 64) as u64);
836			assert_eq!(lo.0, result as u64);
837
838			// Multiplication by 0 gives 0
839			let (hi0, lo0) = wa.smul(Word::ZERO);
840			assert_eq!(hi0, Word::ZERO);
841			assert_eq!(lo0, Word::ZERO);
842
843			// Multiplication by 1 is identity
844			let (hi1, lo1) = wa.smul(Word::ONE);
845			let expected_hi = if (a as i64) < 0 { Word(0xFFFFFFFFFFFFFFFF) } else { Word::ZERO };
846			assert_eq!(hi1, expected_hi);
847			assert_eq!(lo1, wa);
848
849			// Multiplication by -1 negates
850			let (hi_neg, lo_neg) = wa.smul(Word(0xFFFFFFFFFFFFFFFF));
851			let neg_result = -(a as i64 as i128);
852			assert_eq!(hi_neg.0, (neg_result >> 64) as u64);
853			assert_eq!(lo_neg.0, neg_result as u64);
854
855			// Commutative
856			let (hi_ab, lo_ab) = wa.smul(wb);
857			let (hi_reversed, lo_reversed) = wb.smul(wa);
858			assert_eq!(hi_ab, hi_reversed);
859			assert_eq!(lo_ab, lo_reversed);
860		}
861
862		#[test]
863		fn prop_wrapping_sub(a in any::<u64>(), b in any::<u64>()) {
864			let wa = Word(a);
865			let wb = Word(b);
866			let result = wa.wrapping_sub(wb);
867
868			assert_eq!(result.0, a.wrapping_sub(b));
869
870			// Subtracting 0 is identity
871			assert_eq!(wa.wrapping_sub(Word::ZERO), wa);
872
873			// Subtracting itself gives 0
874			assert_eq!(wa.wrapping_sub(wa), Word::ZERO);
875
876			// Adding then subtracting cancels
877			let sum = Word(a.wrapping_add(b));
878			assert_eq!(sum.wrapping_sub(wb), wa);
879		}
880
881		#[test]
882		fn prop_conversions(val in any::<u64>()) {
883			let word = Word::from_u64(val);
884			assert_eq!(word.as_u64(), val);
885			assert_eq!(word, Word(val));
886
887			// Round trip
888			assert_eq!(Word::from_u64(word.as_u64()), word);
889		}
890
891		#[test]
892		fn prop_debug_format(val in any::<u64>()) {
893			let word = Word(val);
894			let debug_str = format!("{:?}", word);
895			assert!(debug_str.starts_with("Word(0x"));
896			assert!(debug_str.ends_with(")"));
897			// Check the hex value is correct (lowercase)
898			let expected = format!("Word({:#018x})", val);
899			assert_eq!(debug_str, expected);
900		}
901	}
902
903	#[test]
904	fn test_32bit_shift_edge_cases() {
905		// Test sll32 edge cases
906		let w1 = Word(0x12345678_9ABCDEF0);
907		assert_eq!(w1.sll32(4).0, 0x23456780_ABCDEF00);
908		assert_eq!(w1.sll32(16).0, 0x56780000_DEF00000);
909
910		// Test that upper bits don't affect lower half and vice versa
911		let w2 = Word(0xFFFFFFFF_00000000);
912		assert_eq!(w2.sll32(1).0, 0xFFFFFFFE_00000000);
913		let w3 = Word(0x00000000_FFFFFFFF);
914		assert_eq!(w3.sll32(1).0, 0x00000000_FFFFFFFE);
915
916		// Test srl32 edge cases
917		assert_eq!(w1.srl32(4).0, 0x01234567_09ABCDEF);
918		assert_eq!(w1.srl32(16).0, 0x00001234_00009ABC);
919
920		// Test sra32 with mixed sign bits
921		let w4 = Word(0x80000000_7FFFFFFF); // Negative upper, positive lower
922		assert_eq!(w4.sra32(1).0, 0xC0000000_3FFFFFFF);
923		assert_eq!(w4.sra32(31).0, 0xFFFFFFFF_00000000);
924
925		let w5 = Word(0x7FFFFFFF_80000000); // Positive upper, negative lower
926		assert_eq!(w5.sra32(1).0, 0x3FFFFFFF_C0000000);
927		assert_eq!(w5.sra32(31).0, 0x00000000_FFFFFFFF);
928
929		// Test boundary values
930		let all_ones = Word(0xFFFFFFFF_FFFFFFFF);
931		assert_eq!(all_ones.sll32(1).0, 0xFFFFFFFE_FFFFFFFE);
932		assert_eq!(all_ones.srl32(1).0, 0x7FFFFFFF_7FFFFFFF);
933		assert_eq!(all_ones.sra32(1).0, 0xFFFFFFFF_FFFFFFFF);
934
935		let alternating = Word(0xAAAAAAAA_55555555);
936		assert_eq!(alternating.sll32(1).0, 0x55555554_AAAAAAAA);
937		assert_eq!(alternating.srl32(1).0, 0x55555555_2AAAAAAA);
938		assert_eq!(alternating.sra32(1).0, 0xD5555555_2AAAAAAA);
939
940		// Test zero shifts
941		assert_eq!(w1.sll32(0), w1);
942		assert_eq!(w1.srl32(0), w1);
943		assert_eq!(w1.sra32(0), w1);
944
945		// Test that shifts are independent between halves
946		let w6 = Word(0x00000001_00000000);
947		assert_eq!(w6.sll32(31).0, 0x80000000_00000000);
948		assert_eq!(w6.srl32(1).0, 0x00000000_00000000);
949
950		// Test rotr32 edge cases
951		let w7 = Word(0x80000001_80000001);
952		assert_eq!(w7.rotr32(1).0, 0xC0000000_C0000000);
953		assert_eq!(w7.rotr32(31).0, 0x00000003_00000003);
954
955		// Test rotr32 rotation wrapping
956		let w8 = Word(0x12345678_9ABCDEF0);
957		assert_eq!(w8.rotr32(4).0, 0x81234567_09ABCDEF);
958		assert_eq!(w8.rotr32(16).0, 0x56781234_DEF09ABC);
959
960		// Test rotr32 with different values in each half
961		let w9 = Word(0xFFFF0000_0000FFFF);
962		assert_eq!(w9.rotr32(16).0, 0x0000FFFF_FFFF0000);
963
964		// Test rotr32 zero rotation
965		assert_eq!(w8.rotr32(0), w8);
966	}
967}