Skip to main content

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