Skip to main content

thruput_u256/
signed_fmt.rs

1//! [`I256`] formatting.
2//!
3//! Split the same way the unsigned side is: `Debug` is unconditional, because
4//! contract state structs `#[derive(Debug)]` over their fields and gating it
5//! would break guest builds, and everything that needs the decimal division
6//! loop sits behind `fmt`.
7//!
8//! `Debug` renders sign-magnitude hex (`-0x2a`), not the two's complement bits
9//! -- a diagnostic that prints `-1` as sixty-four `f`s is not one. `LowerHex`
10//! and `UpperHex` do print the raw bits, matching the primitives, where
11//! `format!("{:x}", -1i8)` is `"ff"`.
12
13use crate::I256;
14use crate::fmt::{HEX_DIGITS, LOWER, hex_digits};
15use core::fmt;
16
17#[cfg(feature = "fmt")]
18use crate::fmt::{DEC_DIGITS, UPPER, dec_digits};
19
20impl fmt::Debug for I256 {
21    /// Renders as sign-magnitude `0x`-prefixed hex.
22    ///
23    /// Like [`U256`](crate::U256)'s `Debug`, this does not honour width or
24    /// fill flags; use `{:x}` when the exact textual shape matters.
25    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26        let mut buf = [0u8; HEX_DIGITS];
27        if self.is_negative() {
28            f.write_str("-")?;
29        }
30        f.write_str("0x")?;
31        f.write_str(hex_digits(&self.unsigned_abs(), &mut buf, &LOWER))
32    }
33}
34
35#[cfg(feature = "fmt")]
36impl fmt::LowerHex for I256 {
37    /// The two's complement bits, as the primitives do it: `-1` is all `f`s,
38    /// not `-1`.
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        let mut buf = [0u8; HEX_DIGITS];
41        let digits = hex_digits(&self.cast_unsigned(), &mut buf, &LOWER);
42        f.pad_integral(true, "0x", digits)
43    }
44}
45
46#[cfg(feature = "fmt")]
47impl fmt::UpperHex for I256 {
48    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49        let mut buf = [0u8; HEX_DIGITS];
50        let digits = hex_digits(&self.cast_unsigned(), &mut buf, &UPPER);
51        // Lowercase `0x` even here: `format!("{:#X}", 255u8)` is `"0xFF"`.
52        f.pad_integral(true, "0x", digits)
53    }
54}
55
56#[cfg(feature = "fmt")]
57impl fmt::Display for I256 {
58    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59        let mut buf = [0u8; DEC_DIGITS];
60        let digits = dec_digits(&self.unsigned_abs(), &mut buf);
61        // `pad_integral` writes the sign itself, and honours `+`, width, fill
62        // and zero-padding the way it does for the primitives -- so
63        // `format!("{:+06}", -42)` lines up.
64        f.pad_integral(!self.is_negative(), "", digits)
65    }
66}
67
68/// Returned when a string cannot be parsed as an [`I256`].
69#[cfg(feature = "fmt")]
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub enum ParseI256Error {
72    /// The input was empty.
73    Empty,
74    /// The input contained a character that is neither a leading sign nor a
75    /// decimal digit -- including a sign with no digits after it.
76    InvalidDigit,
77    /// The value does not fit in a signed 256-bit integer.
78    Overflow,
79}
80
81#[cfg(feature = "fmt")]
82impl fmt::Display for ParseI256Error {
83    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84        f.write_str(match self {
85            Self::Empty => "cannot parse an empty string as I256",
86            Self::InvalidDigit => "invalid decimal digit",
87            Self::Overflow => "value does not fit in a signed 256-bit integer",
88        })
89    }
90}
91
92#[cfg(all(feature = "fmt", feature = "std"))]
93impl std::error::Error for ParseI256Error {}
94
95#[cfg(feature = "fmt")]
96impl core::str::FromStr for I256 {
97    type Err = ParseI256Error;
98
99    fn from_str(s: &str) -> Result<Self, Self::Err> {
100        use crate::{ParseU256Error, U256};
101
102        let (negative, digits) = match s.as_bytes().first() {
103            None => return Err(ParseI256Error::Empty),
104            Some(b'-') => (true, &s[1..]),
105            Some(b'+') => (false, &s[1..]),
106            _ => (false, s),
107        };
108        // A bare sign is an invalid digit, not an empty string -- the same
109        // answer `"-".parse::<i32>()` gives.
110        if digits.is_empty() {
111            return Err(ParseI256Error::InvalidDigit);
112        }
113
114        // The magnitude goes through the unsigned parser rather than a second
115        // copy of its accumulation loop. Two copies of an overflow-sensitive
116        // parse is one too many: a fix to either would silently leave the
117        // other behind, and a guest linking both would pay for both.
118        let magnitude = U256::from_str(digits).map_err(|e| match e {
119            // Unreachable -- `digits` is non-empty above -- but mapped rather
120            // than unwrapped so a change to either parser cannot panic here.
121            ParseU256Error::Empty => ParseI256Error::Empty,
122            ParseU256Error::InvalidDigit => ParseI256Error::InvalidDigit,
123            ParseU256Error::Overflow => ParseI256Error::Overflow,
124        })?;
125
126        // The negative range reaches one further, so the bound is on the
127        // magnitude rather than on the value it would become. Note this is a
128        // *second* bound: the unsigned parser has already rejected anything
129        // past `U256::MAX`, and this narrows what is left to the signed range.
130        let limit = if negative {
131            U256::ONE.wrapping_shl(255)
132        } else {
133            Self::MAX.cast_unsigned()
134        };
135        if magnitude > limit {
136            return Err(ParseI256Error::Overflow);
137        }
138
139        Ok(if negative {
140            magnitude.wrapping_neg().cast_signed()
141        } else {
142            magnitude.cast_signed()
143        })
144    }
145}