xrpld_number/
lib.rs

1mod xrpl_iou_value;
2
3pub use xrpl_iou_value::{FLOAT_NEGATIVE_ONE, FLOAT_ONE, FLOAT_ZERO, XrplIouValue};
4
5// Include the generated bindings in a module to avoid naming conflicts
6mod ffi {
7    #![allow(non_upper_case_globals)]
8    #![allow(non_camel_case_types)]
9    #![allow(non_snake_case)]
10    #![allow(unused)]
11    include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
12}
13
14use std::ffi::CStr;
15use std::fmt;
16use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
17
18/// Error types for Number operations
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub enum NumberError {
21    Overflow,
22    DivideByZero,
23    InvalidArgument,
24    OutOfMemory,
25    Unknown,
26}
27
28impl fmt::Display for NumberError {
29    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30        match self {
31            NumberError::Overflow => write!(f, "Number overflow"),
32            NumberError::DivideByZero => write!(f, "Division by zero"),
33            NumberError::InvalidArgument => write!(f, "Invalid argument"),
34            NumberError::OutOfMemory => write!(f, "Out of memory"),
35            NumberError::Unknown => write!(f, "Unknown error"),
36        }
37    }
38}
39
40impl std::error::Error for NumberError {}
41
42impl From<ffi::NumberError> for NumberError {
43    fn from(error: ffi::NumberError) -> Self {
44        match error {
45            ffi::NumberError_NUMBER_ERROR_OVERFLOW => NumberError::Overflow,
46            ffi::NumberError_NUMBER_ERROR_DIVIDE_BY_ZERO => NumberError::DivideByZero,
47            ffi::NumberError_NUMBER_ERROR_INVALID_ARGUMENT => NumberError::InvalidArgument,
48            ffi::NumberError_NUMBER_ERROR_OUT_OF_MEMORY => NumberError::OutOfMemory,
49            _ => NumberError::Unknown,
50        }
51    }
52}
53
54/// Rounding mode for Number operations
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub enum RoundingMode {
57    ToNearest,
58    TowardsZero,
59    Downward,
60    Upward,
61}
62
63impl From<RoundingMode> for ffi::RoundingMode {
64    fn from(mode: RoundingMode) -> Self {
65        match mode {
66            RoundingMode::ToNearest => ffi::RoundingMode_ROUNDING_TO_NEAREST,
67            RoundingMode::TowardsZero => ffi::RoundingMode_ROUNDING_TOWARDS_ZERO,
68            RoundingMode::Downward => ffi::RoundingMode_ROUNDING_DOWNWARD,
69            RoundingMode::Upward => ffi::RoundingMode_ROUNDING_UPWARD,
70        }
71    }
72}
73
74impl From<ffi::RoundingMode> for RoundingMode {
75    fn from(mode: ffi::RoundingMode) -> Self {
76        match mode {
77            ffi::RoundingMode_ROUNDING_TO_NEAREST => RoundingMode::ToNearest,
78            ffi::RoundingMode_ROUNDING_TOWARDS_ZERO => RoundingMode::TowardsZero,
79            ffi::RoundingMode_ROUNDING_DOWNWARD => RoundingMode::Downward,
80            ffi::RoundingMode_ROUNDING_UPWARD => RoundingMode::Upward,
81            _ => RoundingMode::ToNearest,
82        }
83    }
84}
85
86/// High-precision decimal number
87pub struct Number {
88    ptr: *mut ffi::Number,
89}
90
91unsafe impl Send for Number {}
92unsafe impl Sync for Number {}
93
94impl Number {
95    /// Create a new Number initialized to zero
96    pub fn new() -> Self {
97        let ptr = unsafe { ffi::number_new() };
98        if ptr.is_null() {
99            panic!("Failed to allocate Number");
100        }
101        Number { ptr }
102    }
103
104    /// Create a Number from an i64 mantissa
105    pub fn from_i64(mantissa: i64) -> Result<Self, NumberError> {
106        let mut error = ffi::NumberError_NUMBER_SUCCESS;
107        let ptr = unsafe { ffi::number_new_from_int64(mantissa, &mut error) };
108        if ptr.is_null() {
109            return Err(error.into());
110        }
111        Ok(Number { ptr })
112    }
113
114    /// Create a Number from mantissa and exponent
115    pub fn from_mantissa_exponent(mantissa: i64, exponent: i32) -> Result<Self, NumberError> {
116        let mut error = ffi::NumberError_NUMBER_SUCCESS;
117        let ptr = unsafe { ffi::number_new_from_mantissa_exponent(mantissa, exponent, &mut error) };
118        if ptr.is_null() {
119            return Err(error.into());
120        }
121        Ok(Number { ptr })
122    }
123
124    /// Create a Number from an XRP Ledger IOU value (8-byte buffer)
125    ///
126    /// Parses the XRP Ledger token amount format:
127    /// - Bit 0 (MSB): Type bit (1=token, 0=XRP)
128    /// - Bit 1: Sign bit (1=positive, 0=negative)
129    /// - Bits 2-9: Exponent (unsigned, add 97 to get actual exponent in range -96 to +80)
130    /// - Bits 10-63: Mantissa (54 bits, normalized to 10^15 to 10^16-1)
131    pub fn from_xrpl_iou_value(buffer: XrplIouValue) -> Result<Self, NumberError> {
132        if buffer == FLOAT_ZERO {
133            return Ok(Number::new());
134        }
135
136        // Convert bytes to u64 (big-endian)
137        let value = u64::from_be_bytes(buffer);
138
139        // Extract fields using bit operations
140        let type_bit = (value >> 63) & 1;
141        let sign_bit = (value >> 62) & 1;
142        let exponent_bits = ((value >> 54) & 0xFF) as u8;
143        let mantissa_bits = value & 0x3FFFFFFFFFFFFF; // 54 bits
144
145        // Validate this is a token amount (type bit should be 1)
146        if type_bit != 1 {
147            return Err(NumberError::InvalidArgument);
148        }
149
150        // Validate mantissa is in expected range (should be normalized)
151        if mantissa_bits == 0 {
152            // Zero mantissa should only occur with the special zero case
153            return Err(NumberError::InvalidArgument);
154        }
155
156        // Convert exponent: subtract 97 to get actual exponent (-96 to +80)
157        let exponent = (exponent_bits as i32) - 97;
158
159        // Validate exponent range
160        if !(-96..=80).contains(&exponent) {
161            return Err(NumberError::InvalidArgument);
162        }
163
164        // Convert mantissa to signed based on sign bit
165        let mantissa = if sign_bit == 1 {
166            mantissa_bits as i64 // Positive
167        } else {
168            -(mantissa_bits as i64) // Negative
169        };
170
171        // Create Number from mantissa and exponent
172        Self::from_mantissa_exponent(mantissa, exponent)
173    }
174
175    /// Convert this Number to an XRP Ledger IOU value (8-byte buffer)
176    ///
177    /// Serializes to the XRP Ledger token amount format:
178    /// - Bit 0 (MSB): Type bit (1=token, 0=XRP)
179    /// - Bit 1: Sign bit (1=positive, 0=negative)
180    /// - Bits 2-9: Exponent (unsigned, add 97 to get actual exponent in range -96 to +80)
181    /// - Bits 10-63: Mantissa (54 bits, normalized to 10^15 to 10^16-1)
182    pub fn to_xrpl_iou_value(&self) -> Result<XrplIouValue, NumberError> {
183        // Special case for zero
184        if self.is_zero() {
185            return Ok(FLOAT_ZERO);
186        }
187
188        let mantissa = self.mantissa();
189        let exponent = self.exponent();
190
191        // Validate that we can represent this number in XRPL format
192        if !(-96..=80).contains(&exponent) {
193            return Err(NumberError::InvalidArgument);
194        }
195
196        // Get absolute mantissa for bit packing
197        let abs_mantissa = mantissa.unsigned_abs();
198
199        // Validate mantissa is in the expected range for XRPL format
200        // The C++ Number class should normalize values, but let's be safe
201        if abs_mantissa == 0 {
202            return Ok(FLOAT_ZERO);
203        }
204
205        // Check if mantissa fits in 54 bits
206        if abs_mantissa > 0x3FFFFFFFFFFFFF {
207            return Err(NumberError::InvalidArgument);
208        }
209
210        // Determine sign bit (1=positive, 0=negative)
211        let sign_bit = if mantissa >= 0 { 1u64 } else { 0u64 };
212
213        // Convert exponent: add 97 to make it unsigned
214        let exponent_bits = (exponent + 97) as u8;
215
216        // Construct the 64-bit value
217        let mut value = 0u64;
218        value |= 1u64 << 63; // Type bit = 1 (token)
219        value |= sign_bit << 62; // Sign bit
220        value |= (exponent_bits as u64) << 54; // Exponent in bits 54-61
221        value |= abs_mantissa; // Mantissa in bits 0-53
222
223        // Convert to big-endian bytes
224        Ok(value.to_be_bytes())
225    }
226
227    /// Get the mantissa of this Number
228    pub fn mantissa(&self) -> i64 {
229        unsafe { ffi::number_get_mantissa(self.ptr) }
230    }
231
232    /// Get the exponent of this Number
233    pub fn exponent(&self) -> i32 {
234        unsafe { ffi::number_get_exponent(self.ptr) }
235    }
236
237    /// Convert to i64 (with potential precision loss)
238    pub fn to_i64(&self) -> Result<i64, NumberError> {
239        let mut result = 0i64;
240        let error = unsafe { ffi::number_to_int64(self.ptr, &mut result) };
241        if error == ffi::NumberError_NUMBER_SUCCESS {
242            Ok(result)
243        } else {
244            Err(error.into())
245        }
246    }
247
248    /// Get the sign of this Number (-1, 0, or 1)
249    pub fn signum(&self) -> i32 {
250        unsafe { ffi::number_signum(self.ptr) }
251    }
252
253    /// Check if this Number is zero
254    pub fn is_zero(&self) -> bool {
255        unsafe { ffi::number_is_zero(self.ptr) }
256    }
257
258    /// Compute absolute value
259    pub fn abs(&self) -> Result<Self, NumberError> {
260        let result = Number::new();
261        let error = unsafe { ffi::number_abs(result.ptr, self.ptr) };
262        if error == ffi::NumberError_NUMBER_SUCCESS {
263            Ok(result)
264        } else {
265            Err(error.into())
266        }
267    }
268
269    /// Raise this Number to an unsigned integer power
270    pub fn pow(&self, exponent: u32) -> Result<Self, NumberError> {
271        let result = Number::new();
272        let error = unsafe { ffi::number_power_uint(result.ptr, self.ptr, exponent) };
273        if error == ffi::NumberError_NUMBER_SUCCESS {
274            Ok(result)
275        } else {
276            Err(error.into())
277        }
278    }
279
280    /// Compute the nth root of this Number
281    pub fn root(&self, degree: u32) -> Result<Self, NumberError> {
282        let result = Number::new();
283        let error = unsafe { ffi::number_root(result.ptr, self.ptr, degree) };
284        if error == ffi::NumberError_NUMBER_SUCCESS {
285            Ok(result)
286        } else {
287            Err(error.into())
288        }
289    }
290
291    /// Compute the square root of this Number
292    pub fn sqrt(&self) -> Result<Self, NumberError> {
293        let result = Number::new();
294        let error = unsafe { ffi::number_sqrt(result.ptr, self.ptr) };
295        if error == ffi::NumberError_NUMBER_SUCCESS {
296            Ok(result)
297        } else {
298            Err(error.into())
299        }
300    }
301
302    /// Compute the base-10 logarithm of this Number
303    pub fn log10(&self) -> Result<Self, NumberError> {
304        let result = Number::new();
305        let error = unsafe { ffi::number_log10(result.ptr, self.ptr) };
306        if error == ffi::NumberError_NUMBER_SUCCESS {
307            Ok(result)
308        } else {
309            Err(error.into())
310        }
311    }
312
313    /// Get the current global rounding mode
314    pub fn get_rounding_mode() -> RoundingMode {
315        let mode = unsafe { ffi::number_get_rounding_mode() };
316        mode.into()
317    }
318
319    /// Set the global rounding mode, returning the previous mode
320    pub fn set_rounding_mode(mode: RoundingMode) -> RoundingMode {
321        let prev_mode = unsafe { ffi::number_set_rounding_mode(mode.into()) };
322        prev_mode.into()
323    }
324
325    /// Get the minimum representable Number
326    pub fn min() -> Self {
327        let ptr = unsafe { ffi::number_min() };
328        if ptr.is_null() {
329            panic!("Failed to create min Number");
330        }
331        Number { ptr }
332    }
333
334    /// Get the maximum representable Number
335    pub fn max() -> Self {
336        let ptr = unsafe { ffi::number_max() };
337        if ptr.is_null() {
338            panic!("Failed to create max Number");
339        }
340        Number { ptr }
341    }
342
343    /// Get the lowest (most negative) representable Number
344    pub fn lowest() -> Self {
345        let ptr = unsafe { ffi::number_lowest() };
346        if ptr.is_null() {
347            panic!("Failed to create lowest Number");
348        }
349        Number { ptr }
350    }
351}
352
353impl Default for Number {
354    fn default() -> Self {
355        Self::new()
356    }
357}
358
359impl Clone for Number {
360    fn clone(&self) -> Self {
361        let mut error = ffi::NumberError_NUMBER_SUCCESS;
362        let ptr = unsafe { ffi::number_clone(self.ptr, &mut error) };
363        if ptr.is_null() {
364            panic!("Failed to clone Number: {:?}", NumberError::from(error));
365        }
366        Number { ptr }
367    }
368}
369
370impl Drop for Number {
371    fn drop(&mut self) {
372        unsafe { ffi::number_free(self.ptr) };
373    }
374}
375
376impl fmt::Display for Number {
377    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
378        let len = unsafe { ffi::number_string_length(self.ptr) };
379        let mut buffer = vec![0u8; len + 1];
380        let error = unsafe {
381            ffi::number_to_string(self.ptr, buffer.as_mut_ptr() as *mut i8, buffer.len())
382        };
383        if error != ffi::NumberError_NUMBER_SUCCESS {
384            return Err(fmt::Error);
385        }
386        let cstr = unsafe { CStr::from_ptr(buffer.as_ptr() as *const i8) };
387        let s = cstr.to_str().map_err(|_| fmt::Error)?;
388        write!(f, "{}", s)
389    }
390}
391
392impl fmt::Debug for Number {
393    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
394        f.debug_struct("Number")
395            .field("mantissa", &self.mantissa())
396            .field("exponent", &self.exponent())
397            .field("value", &self.to_string())
398            .finish()
399    }
400}
401
402// Arithmetic operations
403impl Add for &Number {
404    type Output = Result<Number, NumberError>;
405
406    fn add(self, rhs: &Number) -> Self::Output {
407        let result = Number::new();
408        let error = unsafe { ffi::number_add(result.ptr, self.ptr, rhs.ptr) };
409        if error == ffi::NumberError_NUMBER_SUCCESS {
410            Ok(result)
411        } else {
412            Err(error.into())
413        }
414    }
415}
416
417impl Add for Number {
418    type Output = Result<Number, NumberError>;
419
420    fn add(self, rhs: Number) -> Self::Output {
421        &self + &rhs
422    }
423}
424
425impl Sub for &Number {
426    type Output = Result<Number, NumberError>;
427
428    fn sub(self, rhs: &Number) -> Self::Output {
429        let result = Number::new();
430        let error = unsafe { ffi::number_subtract(result.ptr, self.ptr, rhs.ptr) };
431        if error == ffi::NumberError_NUMBER_SUCCESS {
432            Ok(result)
433        } else {
434            Err(error.into())
435        }
436    }
437}
438
439impl Sub for Number {
440    type Output = Result<Number, NumberError>;
441
442    fn sub(self, rhs: Number) -> Self::Output {
443        &self - &rhs
444    }
445}
446
447impl Mul for &Number {
448    type Output = Result<Number, NumberError>;
449
450    fn mul(self, rhs: &Number) -> Self::Output {
451        let result = Number::new();
452        let error = unsafe { ffi::number_multiply(result.ptr, self.ptr, rhs.ptr) };
453        if error == ffi::NumberError_NUMBER_SUCCESS {
454            Ok(result)
455        } else {
456            Err(error.into())
457        }
458    }
459}
460
461impl Mul for Number {
462    type Output = Result<Number, NumberError>;
463
464    fn mul(self, rhs: Number) -> Self::Output {
465        &self * &rhs
466    }
467}
468
469impl Div for &Number {
470    type Output = Result<Number, NumberError>;
471
472    fn div(self, rhs: &Number) -> Self::Output {
473        let result = Number::new();
474        let error = unsafe { ffi::number_divide(result.ptr, self.ptr, rhs.ptr) };
475        if error == ffi::NumberError_NUMBER_SUCCESS {
476            Ok(result)
477        } else {
478            Err(error.into())
479        }
480    }
481}
482
483impl Div for Number {
484    type Output = Result<Number, NumberError>;
485
486    fn div(self, rhs: Number) -> Self::Output {
487        &self / &rhs
488    }
489}
490
491impl Neg for &Number {
492    type Output = Result<Number, NumberError>;
493
494    fn neg(self) -> Self::Output {
495        let result = Number::new();
496        let error = unsafe { ffi::number_negate(result.ptr, self.ptr) };
497        if error == ffi::NumberError_NUMBER_SUCCESS {
498            Ok(result)
499        } else {
500            Err(error.into())
501        }
502    }
503}
504
505impl Neg for Number {
506    type Output = Result<Number, NumberError>;
507
508    fn neg(self) -> Self::Output {
509        -&self
510    }
511}
512
513// Assignment operations
514impl AddAssign<&Number> for Number {
515    fn add_assign(&mut self, rhs: &Number) {
516        let error = unsafe { ffi::number_add_assign(self.ptr, rhs.ptr) };
517        if error != ffi::NumberError_NUMBER_SUCCESS {
518            panic!("Number addition failed: {:?}", NumberError::from(error));
519        }
520    }
521}
522
523impl AddAssign<Number> for Number {
524    fn add_assign(&mut self, rhs: Number) {
525        *self += &rhs;
526    }
527}
528
529impl SubAssign<&Number> for Number {
530    fn sub_assign(&mut self, rhs: &Number) {
531        let error = unsafe { ffi::number_subtract_assign(self.ptr, rhs.ptr) };
532        if error != ffi::NumberError_NUMBER_SUCCESS {
533            panic!("Number subtraction failed: {:?}", NumberError::from(error));
534        }
535    }
536}
537
538impl SubAssign<Number> for Number {
539    fn sub_assign(&mut self, rhs: Number) {
540        *self -= &rhs;
541    }
542}
543
544impl MulAssign<&Number> for Number {
545    fn mul_assign(&mut self, rhs: &Number) {
546        let error = unsafe { ffi::number_multiply_assign(self.ptr, rhs.ptr) };
547        if error != ffi::NumberError_NUMBER_SUCCESS {
548            panic!(
549                "Number multiplication failed: {:?}",
550                NumberError::from(error)
551            );
552        }
553    }
554}
555
556impl MulAssign<Number> for Number {
557    fn mul_assign(&mut self, rhs: Number) {
558        *self *= &rhs;
559    }
560}
561
562impl DivAssign<&Number> for Number {
563    fn div_assign(&mut self, rhs: &Number) {
564        let error = unsafe { ffi::number_divide_assign(self.ptr, rhs.ptr) };
565        if error != ffi::NumberError_NUMBER_SUCCESS {
566            panic!("Number division failed: {:?}", NumberError::from(error));
567        }
568    }
569}
570
571impl DivAssign<Number> for Number {
572    fn div_assign(&mut self, rhs: Number) {
573        *self /= &rhs;
574    }
575}
576
577// Comparison operations
578impl PartialEq for Number {
579    fn eq(&self, other: &Self) -> bool {
580        unsafe { ffi::number_equals(self.ptr, other.ptr) }
581    }
582}
583
584impl Eq for Number {}
585
586impl PartialOrd for Number {
587    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
588        Some(self.cmp(other))
589    }
590}
591
592impl Ord for Number {
593    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
594        if unsafe { ffi::number_less_than(self.ptr, other.ptr) } {
595            std::cmp::Ordering::Less
596        } else if unsafe { ffi::number_greater_than(self.ptr, other.ptr) } {
597            std::cmp::Ordering::Greater
598        } else {
599            std::cmp::Ordering::Equal
600        }
601    }
602}
603
604// Convenience constructors
605impl From<i64> for Number {
606    fn from(value: i64) -> Self {
607        Number::from_i64(value).expect("Failed to create Number from i64")
608    }
609}
610
611impl TryFrom<XrplIouValue> for Number {
612    type Error = NumberError;
613
614    fn try_from(buffer: XrplIouValue) -> Result<Self, Self::Error> {
615        Number::from_xrpl_iou_value(buffer)
616    }
617}
618
619impl TryFrom<&Number> for XrplIouValue {
620    type Error = NumberError;
621
622    fn try_from(number: &Number) -> Result<Self, Self::Error> {
623        number.to_xrpl_iou_value()
624    }
625}
626
627impl TryFrom<Number> for XrplIouValue {
628    type Error = NumberError;
629
630    fn try_from(number: Number) -> Result<Self, Self::Error> {
631        number.to_xrpl_iou_value()
632    }
633}
634
635#[cfg(test)]
636mod tests {
637    use super::*;
638
639    #[test]
640    fn test_basic_arithmetic() {
641        let a = Number::from(100);
642        let b = Number::from(50);
643
644        let sum = (&a + &b).expect("Addition failed");
645        assert_eq!(sum.to_i64().expect("Conversion failed"), 150);
646
647        let diff = (&a - &b).expect("Subtraction failed");
648        assert_eq!(diff.to_i64().expect("Conversion failed"), 50);
649
650        let prod = (&a * &b).expect("Multiplication failed");
651        assert_eq!(prod.to_i64().expect("Conversion failed"), 5000);
652
653        let quot = (&a / &b).expect("Division failed");
654        assert_eq!(quot.to_i64().expect("Conversion failed"), 2);
655    }
656
657    #[test]
658    fn test_comparison() {
659        let a = Number::from(100);
660        let b = Number::from(50);
661        let c = Number::from(100);
662
663        assert!(a > b);
664        assert!(b < a);
665        assert!(a == c);
666        assert!(a >= c);
667        assert!(b <= a);
668    }
669
670    #[test]
671    fn test_display() {
672        let n = Number::from(12345);
673        let s = format!("{}", n);
674        assert!(!s.is_empty());
675    }
676
677    #[test]
678    fn test_mantissa_exponent() {
679        let n = Number::from_mantissa_exponent(12345, -2).expect("Failed to create number");
680        // The Number class normalizes values, so the actual mantissa/exponent may differ
681        // Let's just verify the value is correct when converted back
682        assert_eq!(n.to_i64().expect("Conversion failed"), 123);
683    }
684
685    #[test]
686    fn test_zero() {
687        let zero = Number::new();
688        assert!(zero.is_zero());
689        assert_eq!(zero.signum(), 0);
690    }
691
692    #[test]
693    fn test_mathematical_functions() {
694        let four = Number::from(4);
695        let sqrt_four = four.sqrt().expect("Square root failed");
696        assert_eq!(sqrt_four.to_i64().expect("Conversion failed"), 2);
697
698        let two = Number::from(2);
699        let eight = two.pow(3).expect("Power failed");
700        assert_eq!(eight.to_i64().expect("Conversion failed"), 8);
701    }
702
703    #[test]
704    fn test_xrpl_iou_value_zero() {
705        // Test the special case for zero: 0x8000000000000000
706        let zero_bytes = [0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
707        let number = Number::from_xrpl_iou_value(zero_bytes).expect("Failed to parse zero");
708        assert!(number.is_zero());
709        assert_eq!(number.signum(), 0);
710    }
711
712    #[test]
713    fn test_xrpl_iou_value_positive() {
714        // Test a positive value representing 1.0
715        // To get 1.0, we want: mantissa * 10^exponent = 1.0
716        // Using mantissa = 1000000000000000 (10^15) and exponent = -15: 10^15 * 10^(-15) = 1.0
717        // Type=1, Sign=1, Exp=97+(-15)=82=0x52, Mantissa=1000000000000000
718        // Let's construct this manually to be sure
719        let mantissa = 1_000_000_000_000_000u64; // 10^15
720        let exponent = 82u8; // 97 + (-15)
721
722        let mut value = 0u64;
723        value |= 1u64 << 63; // Type bit = 1
724        value |= 1u64 << 62; // Sign bit = 1 (positive)
725        value |= (exponent as u64) << 54; // Exponent in bits 54-61
726        value |= mantissa; // Mantissa in bits 0-53
727
728        let bytes = value.to_be_bytes();
729        let number = Number::from_xrpl_iou_value(bytes).expect("Failed to parse positive");
730
731        assert!(!number.is_zero());
732        assert_eq!(number.signum(), 1);
733
734        // The Number class normalizes values, so let's just check it's approximately 1
735        let string_val = number.to_string();
736        assert!(string_val.starts_with('1') || string_val == "1");
737    }
738
739    #[test]
740    fn test_xrpl_iou_value_negative() {
741        // Test a negative value representing -1.0
742        // Same as positive test but with sign bit = 0 (negative)
743        let mantissa = 1_000_000_000_000_000u64; // 10^15
744        let exponent = 82u8; // 97 + (-15)
745
746        let mut value = 0u64;
747        value |= 1u64 << 63; // Type bit = 1
748        value |= 0u64 << 62; // Sign bit = 0 (negative)
749        value |= (exponent as u64) << 54; // Exponent in bits 54-61
750        value |= mantissa; // Mantissa in bits 0-53
751
752        let bytes = value.to_be_bytes();
753        let number = Number::from_xrpl_iou_value(bytes).expect("Failed to parse negative");
754
755        assert!(!number.is_zero());
756        assert_eq!(number.signum(), -1);
757
758        // The Number class normalizes values, so let's just check it's approximately -1
759        let string_val = number.to_string();
760        assert!(string_val.starts_with("-1") || string_val == "-1");
761    }
762
763    #[test]
764    fn test_xrpl_iou_value_invalid_type() {
765        // Test invalid type bit (0 instead of 1)
766        let invalid_bytes = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01];
767        let result = Number::from_xrpl_iou_value(invalid_bytes);
768        assert!(matches!(result, Err(NumberError::InvalidArgument)));
769    }
770
771    #[test]
772    fn test_xrpl_iou_value_invalid_exponent() {
773        // Test exponent out of range (exponent=255, which is 255-97=158, > 80)
774        // Type=1, Sign=1, Exp=255, Mantissa=1000000000000000
775        let invalid_bytes = [0xFF, 0xC0, 0x6F, 0x7B, 0x5C, 0x00, 0x00, 0x00];
776        let result = Number::from_xrpl_iou_value(invalid_bytes);
777        assert!(matches!(result, Err(NumberError::InvalidArgument)));
778    }
779
780    #[test]
781    fn test_xrpl_iou_value_try_from() {
782        // Test the TryFrom implementation
783        let zero_bytes = [0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
784        let number: Number = zero_bytes.try_into().expect("TryFrom failed");
785        assert!(number.is_zero());
786    }
787
788    #[test]
789    fn test_xrpl_iou_value_large_mantissa() {
790        // Test with maximum mantissa (9999999999999999 = 0x2386F26FC0FFFF)
791        // Type=1, Sign=1, Exp=97+(-15)=82, Mantissa=9999999999999999
792        // This tests the upper bound of the normalized range
793        let max_mantissa = 9_999_999_999_999_999u64;
794        let mut value = 0u64;
795        value |= 1u64 << 63; // Type bit
796        value |= 1u64 << 62; // Sign bit
797        value |= 82u64 << 54; // Exponent
798        value |= max_mantissa; // Mantissa
799
800        let bytes = value.to_be_bytes();
801        let number = Number::from_xrpl_iou_value(bytes).expect("Failed to parse large mantissa");
802        assert!(!number.is_zero());
803        assert_eq!(number.signum(), 1);
804    }
805
806    #[test]
807    fn test_xrpl_iou_value_float_one_constant() {
808        // Test the FLOAT_ONE constant represents 1.0
809        let number = Number::from_xrpl_iou_value(FLOAT_ONE).expect("Failed to parse FLOAT_ONE");
810        assert!(!number.is_zero());
811        assert_eq!(number.signum(), 1);
812
813        // Should be exactly 1.0
814        let string_val = number.to_string();
815        assert_eq!(string_val, "1");
816
817        // Should convert to integer 1
818        assert_eq!(number.to_i64().expect("Conversion failed"), 1);
819    }
820
821    #[test]
822    fn test_xrpl_iou_value_float_negative_one_constant() {
823        // Test the FLOAT_NEGATIVE_ONE constant represents -1.0
824        let number = Number::from_xrpl_iou_value(FLOAT_NEGATIVE_ONE)
825            .expect("Failed to parse FLOAT_NEGATIVE_ONE");
826        assert!(!number.is_zero());
827        assert_eq!(number.signum(), -1);
828
829        // Should be exactly -1.0
830        let string_val = number.to_string();
831        assert_eq!(string_val, "-1");
832
833        // Should convert to integer -1
834        assert_eq!(number.to_i64().expect("Conversion failed"), -1);
835    }
836
837    #[test]
838    fn test_xrpl_iou_value_constants_arithmetic() {
839        // Test arithmetic operations with the constants
840        let one = Number::from_xrpl_iou_value(FLOAT_ONE).expect("Failed to parse FLOAT_ONE");
841        let neg_one = Number::from_xrpl_iou_value(FLOAT_NEGATIVE_ONE)
842            .expect("Failed to parse FLOAT_NEGATIVE_ONE");
843
844        // Test addition: 1 + (-1) = 0
845        let sum = (&one + &neg_one).expect("Addition failed");
846        assert!(sum.is_zero());
847
848        // Test subtraction: 1 - (-1) = 2
849        let diff = (&one - &neg_one).expect("Subtraction failed");
850        assert_eq!(diff.to_i64().expect("Conversion failed"), 2);
851
852        // Test multiplication: 1 * (-1) = -1
853        let prod = (&one * &neg_one).expect("Multiplication failed");
854        assert_eq!(prod.signum(), -1);
855        assert_eq!(prod.to_i64().expect("Conversion failed"), -1);
856
857        // Test that neg_one is the negation of one
858        let negated_one = (-&one).expect("Negation failed");
859        assert_eq!(negated_one.signum(), -1);
860        assert_eq!(negated_one, neg_one);
861    }
862
863    #[test]
864    fn test_xrpl_iou_value_constants_try_from() {
865        // Test TryFrom with the constants
866        let one: Number = FLOAT_ONE.try_into().expect("TryFrom FLOAT_ONE failed");
867        let neg_one: Number = FLOAT_NEGATIVE_ONE
868            .try_into()
869            .expect("TryFrom FLOAT_NEGATIVE_ONE failed");
870
871        assert_eq!(one.to_i64().expect("Conversion failed"), 1);
872        assert_eq!(neg_one.to_i64().expect("Conversion failed"), -1);
873    }
874
875    #[test]
876    fn test_to_xrpl_iou_value_zero() {
877        // Test converting zero to XRPL format
878        let zero = Number::new();
879        let bytes = zero.to_xrpl_iou_value().expect("Failed to convert zero");
880        assert_eq!(bytes, FLOAT_ZERO);
881
882        // Verify round-trip
883        let back_to_zero = Number::from_xrpl_iou_value(bytes).expect("Failed to parse back");
884        assert!(back_to_zero.is_zero());
885    }
886
887    #[test]
888    fn test_to_xrpl_iou_value_constants() {
889        // Test converting the constant values
890        let one = Number::from_xrpl_iou_value(FLOAT_ONE).expect("Failed to parse FLOAT_ONE");
891        let one_bytes = one.to_xrpl_iou_value().expect("Failed to convert one");
892        assert_eq!(one_bytes, FLOAT_ONE);
893
894        let neg_one = Number::from_xrpl_iou_value(FLOAT_NEGATIVE_ONE)
895            .expect("Failed to parse FLOAT_NEGATIVE_ONE");
896        let neg_one_bytes = neg_one
897            .to_xrpl_iou_value()
898            .expect("Failed to convert negative one");
899        assert_eq!(neg_one_bytes, FLOAT_NEGATIVE_ONE);
900    }
901
902    #[test]
903    fn test_to_xrpl_iou_value_round_trip() {
904        // Test round-trip conversion for various values
905        let test_cases = vec![
906            Number::from(123),
907            Number::from(-456),
908            Number::from(1_000_000),
909            Number::from(-999_999),
910        ];
911
912        for original in test_cases {
913            let bytes = original
914                .to_xrpl_iou_value()
915                .expect("Failed to convert to XRPL");
916            let converted =
917                Number::from_xrpl_iou_value(bytes).expect("Failed to convert from XRPL");
918
919            // Due to normalization in the Number class, we compare string representations
920            // or check that they're approximately equal
921            let orig_str = original.to_string();
922            let conv_str = converted.to_string();
923            assert_eq!(orig_str, conv_str, "Round-trip failed for {}", original);
924        }
925    }
926
927    #[test]
928    fn test_to_xrpl_iou_value_precision() {
929        // Test with high precision numbers
930        let precise = Number::from_mantissa_exponent(1234567890123456, -10)
931            .expect("Failed to create precise number");
932
933        let bytes = precise
934            .to_xrpl_iou_value()
935            .expect("Failed to convert precise number");
936        let back = Number::from_xrpl_iou_value(bytes).expect("Failed to parse back");
937
938        // Should maintain precision through round-trip
939        assert_eq!(precise.to_string(), back.to_string());
940    }
941
942    #[test]
943    fn test_try_from_implementations() {
944        // Test Number -> XrplIouValue TryFrom
945        let number = Number::from(123);
946        let bytes: XrplIouValue = (&number).try_into().expect("TryFrom reference failed");
947        let bytes_owned: XrplIouValue = number.clone().try_into().expect("TryFrom owned failed");
948
949        assert_eq!(bytes, bytes_owned);
950
951        // Test round-trip with TryFrom
952        let back: Number = bytes.try_into().expect("TryFrom back failed");
953        assert_eq!(number, back);
954    }
955}