Skip to main content

cbor_core/ext/
num_bigint.rs

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