binius_core/
word.rs

1// Copyright 2025 Irreducible Inc.
2//! [`Word`] related definitions.
3
4use std::{
5	fmt,
6	ops::{BitAnd, BitOr, BitXor, Not, Shl, Shr},
7};
8
9use binius_utils::serialization::{DeserializeBytes, SerializationError, SerializeBytes};
10use bytes::{Buf, BufMut};
11
12/// [`Word`] is 64-bit value and is a fundamental unit of data in Binius64. All computation and
13/// constraints operate on it.
14#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
15pub struct Word(pub u64);
16
17impl Word {
18	/// All zero bit pattern, zero, nil, null.
19	pub const ZERO: Word = Word(0);
20	/// 1.
21	pub const ONE: Word = Word(1);
22	/// All bits set to one.
23	pub const ALL_ONE: Word = Word(u64::MAX);
24	/// 32 lower bits are set to one, all other bits are zero.
25	pub const MASK_32: Word = Word(0x00000000FFFFFFFF);
26	/// Most Significant Bit is set to one, all other bits are zero.
27	///
28	/// This is a canonical representation of true.
29	pub const MSB_ONE: Word = Word(0x8000000000000000);
30}
31
32impl fmt::Debug for Word {
33	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34		write!(f, "Word({:#018x})", self.0)
35	}
36}
37
38impl BitAnd for Word {
39	type Output = Self;
40
41	fn bitand(self, rhs: Self) -> Self::Output {
42		Word(self.0 & rhs.0)
43	}
44}
45
46impl BitOr for Word {
47	type Output = Self;
48
49	fn bitor(self, rhs: Self) -> Self::Output {
50		Word(self.0 | rhs.0)
51	}
52}
53
54impl BitXor for Word {
55	type Output = Self;
56
57	fn bitxor(self, rhs: Self) -> Self::Output {
58		Word(self.0 ^ rhs.0)
59	}
60}
61
62impl Shl<u32> for Word {
63	type Output = Self;
64
65	fn shl(self, rhs: u32) -> Self::Output {
66		Word(self.0 << rhs)
67	}
68}
69
70impl Shr<u32> for Word {
71	type Output = Self;
72
73	fn shr(self, rhs: u32) -> Self::Output {
74		Word(self.0 >> rhs)
75	}
76}
77
78impl Not for Word {
79	type Output = Self;
80
81	fn not(self) -> Self::Output {
82		Word(!self.0)
83	}
84}
85
86impl Word {
87	/// Creates a new `Word` from a 64-bit unsigned integer.
88	pub fn from_u64(value: u64) -> Word {
89		Word(value)
90	}
91
92	/// Performs 32-bit addition.
93	///
94	/// Returns (sum, carry_out) where ith carry_out bit is set to one if there is a carry out at
95	/// that bit position.
96	pub fn iadd_cout_32(self, rhs: Word) -> (Word, Word) {
97		let Word(lhs) = self;
98		let Word(rhs) = rhs;
99		let full_sum = lhs.wrapping_add(rhs);
100		let sum = full_sum & 0x00000000_FFFFFFFF;
101		let cout = (lhs & rhs) | ((lhs ^ rhs) & !full_sum);
102		(Word(sum), Word(cout))
103	}
104
105	/// Performs 64-bit addition with carry input bit.
106	///
107	/// cin is a carry-in from the previous addition. Since it can only affect the LSB only, the cin
108	/// could be 1 if there is carry over, or 0 otherwise.
109	///
110	/// Returns (sum, carry_out) where ith carry_out bit is set to one if there is a carry out at
111	/// that bit position.
112	pub fn iadd_cin_cout(self, rhs: Word, cin: Word) -> (Word, Word) {
113		debug_assert!(cin == Word::ZERO || cin == Word::ONE, "cin must be 0 or 1");
114		let Word(lhs) = self;
115		let Word(rhs) = rhs;
116		let Word(cin) = cin;
117		let sum = lhs.wrapping_add(rhs).wrapping_add(cin);
118		let cout = (lhs & rhs) | ((lhs ^ rhs) & !sum);
119		(Word(sum), Word(cout))
120	}
121
122	/// Performs 64-bit subtraction with borrow input bit.
123	///
124	/// bin is a borrow-in from the previous subtraction. Since it can only affect the LSB only, the
125	/// bin could be 1 if there is borrow over, or 0 otherwise.
126	///
127	/// Returns (diff, borrow_out) where ith borrow_out bit is set to one if there is a borrow out
128	/// at that bit position.
129	pub fn isub_bin_bout(self, rhs: Word, bin: Word) -> (Word, Word) {
130		debug_assert!(bin == Word::ZERO || bin == Word::ONE, "bin must be 0 or 1");
131		let Word(lhs) = self;
132		let Word(rhs) = rhs;
133		let Word(bin) = bin;
134		let diff = lhs.wrapping_sub(rhs).wrapping_sub(bin);
135		let bout = (!lhs & rhs) | (!(lhs ^ rhs) & diff);
136		(Word(diff), Word(bout))
137	}
138
139	/// Performs shift right by a given number of bits followed by masking with a 32-bit mask.
140	pub fn shr_32(self, n: u32) -> Word {
141		let Word(value) = self;
142		// Shift right logically by n bits and mask with 32-bit mask
143		let result = (value >> n) & 0x00000000_FFFFFFFF;
144		Word(result)
145	}
146
147	/// Shift Arithmetic Right by a given number of bits.
148	///
149	/// This is similar to a logical shift right, but it shifts the sign bit to the right.
150	pub fn sar(&self, n: u32) -> Word {
151		let Word(value) = self;
152		let value = *value as i64;
153		let result = value >> n;
154		Word(result as u64)
155	}
156
157	/// Rotate Right by a given number of bits followed by masking with a 32-bit mask.
158	pub fn rotr_32(self, n: u32) -> Word {
159		let Word(value) = self;
160		let n = n % 32; // Ensure n is within 0-31 range
161		// Extract lower 32 bits for rotation
162		let value_32 = value & 0x00000000_FFFFFFFF;
163		if n == 0 {
164			return Word(value_32); // Avoid full-width shift
165		}
166		// Rotate right: (value >> n) | (value << (32 - n))
167		let result = ((value_32 >> n) | (value_32 << (32 - n))) & 0x00000000_FFFFFFFF;
168		Word(result)
169	}
170
171	/// Rotate Right by a given number of bits.
172	pub fn rotr(self, n: u32) -> Word {
173		let Word(value) = self;
174		let n = n % 64; // Ensure n is within 0-63 range
175		if n == 0 {
176			return self; // Avoid full-width shift
177		}
178		let result = (value << (64 - n)) | (value >> n);
179		Word(result)
180	}
181
182	/// Unsigned integer multiplication.
183	///
184	/// Multiplies two 64-bit unsigned integers and returns the 128-bit result split into high and
185	/// low 64-bit words, respectively.
186	pub fn imul(self, rhs: Word) -> (Word, Word) {
187		let Word(lhs) = self;
188		let Word(rhs) = rhs;
189		let result = (lhs as u128) * (rhs as u128);
190
191		let hi = (result >> 64) as u64;
192		let lo = (result & 0x0000000000000000_FFFFFFFFFFFFFFFF) as u64;
193		(Word(hi), Word(lo))
194	}
195
196	/// Signed integer multiplication.
197	///
198	/// Multiplies two 64-bit signed integers and returns the 128-bit result split into high and
199	/// low 64-bit words, respectively.
200	pub fn smul(self, rhs: Word) -> (Word, Word) {
201		let Word(lhs) = self;
202		let Word(rhs) = rhs;
203		// Interpret as signed 64-bit integers
204		let a = lhs as i64;
205		let b = rhs as i64;
206		// Perform signed multiplication as 128-bit
207		let result = (a as i128) * (b as i128);
208		// Extract high and low 64-bit words
209		let hi = (result >> 64) as u64;
210		let lo = result as u64;
211		(Word(hi), Word(lo))
212	}
213
214	/// Integer addition.
215	///
216	/// Wraps around on overflow.
217	pub fn wrapping_add(self, rhs: Word) -> Word {
218		Word(self.0.wrapping_add(rhs.0))
219	}
220
221	/// Integer subtraction.
222	///
223	/// Wraps around on overflow.
224	pub fn wrapping_sub(self, rhs: Word) -> Word {
225		Word(self.0.wrapping_sub(rhs.0))
226	}
227
228	/// Returns the integer value as a 64-bit unsigned integer.
229	pub fn as_u64(self) -> u64 {
230		self.0
231	}
232
233	/// Tests if this Word represents true as an MSB-bool.
234	///
235	/// In MSB-bool representation, a value is true if its Most Significant Bit (bit 63) is set to
236	/// 1. All other bits are ignored for the boolean value.
237	///
238	/// Returns true if the MSB is 1, false otherwise.
239	pub fn is_msb_true(self) -> bool {
240		(self.0 & 0x8000000000000000) != 0
241	}
242
243	/// Tests if this Word represents false as an MSB-bool.
244	///
245	/// In MSB-bool representation, a value is false if its Most Significant Bit (bit 63) is 0.
246	/// All other bits are ignored for the boolean value.
247	///
248	/// Returns true if the MSB is 0, false otherwise.
249	pub fn is_msb_false(self) -> bool {
250		(self.0 & 0x8000000000000000) == 0
251	}
252}
253
254impl SerializeBytes for Word {
255	fn serialize(&self, write_buf: impl BufMut) -> Result<(), SerializationError> {
256		self.0.serialize(write_buf)
257	}
258}
259
260impl DeserializeBytes for Word {
261	fn deserialize(read_buf: impl Buf) -> Result<Self, SerializationError>
262	where
263		Self: Sized,
264	{
265		Ok(Word(u64::deserialize(read_buf)?))
266	}
267}
268
269#[cfg(test)]
270mod tests {
271	use proptest::prelude::*;
272
273	use super::*;
274
275	#[test]
276	fn test_constants() {
277		assert_eq!(Word::ZERO, Word(0));
278		assert_eq!(Word::ONE, Word(1));
279		assert_eq!(Word::ALL_ONE, Word(0xFFFFFFFFFFFFFFFF));
280		assert_eq!(Word::MASK_32, Word(0x00000000FFFFFFFF));
281		assert_eq!(Word::MSB_ONE, Word(0x8000000000000000));
282	}
283
284	#[test]
285	fn test_msb_bool() {
286		// Test MSB_ONE is true
287		assert!(Word::MSB_ONE.is_msb_true());
288		assert!(!Word::MSB_ONE.is_msb_false());
289
290		// Test ZERO is false
291		assert!(!Word::ZERO.is_msb_true());
292		assert!(Word::ZERO.is_msb_false());
293
294		// Test various values with MSB set
295		assert!(Word(0x8000000000000000).is_msb_true());
296		assert!(Word(0x8000000000000001).is_msb_true());
297		assert!(Word(0x80000000FFFFFFFF).is_msb_true());
298		assert!(Word(0xFFFFFFFFFFFFFFFF).is_msb_true());
299
300		// Test various values with MSB clear
301		assert!(Word(0x7FFFFFFFFFFFFFFF).is_msb_false());
302		assert!(Word(0x0000000000000001).is_msb_false());
303		assert!(Word(0x00000000FFFFFFFF).is_msb_false());
304		assert!(Word(0x7000000000000000).is_msb_false());
305
306		// Verify complementary behavior
307		let test_word = Word(0x8123456789ABCDEF);
308		assert!(test_word.is_msb_true());
309		assert!(!test_word.is_msb_false());
310
311		let test_word2 = Word(0x7123456789ABCDEF);
312		assert!(!test_word2.is_msb_true());
313		assert!(test_word2.is_msb_false());
314	}
315
316	proptest! {
317		#[test]
318		fn prop_msb_bool(val in any::<u64>()) {
319			let word = Word(val);
320
321			// is_msb_true and is_msb_false should be complementary
322			assert_eq!(word.is_msb_true(), !word.is_msb_false());
323			assert_eq!(word.is_msb_false(), !word.is_msb_true());
324
325			// Check against direct bit manipulation
326			let msb_set = (val & 0x8000000000000000) != 0;
327			assert_eq!(word.is_msb_true(), msb_set);
328			assert_eq!(word.is_msb_false(), !msb_set);
329
330			// MSB operations should ignore lower bits
331			let word_with_msb = Word(val | 0x8000000000000000);
332			let word_without_msb = Word(val & 0x7FFFFFFFFFFFFFFF);
333			assert!(word_with_msb.is_msb_true());
334			assert!(word_without_msb.is_msb_false());
335		}
336
337		#[test]
338		fn prop_bitwise_and(a in any::<u64>(), b in any::<u64>()) {
339			let wa = Word(a);
340			let wb = Word(b);
341
342			// Basic AND properties
343			assert_eq!((wa & wb).0, a & b);
344			assert_eq!(wa & Word::ALL_ONE, wa);
345			assert_eq!(wa & Word::ZERO, Word::ZERO);
346			assert_eq!(wa & wa, wa); // Idempotent
347
348			// Commutative
349			assert_eq!(wa & wb, wb & wa);
350		}
351
352		#[test]
353		fn prop_bitwise_or(a in any::<u64>(), b in any::<u64>()) {
354			let wa = Word(a);
355			let wb = Word(b);
356
357			// Basic OR properties
358			assert_eq!((wa | wb).0, a | b);
359			assert_eq!(wa | Word::ZERO, wa);
360			assert_eq!(wa | Word::ALL_ONE, Word::ALL_ONE);
361			assert_eq!(wa | wa, wa); // Idempotent
362
363			// Commutative
364			assert_eq!(wa | wb, wb | wa);
365		}
366
367		#[test]
368		fn prop_bitwise_xor(a in any::<u64>(), b in any::<u64>()) {
369			let wa = Word(a);
370			let wb = Word(b);
371
372			// Basic XOR properties
373			assert_eq!((wa ^ wb).0, a ^ b);
374			assert_eq!(wa ^ Word::ZERO, wa);
375			assert_eq!(wa ^ wa, Word::ZERO);
376			assert_eq!(wa ^ Word::ALL_ONE, !wa);
377
378			// Commutative
379			assert_eq!(wa ^ wb, wb ^ wa);
380
381			// Double XOR cancels
382			assert_eq!(wa ^ wb ^ wb, wa);
383		}
384
385		#[test]
386		fn prop_bitwise_not(a in any::<u64>()) {
387			let wa = Word(a);
388
389			// Basic NOT properties
390			assert_eq!((!wa).0, !a);
391			assert_eq!(!(!wa), wa); // Double negation
392			assert_eq!(!Word::ZERO, Word::ALL_ONE);
393			assert_eq!(!Word::ALL_ONE, Word::ZERO);
394
395			// De Morgan's laws
396			let wb = Word(a.wrapping_add(1));
397			assert_eq!(!(wa & wb), !wa | !wb);
398			assert_eq!(!(wa | wb), !wa & !wb);
399		}
400
401		#[test]
402		fn prop_shift_left(val in any::<u64>(), shift in 0u32..64) {
403			let w = Word(val);
404			assert_eq!((w << shift).0, val << shift);
405
406			// Shifting by 0 is identity
407			assert_eq!(w << 0, w);
408
409			// Shifting by 64 or more gives 0
410			if shift >= 64 {
411				assert_eq!((w << shift).0, 0);
412			}
413		}
414
415		#[test]
416		fn prop_shift_right(val in any::<u64>(), shift in 0u32..64) {
417			let w = Word(val);
418			assert_eq!((w >> shift).0, val >> shift);
419
420			// Shifting by 0 is identity
421			assert_eq!(w >> 0, w);
422
423			// Shifting by 64 or more gives 0
424			if shift >= 64 {
425				assert_eq!((w >> shift).0, 0);
426			}
427		}
428
429		#[test]
430		fn prop_shift_inverse(val in any::<u64>(), shift in 1u32..64) {
431			let w = Word(val);
432			// Left then right shift loses high bits
433			let mask = (1u64 << (64 - shift)) - 1;
434			assert_eq!(((w << shift) >> shift).0, val & mask);
435
436			// Right then left shift loses low bits
437			let high_mask = !((1u64 << shift) - 1);
438			assert_eq!(((w >> shift) << shift).0, val & high_mask);
439		}
440
441		#[test]
442		fn prop_sar(val in any::<u64>(), shift in 0u32..64) {
443			let w = Word(val);
444			let expected = ((val as i64) >> shift) as u64;
445			assert_eq!(w.sar(shift).0, expected);
446
447			// SAR by 0 is identity
448			assert_eq!(w.sar(0), w);
449
450			// SAR by 63 gives all 0s or all 1s depending on sign
451			let sign_extended = if (val as i64) < 0 {
452				Word(0xFFFFFFFFFFFFFFFF)
453			} else {
454				Word(0)
455			};
456			assert_eq!(w.sar(63), sign_extended);
457		}
458
459		#[test]
460		fn prop_sar_sign_extension(val in any::<u64>(), shift in 1u32..64) {
461			let w = Word(val);
462			let result = w.sar(shift);
463
464			// Check sign bit is extended
465			let is_negative = (val as i64) < 0;
466			if is_negative {
467				// High bits should all be 1
468				let mask = !((1u64 << (64 - shift)) - 1);
469				assert_eq!(result.0 & mask, mask);
470			} else {
471				// High bits should all be 0
472				let mask = !((1u64 << (64 - shift)) - 1);
473				assert_eq!(result.0 & mask, 0);
474			}
475		}
476
477		#[test]
478		fn prop_iadd_cout_32(a in any::<u32>(), b in any::<u32>()) {
479			let wa = Word(a as u64);
480			let wb = Word(b as u64);
481			let (sum, cout) = wa.iadd_cout_32(wb);
482
483			// Sum should be masked to 32 bits
484			assert_eq!(sum.0, (a as u64 + b as u64) & 0xFFFFFFFF);
485
486			// Carry computation: cout = (a & b) | ((a ^ b) & !sum)
487			let expected_cout = (a as u64 & b as u64) | ((a as u64 ^ b as u64) & !sum.0);
488			assert_eq!(cout.0, expected_cout);
489
490			// Identity: adding 0 produces no carries
491			let (sum0, cout0) = wa.iadd_cout_32(Word::ZERO);
492			assert_eq!(sum0.0, a as u64);
493			assert_eq!(cout0, Word::ZERO);
494		}
495
496		#[test]
497		fn prop_iadd_cin_cout(a in any::<u64>(), b in any::<u64>(), cin in 0u64..=1) {
498			let wa = Word(a);
499			let wb = Word(b);
500			let wcin = Word(cin);
501			let (sum, cout) = wa.iadd_cin_cout(wb, wcin);
502
503			// Basic addition with carry
504			let expected_sum = a.wrapping_add(b).wrapping_add(cin);
505			assert_eq!(sum.0, expected_sum);
506
507			// Carry computation: cout at each bit position
508			let expected_cout = (a & b) | ((a ^ b) & !expected_sum);
509			assert_eq!(cout.0, expected_cout);
510
511			// Without carry in, same as regular addition
512			let (sum0, cout0) = wa.iadd_cin_cout(wb, Word::ZERO);
513			let full_sum = a.wrapping_add(b);
514			assert_eq!(sum0.0, full_sum);
515			assert_eq!(cout0.0, (a & b) | ((a ^ b) & !full_sum));
516		}
517
518		#[test]
519		fn prop_isub_bin_bout(a in any::<u64>(), b in any::<u64>(), bin in 0u64..=1) {
520			let wa = Word(a);
521			let wb = Word(b);
522			let wbin = Word(bin);
523			let (diff, bout) = wa.isub_bin_bout(wb, wbin);
524
525			// Basic subtraction with borrow
526			let expected_diff = a.wrapping_sub(b).wrapping_sub(bin);
527			assert_eq!(diff.0, expected_diff);
528
529			// Borrow computation: bout = (!a & b) | (!(a ^ b) & diff)
530			let expected_bout = (!a & b) | (!(a ^ b) & expected_diff);
531			assert_eq!(bout.0, expected_bout);
532
533			// Without borrow in
534			let (diff0, bout0) = wa.isub_bin_bout(wb, Word::ZERO);
535			let expected = a.wrapping_sub(b);
536			assert_eq!(diff0.0, expected);
537			assert_eq!(bout0.0, (!a & b) | (!(a ^ b) & expected));
538		}
539
540		#[test]
541		fn prop_shr_32(val in any::<u64>(), shift in 0u32..64) {
542			let w = Word(val);
543			let result = w.shr_32(shift);
544
545			// Result should be the full value shifted right, then masked to 32 bits
546			let expected = (val >> shift) & 0xFFFFFFFF;
547			assert_eq!(result.0, expected);
548
549			// Shifting by 0 gives lower 32 bits
550			assert_eq!(w.shr_32(0).0, val & 0xFFFFFFFF);
551
552			// Shifting by 32 or more gives upper bits or zeros
553			if shift >= 32 {
554				assert_eq!(result.0, (val >> shift) & 0xFFFFFFFF);
555			}
556		}
557
558		#[test]
559		fn prop_rotr_32(val in any::<u32>(), rotate in 0u32..64) {
560			let w = Word(val as u64);
561			let result = w.rotr_32(rotate);
562
563			// Only lower 32 bits are rotated
564			let rotate_mod = rotate % 32;
565			let val32 = val as u64;
566			let expected = if rotate_mod == 0 {
567				val32
568			} else {
569				((val32 >> rotate_mod) | (val32 << (32 - rotate_mod))) & 0xFFFFFFFF
570			};
571			assert_eq!(result.0, expected);
572
573			// Rotation by 0 or 32 is identity
574			assert_eq!(w.rotr_32(0).0, val32);
575			assert_eq!(w.rotr_32(32).0, val32);
576		}
577
578		#[test]
579		fn prop_rotr(val in any::<u64>(), rotate in 0u32..128) {
580			let w = Word(val);
581			let result = w.rotr(rotate);
582
583			// Rotation is modulo 64
584			let rotate_mod = rotate % 64;
585			let expected = if rotate_mod == 0 {
586				val
587			} else {
588				(val >> rotate_mod) | (val << (64 - rotate_mod))
589			};
590			assert_eq!(result.0, expected);
591
592			// Rotation by 0 or 64 is identity
593			assert_eq!(w.rotr(0), w);
594			assert_eq!(w.rotr(64), w);
595
596			// Double rotation
597			let r1 = rotate % 64;
598			let r2 = (64 - r1) % 64;
599			if r1 != 0 {
600				assert_eq!(w.rotr(r1).rotr(r2), w);
601			}
602		}
603
604		#[test]
605		fn prop_imul(a in any::<u64>(), b in any::<u64>()) {
606			let wa = Word(a);
607			let wb = Word(b);
608			let (hi, lo) = wa.imul(wb);
609
610			// Check against native 128-bit multiplication
611			let result = (a as u128) * (b as u128);
612			assert_eq!(hi.0, (result >> 64) as u64);
613			assert_eq!(lo.0, result as u64);
614
615			// Multiplication by 0 gives 0
616			let (hi0, lo0) = wa.imul(Word::ZERO);
617			assert_eq!(hi0, Word::ZERO);
618			assert_eq!(lo0, Word::ZERO);
619
620			// Multiplication by 1 is identity
621			let (hi1, lo1) = wa.imul(Word::ONE);
622			assert_eq!(hi1, Word::ZERO);
623			assert_eq!(lo1, wa);
624
625			// Commutative
626			let (hi_ab, lo_ab) = wa.imul(wb);
627			let (hi_reversed, lo_reversed) = wb.imul(wa);
628			assert_eq!(hi_ab, hi_reversed);
629			assert_eq!(lo_ab, lo_reversed);
630		}
631
632		#[test]
633		fn prop_smul(a in any::<u64>(), b in any::<u64>()) {
634			let wa = Word(a);
635			let wb = Word(b);
636			let (hi, lo) = wa.smul(wb);
637
638			// Check against native 128-bit signed multiplication
639			let result = (a as i64 as i128) * (b as i64 as i128);
640			assert_eq!(hi.0, (result >> 64) as u64);
641			assert_eq!(lo.0, result as u64);
642
643			// Multiplication by 0 gives 0
644			let (hi0, lo0) = wa.smul(Word::ZERO);
645			assert_eq!(hi0, Word::ZERO);
646			assert_eq!(lo0, Word::ZERO);
647
648			// Multiplication by 1 is identity
649			let (hi1, lo1) = wa.smul(Word::ONE);
650			let expected_hi = if (a as i64) < 0 { Word(0xFFFFFFFFFFFFFFFF) } else { Word::ZERO };
651			assert_eq!(hi1, expected_hi);
652			assert_eq!(lo1, wa);
653
654			// Multiplication by -1 negates
655			let (hi_neg, lo_neg) = wa.smul(Word(0xFFFFFFFFFFFFFFFF));
656			let neg_result = -(a as i64 as i128);
657			assert_eq!(hi_neg.0, (neg_result >> 64) as u64);
658			assert_eq!(lo_neg.0, neg_result as u64);
659
660			// Commutative
661			let (hi_ab, lo_ab) = wa.smul(wb);
662			let (hi_reversed, lo_reversed) = wb.smul(wa);
663			assert_eq!(hi_ab, hi_reversed);
664			assert_eq!(lo_ab, lo_reversed);
665		}
666
667		#[test]
668		fn prop_wrapping_sub(a in any::<u64>(), b in any::<u64>()) {
669			let wa = Word(a);
670			let wb = Word(b);
671			let result = wa.wrapping_sub(wb);
672
673			assert_eq!(result.0, a.wrapping_sub(b));
674
675			// Subtracting 0 is identity
676			assert_eq!(wa.wrapping_sub(Word::ZERO), wa);
677
678			// Subtracting itself gives 0
679			assert_eq!(wa.wrapping_sub(wa), Word::ZERO);
680
681			// Adding then subtracting cancels
682			let sum = Word(a.wrapping_add(b));
683			assert_eq!(sum.wrapping_sub(wb), wa);
684		}
685
686		#[test]
687		fn prop_conversions(val in any::<u64>()) {
688			let word = Word::from_u64(val);
689			assert_eq!(word.as_u64(), val);
690			assert_eq!(word, Word(val));
691
692			// Round trip
693			assert_eq!(Word::from_u64(word.as_u64()), word);
694		}
695
696		#[test]
697		fn prop_debug_format(val in any::<u64>()) {
698			let word = Word(val);
699			let debug_str = format!("{:?}", word);
700			assert!(debug_str.starts_with("Word(0x"));
701			assert!(debug_str.ends_with(")"));
702			// Check the hex value is correct (lowercase)
703			let expected = format!("Word({:#018x})", val);
704			assert_eq!(debug_str, expected);
705		}
706	}
707}