cbor_data/value/
number.rs

1use self::Number::*;
2use crate::{constants::*, Encoder, ItemKind, TaggedItem, Writer};
3use std::{borrow::Cow, marker::PhantomData};
4
5/// Representation of a number extracted from a CBOR item
6#[derive(Debug, Clone, PartialEq)]
7pub enum Number<'a> {
8    /// an integer number from major types 0 or 1
9    Int(i128),
10    /// a floating-point number from major type 7
11    IEEE754(f64),
12    /// a big integer or big decimal with value `mantissa * 10.pow(exponent)`
13    Decimal(Exponential<'a, Ten>),
14    /// a big integer or big decimal with value `mantissa * 2.pow(exponent)`
15    Float(Exponential<'a, Two>),
16}
17
18impl<'a> Number<'a> {
19    /// Interpret the given item as per [RFC8949 ยง3.4.4](https://www.rfc-editor.org/rfc/rfc8949.html#section-3.4.4)
20    pub(crate) fn from_bignum(item: TaggedItem<'a>) -> Option<Self> {
21        let tag = item.tags().single()?;
22        let (exp, mant) = if let ItemKind::Array(mut a) = item.kind() {
23            if let (Some(e), Some(m), None) = (a.next(), a.next(), a.next()) {
24                (e, m)
25            } else {
26                return None;
27            }
28        } else {
29            return None;
30        };
31        let exponent = match exp.kind() {
32            ItemKind::Pos(x) => i128::from(x),
33            ItemKind::Neg(x) => -1 - i128::from(x),
34            _ => return None,
35        };
36        if !matches!(
37            mant.kind(),
38            ItemKind::Pos(_) | ItemKind::Neg(_) | ItemKind::Bytes(_)
39        ) {
40            // This check ensures that Decimal(e) below has exponent == 0
41            return None;
42        }
43        if let super::CborValue::Number(n) = mant.decode() {
44            match n {
45                Int(mut n) => {
46                    let inverted = n < 0;
47                    if inverted {
48                        n = -1 - n;
49                    }
50                    let start = n.leading_zeros() as usize / 8;
51                    let bytes = n.to_be_bytes();
52                    if tag == TAG_BIGDECIMAL {
53                        Some(Decimal(Exponential {
54                            exponent,
55                            mantissa: Cow::Owned(bytes[start..].to_vec()),
56                            inverted,
57                            _ph: PhantomData,
58                        }))
59                    } else {
60                        Some(Float(Exponential {
61                            exponent,
62                            mantissa: Cow::Owned(bytes[start..].to_vec()),
63                            inverted,
64                            _ph: PhantomData,
65                        }))
66                    }
67                }
68                Decimal(e) => {
69                    if tag == TAG_BIGDECIMAL {
70                        Some(Decimal(e.with_exponent(exponent)))
71                    } else {
72                        Some(Float(e.with_exponent(exponent)))
73                    }
74                }
75                _ => None,
76            }
77        } else {
78            None
79        }
80    }
81
82    pub(crate) fn encode<E: Encoder>(&self, encoder: E) -> E::Output {
83        match self {
84            Number::Int(i) => {
85                let i = *i;
86                if i >= 0 {
87                    if i <= (u64::MAX as i128) {
88                        encoder.write_pos(i as u64, None)
89                    } else {
90                        let bytes = i.to_be_bytes();
91                        let first = bytes
92                            .iter()
93                            .enumerate()
94                            .find_map(|(idx, byte)| if *byte != 0 { Some(idx) } else { None })
95                            .unwrap();
96                        encoder.write_bytes(&bytes[first..], [TAG_BIGNUM_POS])
97                    }
98                } else if i >= -(u64::MAX as i128 + 1) {
99                    encoder.write_neg((-1 - i) as u64, None)
100                } else {
101                    let bytes = (-1 - i).to_be_bytes();
102                    let first = bytes
103                        .iter()
104                        .enumerate()
105                        .find_map(|(idx, byte)| if *byte != 0 { Some(idx) } else { None })
106                        .unwrap();
107                    encoder.write_bytes(&bytes[first..], [TAG_BIGNUM_NEG])
108                }
109            }
110            Number::IEEE754(f) => encoder.encode_f64(*f),
111            Number::Decimal(d) => encode_big(d, encoder),
112            Number::Float(d) => encode_big(d, encoder),
113        }
114    }
115
116    /// Cut ties with possibly referenced byte slices, allocating if necessary
117    pub fn make_static(self) -> Number<'static> {
118        match self {
119            Int(i) => Int(i),
120            IEEE754(f) => IEEE754(f),
121            Decimal(e) => Decimal(e.make_static()),
122            Float(e) => Float(e.make_static()),
123        }
124    }
125
126    pub fn get_type(&self) -> &'static str {
127        match self {
128            Int(_) => "small integer",
129            IEEE754(_) => "small float",
130            Decimal(_) => "big decimal",
131            Float(_) => "big float",
132        }
133    }
134}
135
136/// A representation of a bignum
137///
138/// The base is statically known while the exponent is dynamic. The mantissa is not guaranteed
139/// to have optimal encoding, i.e. it can have leading zero bytes.
140///
141/// The represented value is `m * base.pow(exponent)`, where
142///
143///  - `m = mantissa` for `inverted == false`, and
144///  - `m = -1 - mantissa` for `inverted = true`.
145#[derive(Debug, Clone, PartialEq, Hash)]
146pub struct Exponential<'a, B: Base> {
147    exponent: i128,
148    /// mantissa in big-endian format
149    mantissa: Cow<'a, [u8]>,
150    /// if this is true, then the mantissa bytes represent `-1 - mantissa`
151    inverted: bool,
152    _ph: PhantomData<B>,
153}
154
155impl<'a> Exponential<'a, Ten> {
156    /// Interpret as a bignum assuming this is an appropriately tagged byte string
157    pub(crate) fn from_bytes(item: TaggedItem<'a>) -> Option<Self> {
158        let tag = item.tags().single()?;
159        if let ItemKind::Bytes(bytes) = item.kind() {
160            Some(Exponential {
161                exponent: 0,
162                mantissa: bytes.as_cow(),
163                inverted: tag == TAG_BIGNUM_NEG,
164                _ph: PhantomData,
165            })
166        } else {
167            None
168        }
169    }
170
171    fn with_exponent<BB: Base>(self, exponent: i128) -> Exponential<'a, BB> {
172        Exponential {
173            exponent,
174            mantissa: self.mantissa,
175            inverted: self.inverted,
176            _ph: PhantomData,
177        }
178    }
179}
180
181impl<'a, B: Base> Exponential<'a, B> {
182    pub fn new(exponent: i128, mantissa: Cow<'a, [u8]>, inverted: bool) -> Self {
183        Self {
184            exponent,
185            mantissa,
186            inverted,
187            _ph: PhantomData,
188        }
189    }
190
191    /// Cut ties with possibly referenced byte slices, allocating if necessary
192    pub fn make_static(self) -> Exponential<'static, B> {
193        Exponential {
194            exponent: self.exponent,
195            mantissa: super::ms(self.mantissa),
196            inverted: self.inverted,
197            _ph: PhantomData,
198        }
199    }
200
201    /// Get a reference to the exponential's exponent.
202    pub fn exponent(&self) -> i128 {
203        self.exponent
204    }
205
206    /// Get a reference to the exponential's mantissa.
207    pub fn mantissa(&self) -> &[u8] {
208        self.mantissa.as_ref()
209    }
210
211    /// Get a reference to the exponential's inverted.
212    pub fn inverted(&self) -> bool {
213        self.inverted
214    }
215}
216
217pub trait Base {
218    const BASE: u64;
219    const TAG: u64;
220}
221#[derive(Debug, Clone, PartialEq)]
222pub struct Two;
223impl Base for Two {
224    const BASE: u64 = 2;
225    const TAG: u64 = TAG_BIGFLOAT;
226}
227#[derive(Debug, Clone, PartialEq)]
228pub struct Ten;
229impl Base for Ten {
230    const BASE: u64 = 10;
231    const TAG: u64 = TAG_BIGDECIMAL;
232}
233
234fn encode_big<B: Base, E: Encoder>(d: &Exponential<B>, encoder: E) -> E::Output {
235    let first = d
236        .mantissa()
237        .iter()
238        .enumerate()
239        .find_map(|(idx, byte)| if *byte != 0 { Some(idx) } else { None })
240        .unwrap_or_else(|| d.mantissa().len());
241    let bytes = &d.mantissa()[first..];
242    if bytes.len() <= 8 {
243        let mut be_bytes = [0u8; 8];
244        be_bytes[8 - bytes.len()..].copy_from_slice(bytes);
245        let num = u64::from_be_bytes(be_bytes);
246        if d.exponent() == 0 {
247            if d.inverted() {
248                encoder.write_neg(num, None)
249            } else {
250                encoder.write_pos(num, None)
251            }
252        } else {
253            encoder.write_array([B::TAG], |builder| {
254                let exp = d.exponent();
255                if exp >= 0 {
256                    builder.write_pos(exp as u64, None);
257                } else {
258                    builder.write_neg((-1 - exp) as u64, None);
259                }
260                if d.inverted() {
261                    builder.write_neg(num, None);
262                } else {
263                    builder.write_pos(num, None);
264                }
265            })
266        }
267    } else if d.exponent() == 0 {
268        if d.inverted() {
269            encoder.write_bytes(bytes, [TAG_BIGNUM_NEG])
270        } else {
271            encoder.write_bytes(bytes, [TAG_BIGNUM_POS])
272        }
273    } else {
274        encoder.write_array([B::TAG], |builder| {
275            let exp = d.exponent();
276            if exp >= 0 {
277                builder.write_pos(exp as u64, None);
278            } else {
279                builder.write_neg((-1 - exp) as u64, None);
280            }
281            if d.inverted() {
282                builder.write_bytes(bytes, [TAG_BIGNUM_NEG]);
283            } else {
284                builder.write_bytes(bytes, [TAG_BIGNUM_POS]);
285            }
286        })
287    }
288}
289
290#[cfg(test)]
291mod tests {
292    use super::*;
293    use crate::{tests::hex, CborBuilder};
294
295    #[test]
296    fn encode() {
297        fn e(n: Number) -> String {
298            CborBuilder::new().encode_number(&n).to_string()
299        }
300        fn d(exp: i128, mant: &str, inv: bool) -> String {
301            e(Decimal(Exponential::new(exp, hex(mant).into(), inv)))
302        }
303        fn f(exp: i128, mant: &str, inv: bool) -> String {
304            e(Float(Exponential::new(exp, hex(mant).into(), inv)))
305        }
306
307        assert_eq!(e(Int(0)), "0");
308        assert_eq!(e(Int(1)), "1");
309        assert_eq!(e(Int(-1)), "-1");
310        assert_eq!(e(Int(u64::MAX.into())), "18446744073709551615");
311        assert_eq!(e(Int(-1 - u64::MAX as i128)), "-18446744073709551616");
312        assert_eq!(e(Int(u64::MAX as i128 + 1)), "2(h'010000000000000000')");
313        assert_eq!(e(Int(-2 - u64::MAX as i128)), "3(h'010000000000000000')");
314
315        assert_eq!(e(IEEE754(-0.0)), "-0.0");
316        assert_eq!(e(IEEE754(1.3e34)), "1.3e34");
317
318        assert_eq!(d(0, "", false), "0");
319        assert_eq!(d(0, "", true), "-1");
320        assert_eq!(d(0, "01", false), "1");
321        assert_eq!(d(0, "01", true), "-2");
322        assert_eq!(d(0, "ffffffffffffffff", false), "18446744073709551615");
323        assert_eq!(d(0, "ffffffffffffffff", true), "-18446744073709551616");
324        assert_eq!(
325            d(0, "010203040506070809", false),
326            "2(h'010203040506070809')"
327        );
328        assert_eq!(d(0, "010203040506070809", true), "3(h'010203040506070809')");
329        assert_eq!(d(1, "", false), "4([1, 0])");
330        assert_eq!(d(1, "", true), "4([1, -1])");
331        assert_eq!(d(1, "01", false), "4([1, 1])");
332        assert_eq!(d(1, "01", true), "4([1, -2])");
333        assert_eq!(
334            d(1, "ffffffffffffffff", false),
335            "4([1, 18446744073709551615])"
336        );
337        assert_eq!(
338            d(1, "ffffffffffffffff", true),
339            "4([1, -18446744073709551616])"
340        );
341        assert_eq!(
342            d(1, "010203040506070809", false),
343            "4([1, 2(h'010203040506070809')])"
344        );
345        assert_eq!(
346            d(1, "010203040506070809", true),
347            "4([1, 3(h'010203040506070809')])"
348        );
349        assert_eq!(d(-1, "", false), "4([-1, 0])");
350        assert_eq!(d(-1, "", true), "4([-1, -1])");
351        assert_eq!(d(-1, "01", false), "4([-1, 1])");
352        assert_eq!(d(-1, "01", true), "4([-1, -2])");
353        assert_eq!(
354            d(-1, "ffffffffffffffff", false),
355            "4([-1, 18446744073709551615])"
356        );
357        assert_eq!(
358            d(-1, "ffffffffffffffff", true),
359            "4([-1, -18446744073709551616])"
360        );
361        assert_eq!(
362            d(-1, "010203040506070809", false),
363            "4([-1, 2(h'010203040506070809')])"
364        );
365        assert_eq!(
366            d(-1, "010203040506070809", true),
367            "4([-1, 3(h'010203040506070809')])"
368        );
369
370        assert_eq!(f(0, "", false), "0");
371        assert_eq!(f(0, "", true), "-1");
372        assert_eq!(f(0, "01", false), "1");
373        assert_eq!(f(0, "01", true), "-2");
374        assert_eq!(f(0, "ffffffffffffffff", false), "18446744073709551615");
375        assert_eq!(f(0, "ffffffffffffffff", true), "-18446744073709551616");
376        assert_eq!(
377            f(0, "010203040506070809", false),
378            "2(h'010203040506070809')"
379        );
380        assert_eq!(f(0, "010203040506070809", true), "3(h'010203040506070809')");
381        assert_eq!(f(1, "", false), "5([1, 0])");
382        assert_eq!(f(1, "", true), "5([1, -1])");
383        assert_eq!(f(1, "01", false), "5([1, 1])");
384        assert_eq!(f(1, "01", true), "5([1, -2])");
385        assert_eq!(
386            f(1, "ffffffffffffffff", false),
387            "5([1, 18446744073709551615])"
388        );
389        assert_eq!(
390            f(1, "ffffffffffffffff", true),
391            "5([1, -18446744073709551616])"
392        );
393        assert_eq!(
394            f(1, "010203040506070809", false),
395            "5([1, 2(h'010203040506070809')])"
396        );
397        assert_eq!(
398            f(1, "010203040506070809", true),
399            "5([1, 3(h'010203040506070809')])"
400        );
401        assert_eq!(f(-1, "", false), "5([-1, 0])");
402        assert_eq!(f(-1, "", true), "5([-1, -1])");
403        assert_eq!(f(-1, "01", false), "5([-1, 1])");
404        assert_eq!(f(-1, "01", true), "5([-1, -2])");
405        assert_eq!(
406            f(-1, "ffffffffffffffff", false),
407            "5([-1, 18446744073709551615])"
408        );
409        assert_eq!(
410            f(-1, "ffffffffffffffff", true),
411            "5([-1, -18446744073709551616])"
412        );
413        assert_eq!(
414            f(-1, "010203040506070809", false),
415            "5([-1, 2(h'010203040506070809')])"
416        );
417        assert_eq!(
418            f(-1, "010203040506070809", true),
419            "5([-1, 3(h'010203040506070809')])"
420        );
421    }
422}