Skip to main content

cbor_core/ext/
num_bigint.rs

1use num_bigint::{BigInt, BigUint, Sign};
2
3use crate::{
4    Error, Result, Value, tag,
5    util::{trim_leading_zeros, u64_from_slice},
6};
7
8// ---------------------------------------------------------------------------
9// BigUint → Value
10// ---------------------------------------------------------------------------
11
12impl From<BigUint> for Value {
13    /// Encodes a `BigUint` as a CBOR integer.
14    ///
15    /// Values that fit in a `u64` are encoded as a plain unsigned integer.
16    /// Larger values are encoded as a tag-2 big integer
17    fn from(value: BigUint) -> Self {
18        from_big_uint(&value)
19    }
20}
21
22impl From<&BigUint> for Value {
23    /// Encodes a `BigUint` as a CBOR integer.
24    ///
25    /// Values that fit in a `u64` are encoded as a plain unsigned integer.
26    /// Larger values are encoded as a tag-2 big integer
27    fn from(value: &BigUint) -> Self {
28        from_big_uint(value)
29    }
30}
31
32fn from_big_uint(value: &BigUint) -> Value {
33    let bytes = value.to_bytes_be();
34    let trimmed = trim_leading_zeros(&bytes);
35
36    if let Ok(number) = u64_from_slice(trimmed) {
37        Value::Unsigned(number)
38    } else if bytes.len() == trimmed.len() {
39        Value::tag(tag::POS_BIG_INT, bytes) // reuse Vec
40    } else {
41        Value::tag(tag::POS_BIG_INT, trimmed) // implicit new Vec
42    }
43}
44
45// ---------------------------------------------------------------------------
46// BigInt → Value
47// ---------------------------------------------------------------------------
48
49impl From<BigInt> for Value {
50    /// Encodes a `BigInt` as a CBOR integer.
51    fn from(value: BigInt) -> Self {
52        from_big_int(&value)
53    }
54}
55
56impl From<&BigInt> for Value {
57    /// Encodes a `BigInt` as a CBOR integer.
58    fn from(value: &BigInt) -> Self {
59        from_big_int(value)
60    }
61}
62
63fn from_big_int(value: &BigInt) -> Value {
64    let magnitude = value.magnitude();
65    match value.sign() {
66        Sign::NoSign | Sign::Plus => magnitude.into(),
67        Sign::Minus => {
68            let bytes = (magnitude - 1_u32).to_bytes_be();
69            let trimmed = trim_leading_zeros(&bytes);
70
71            if let Ok(number) = u64_from_slice(trimmed) {
72                Value::Negative(number)
73            } else if bytes.len() == trimmed.len() {
74                Value::tag(tag::NEG_BIG_INT, bytes) // reuse Vec
75            } else {
76                Value::tag(tag::NEG_BIG_INT, trimmed) // implicit new Vec
77            }
78        }
79    }
80}
81
82// ---------------------------------------------------------------------------
83// Value → BigUint
84// ---------------------------------------------------------------------------
85
86impl TryFrom<Value> for BigUint {
87    type Error = Error;
88
89    /// Extracts a `BigUint` from a CBOR integer value.
90    ///
91    /// Returns `Err(NegativeUnsigned)` for negative integers,
92    /// `Err(IncompatibleType)` for non-integer values.
93    fn try_from(value: Value) -> Result<Self> {
94        to_num_biguint(&value)
95    }
96}
97
98impl TryFrom<&Value> for BigUint {
99    type Error = Error;
100
101    /// Extracts a `BigUint` from a CBOR integer value.
102    ///
103    /// Returns `Err(NegativeUnsigned)` for negative integers,
104    /// `Err(IncompatibleType)` for non-integer values.
105    fn try_from(value: &Value) -> Result<Self> {
106        to_num_biguint(value)
107    }
108}
109
110fn to_num_biguint(value: &Value) -> Result<BigUint> {
111    match value.as_integer_bytes()? {
112        crate::integer::IntegerBytes::UnsignedOwned(bytes) => Ok(BigUint::from(u64::from_be_bytes(bytes))),
113        crate::integer::IntegerBytes::NegativeOwned(_) => Err(Error::NegativeUnsigned),
114        crate::integer::IntegerBytes::UnsignedBorrowed(bytes) => Ok(BigUint::from_bytes_be(bytes)),
115        crate::integer::IntegerBytes::NegativeBorrowed(_) => Err(Error::NegativeUnsigned),
116    }
117}
118
119// ---------------------------------------------------------------------------
120// Value → BigInt
121// ---------------------------------------------------------------------------
122
123impl TryFrom<Value> for BigInt {
124    type Error = Error;
125
126    /// Extracts a `BigInt` from a CBOR integer value.
127    ///
128    /// Returns `Err(IncompatibleType)` for non-integer values.
129    fn try_from(value: Value) -> Result<Self> {
130        to_num_bigint(&value)
131    }
132}
133
134impl TryFrom<&Value> for BigInt {
135    type Error = Error;
136
137    /// Extracts a `BigInt` from a CBOR integer value.
138    ///
139    /// Returns `Err(IncompatibleType)` for non-integer values.
140    fn try_from(value: &Value) -> Result<Self> {
141        to_num_bigint(value)
142    }
143}
144
145fn to_num_bigint(value: &Value) -> Result<BigInt> {
146    match value.as_integer_bytes()? {
147        crate::integer::IntegerBytes::UnsignedOwned(bytes) => Ok(BigUint::from_bytes_be(&bytes).into()),
148        crate::integer::IntegerBytes::NegativeOwned(bytes) => Ok(BigInt::from(!u64::from_be_bytes(bytes) as i64)),
149        crate::integer::IntegerBytes::UnsignedBorrowed(bytes) => Ok(BigUint::from_bytes_be(bytes).into()),
150        crate::integer::IntegerBytes::NegativeBorrowed(bytes) => {
151            // payload = magnitude - 1, so actual = -(payload + 1)
152            let payload = BigUint::from_bytes_be(bytes);
153            Ok(-(BigInt::from(payload) + 1_i32))
154        }
155    }
156}
157
158// ---------------------------------------------------------------------------
159// Tests
160// ---------------------------------------------------------------------------
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165    use crate::DataType;
166
167    fn roundtrip_biguint(n: BigUint) -> BigUint {
168        let encoded = Value::from(n).encode();
169        let decoded = Value::decode(encoded).unwrap();
170        BigUint::try_from(decoded).unwrap()
171    }
172
173    fn roundtrip_bigint(n: BigInt) -> BigInt {
174        BigInt::try_from(Value::from(n)).unwrap()
175    }
176
177    #[test]
178    fn biguint_zero() {
179        assert_eq!(roundtrip_biguint(BigUint::ZERO), BigUint::ZERO);
180    }
181
182    #[test]
183    fn biguint_small() {
184        let n = BigUint::from(42_u32);
185        assert_eq!(roundtrip_biguint(n.clone()), n);
186    }
187
188    #[test]
189    fn biguint_u64_max() {
190        let n = BigUint::from(u64::MAX);
191        let v = Value::from(n.clone());
192        assert!(
193            matches!(v, Value::Unsigned(_)),
194            "u64::MAX should encode as plain Unsigned"
195        );
196        assert_eq!(BigUint::try_from(v).unwrap(), n);
197    }
198
199    #[test]
200    fn biguint_u128_max() {
201        let n = BigUint::from(u128::MAX);
202        let v = Value::from(n.clone());
203        assert!(
204            matches!(&v, Value::Tag(2, _)),
205            "u128::MAX should encode as tag-2 bigint"
206        );
207        assert_eq!(BigUint::try_from(v).unwrap(), n);
208    }
209
210    #[test]
211    fn biguint_from_u128_roundtrip() {
212        for x in [0_u128, 1, 42, u64::MAX as u128, u64::MAX as u128 + 1, u128::MAX] {
213            let expected = BigUint::from(x);
214            let via_value = Value::from(x); // existing From<u128>
215            assert_eq!(BigUint::try_from(via_value).unwrap(), expected, "u128={x}");
216        }
217    }
218
219    #[test]
220    fn biguint_negative_value_errors() {
221        let v = Value::from(-1);
222        assert_eq!(BigUint::try_from(v), Err(Error::NegativeUnsigned));
223
224        let v = Value::from(i128::MIN);
225        assert_eq!(BigUint::try_from(v), Err(Error::NegativeUnsigned));
226    }
227
228    #[test]
229    fn biguint_non_integer_errors() {
230        assert_eq!(
231            BigUint::try_from(Value::from("hello")),
232            Err(Error::IncompatibleType(DataType::Text))
233        );
234        assert_eq!(
235            BigUint::try_from(Value::null()),
236            Err(Error::IncompatibleType(DataType::Null))
237        );
238    }
239
240    #[test]
241    fn bigint_zero() {
242        assert_eq!(roundtrip_bigint(BigInt::ZERO), BigInt::ZERO);
243    }
244
245    #[test]
246    fn bigint_positive_small() {
247        let n = BigInt::from(42);
248        assert_eq!(roundtrip_bigint(n.clone()), n);
249    }
250
251    #[test]
252    fn bigint_negative_one() {
253        let n = BigInt::from(-1);
254        assert_eq!(roundtrip_bigint(n.clone()), n);
255    }
256
257    #[test]
258    fn bigint_i64_min() {
259        let n = BigInt::from(i64::MIN);
260        assert_eq!(roundtrip_bigint(n.clone()), n);
261    }
262
263    #[test]
264    fn bigint_u128_max() {
265        let n = BigInt::from(u128::MAX);
266        let v = Value::from(n.clone());
267        assert!(matches!(&v, Value::Tag(2, _)));
268        assert_eq!(BigInt::try_from(v).unwrap(), n);
269    }
270
271    #[test]
272    fn bigint_i128_min() {
273        let n = BigInt::from(i128::MIN);
274        let v = Value::from(n.clone());
275        assert!(matches!(&v, Value::Tag(3, _)));
276        assert_eq!(BigInt::try_from(v).unwrap(), n);
277    }
278
279    #[test]
280    fn bigint_from_u128_roundtrip() {
281        for x in [0_u128, 1, 42, u64::MAX as u128, u64::MAX as u128 + 1, u128::MAX] {
282            let expected = BigInt::from(x);
283            let via_value = Value::from(x);
284            assert_eq!(BigInt::try_from(via_value).unwrap(), expected, "u128={x}");
285        }
286    }
287
288    #[test]
289    fn bigint_from_i128_roundtrip() {
290        for x in [
291            0,
292            1,
293            -1,
294            42,
295            -42,
296            i64::MIN as i128,
297            i64::MAX as i128,
298            i128::MIN,
299            i128::MAX,
300        ] {
301            let expected = BigInt::from(x);
302            let via_value = Value::from(x);
303            assert_eq!(BigInt::try_from(via_value).unwrap(), expected, "i128={x}");
304        }
305    }
306
307    #[test]
308    fn bigint_non_integer_errors() {
309        assert_eq!(
310            BigInt::try_from(Value::from(0.5)),
311            Err(Error::IncompatibleType(DataType::Float16))
312        );
313        assert_eq!(
314            BigInt::try_from(Value::null()),
315            Err(Error::IncompatibleType(DataType::Null))
316        );
317    }
318
319    // ---- Cross-type consistency ----
320
321    #[test]
322    fn bigint_and_biguint_agree_on_positives() {
323        for x in [0u128, 1, u64::MAX as u128, u128::MAX] {
324            let vu = Value::from(BigUint::from(x));
325            let vi = Value::from(BigInt::from(x));
326            assert_eq!(vu, vi, "BigUint/BigInt encoding differs for {x}");
327        }
328    }
329}