ciborium/value/integer.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
// SPDX-License-Identifier: Apache-2.0
use core::cmp::Ordering;
macro_rules! implfrom {
($( $(#[$($attr:meta)+])? $t:ident)+) => {
$(
$(#[$($attr)+])?
impl From<$t> for Integer {
#[inline]
fn from(value: $t) -> Self {
Self(value as _)
}
}
impl TryFrom<Integer> for $t {
type Error = core::num::TryFromIntError;
#[inline]
fn try_from(value: Integer) -> Result<Self, Self::Error> {
$t::try_from(value.0)
}
}
)+
};
}
/// An abstract integer value
///
/// This opaque type represents an integer value which can be encoded in CBOR
/// without resulting to big integer encoding. Larger values may be encoded
/// using the big integer encoding as described in the CBOR RFC. See the
/// implementations for 128-bit integer conversions on `Value` for more
/// details.
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Integer(i128);
impl Integer {
/// Returns the canonical length this integer will have when serialized to bytes.
/// This is called `canonical` as it is only used for canonically comparing two
/// values. It shouldn't be used in any other context.
fn canonical_len(&self) -> usize {
let x = self.0;
if let Ok(x) = u8::try_from(x) {
if x < 24 {
1
} else {
2
}
} else if let Ok(x) = i8::try_from(x) {
if x >= -24i8 {
1
} else {
2
}
} else if u16::try_from(x).is_ok() || i16::try_from(x).is_ok() {
3
} else if u32::try_from(x).is_ok() || i32::try_from(x).is_ok() {
5
} else if u64::try_from(x).is_ok() || i64::try_from(x).is_ok() {
9
} else {
// Ciborium serializes u128/i128 as BigPos if they don't fit in 64 bits.
// In this special case we have to calculate the length.
// The Tag itself will always be 1 byte.
x.to_be_bytes().len() + 1
}
}
/// Compare two integers as if we were to serialize them, but more efficiently.
pub fn canonical_cmp(&self, other: &Self) -> Ordering {
match self.canonical_len().cmp(&other.canonical_len()) {
Ordering::Equal => {
// Negative numbers are higher in byte-order than positive numbers.
match (self.0.is_negative(), other.0.is_negative()) {
(false, true) => Ordering::Less,
(true, false) => Ordering::Greater,
(true, true) => {
// For negative numbers the byte order puts numbers closer to 0 which
// are lexically higher, lower. So -1 < -2 when sorting by be_bytes().
match self.0.cmp(&other.0) {
Ordering::Less => Ordering::Greater,
Ordering::Equal => Ordering::Equal,
Ordering::Greater => Ordering::Less,
}
}
(_, _) => self.0.cmp(&other.0),
}
}
x => x,
}
}
}
implfrom! {
u8 u16 u32 u64
i8 i16 i32 i64
#[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))]
usize
#[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))]
isize
}
impl TryFrom<i128> for Integer {
type Error = core::num::TryFromIntError;
#[inline]
fn try_from(value: i128) -> Result<Self, Self::Error> {
u64::try_from(match value.is_negative() {
false => value,
true => value ^ !0,
})?;
Ok(Integer(value))
}
}
impl TryFrom<u128> for Integer {
type Error = core::num::TryFromIntError;
#[inline]
fn try_from(value: u128) -> Result<Self, Self::Error> {
Ok(Self(u64::try_from(value)?.into()))
}
}
impl From<Integer> for i128 {
#[inline]
fn from(value: Integer) -> Self {
value.0
}
}
impl TryFrom<Integer> for u128 {
type Error = core::num::TryFromIntError;
#[inline]
fn try_from(value: Integer) -> Result<Self, Self::Error> {
u128::try_from(value.0)
}
}