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