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