use crate::error::{TryFromNumberError, TryFromNumberErrorKind};
#[cfg(all(
aws_sdk_unstable,
any(feature = "serde-serialize", feature = "serde-deserialize")
))]
use serde;
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(
all(aws_sdk_unstable, feature = "serde-deserialize"),
derive(serde::Deserialize)
)]
#[cfg_attr(
all(aws_sdk_unstable, feature = "serde-serialize"),
derive(serde::Serialize)
)]
#[cfg_attr(
any(
all(aws_sdk_unstable, feature = "serde-deserialize"),
all(aws_sdk_unstable, feature = "serde-serialize")
),
serde(untagged)
)]
pub enum Number {
PosInt(u64),
NegInt(i64),
Float(f64),
}
impl Number {
pub fn to_f64_lossy(self) -> f64 {
match self {
Number::PosInt(v) => v as f64,
Number::NegInt(v) => v as f64,
Number::Float(v) => v,
}
}
pub fn to_f32_lossy(self) -> f32 {
match self {
Number::PosInt(v) => v as f32,
Number::NegInt(v) => v as f32,
Number::Float(v) => v as f32,
}
}
}
macro_rules! to_unsigned_integer_converter {
($typ:ident, $styp:expr) => {
#[doc = "Converts to a `"]
#[doc = $styp]
#[doc = "`. This conversion fails if it is lossy."]
impl TryFrom<Number> for $typ {
type Error = TryFromNumberError;
fn try_from(value: Number) -> Result<Self, Self::Error> {
match value {
Number::PosInt(v) => Ok(Self::try_from(v)?),
Number::NegInt(v) => {
Err(TryFromNumberErrorKind::NegativeToUnsignedLossyConversion(v).into())
}
Number::Float(v) => attempt_lossless!(v, $typ),
}
}
}
};
($typ:ident) => {
to_unsigned_integer_converter!($typ, stringify!($typ));
};
}
macro_rules! to_signed_integer_converter {
($typ:ident, $styp:expr) => {
#[doc = "Converts to a `"]
#[doc = $styp]
#[doc = "`. This conversion fails if it is lossy."]
impl TryFrom<Number> for $typ {
type Error = TryFromNumberError;
fn try_from(value: Number) -> Result<Self, Self::Error> {
match value {
Number::PosInt(v) => Ok(Self::try_from(v)?),
Number::NegInt(v) => Ok(Self::try_from(v)?),
Number::Float(v) => attempt_lossless!(v, $typ),
}
}
}
};
($typ:ident) => {
to_signed_integer_converter!($typ, stringify!($typ));
};
}
macro_rules! attempt_lossless {
($value: expr, $typ: ty) => {{
let converted = $value as $typ;
if (converted as f64 == $value) {
Ok(converted)
} else {
Err(TryFromNumberErrorKind::FloatToIntegerLossyConversion($value).into())
}
}};
}
impl TryFrom<Number> for u64 {
type Error = TryFromNumberError;
fn try_from(value: Number) -> Result<Self, Self::Error> {
match value {
Number::PosInt(v) => Ok(v),
Number::NegInt(v) => {
Err(TryFromNumberErrorKind::NegativeToUnsignedLossyConversion(v).into())
}
Number::Float(v) => attempt_lossless!(v, u64),
}
}
}
to_unsigned_integer_converter!(u32);
to_unsigned_integer_converter!(u16);
to_unsigned_integer_converter!(u8);
impl TryFrom<Number> for i64 {
type Error = TryFromNumberError;
fn try_from(value: Number) -> Result<Self, Self::Error> {
match value {
Number::PosInt(v) => Ok(Self::try_from(v)?),
Number::NegInt(v) => Ok(v),
Number::Float(v) => attempt_lossless!(v, i64),
}
}
}
to_signed_integer_converter!(i32);
to_signed_integer_converter!(i16);
to_signed_integer_converter!(i8);
impl TryFrom<Number> for f64 {
type Error = TryFromNumberError;
fn try_from(value: Number) -> Result<Self, Self::Error> {
match value {
Number::PosInt(v) => {
if v <= (1 << 53) {
Ok(v as Self)
} else {
Err(TryFromNumberErrorKind::U64ToFloatLossyConversion(v).into())
}
}
Number::NegInt(v) => {
if (-(1 << 53)..=(1 << 53)).contains(&v) {
Ok(v as Self)
} else {
Err(TryFromNumberErrorKind::I64ToFloatLossyConversion(v).into())
}
}
Number::Float(v) => Ok(v),
}
}
}
impl TryFrom<Number> for f32 {
type Error = TryFromNumberError;
fn try_from(value: Number) -> Result<Self, Self::Error> {
match value {
Number::PosInt(v) => {
if v <= (1 << 24) {
Ok(v as Self)
} else {
Err(TryFromNumberErrorKind::U64ToFloatLossyConversion(v).into())
}
}
Number::NegInt(v) => {
if (-(1 << 24)..=(1 << 24)).contains(&v) {
Ok(v as Self)
} else {
Err(TryFromNumberErrorKind::I64ToFloatLossyConversion(v).into())
}
}
Number::Float(v) => Err(TryFromNumberErrorKind::F64ToF32LossyConversion(v).into()),
}
}
}
#[cfg(test)]
mod test {
use super::Number;
use crate::error::{TryFromNumberError, TryFromNumberErrorKind};
macro_rules! to_unsigned_converter_tests {
($typ:ident) => {
assert_eq!($typ::try_from(Number::PosInt(69u64)).unwrap(), 69);
assert!(matches!(
$typ::try_from(Number::PosInt(($typ::MAX as u64) + 1u64)).unwrap_err(),
TryFromNumberError {
kind: TryFromNumberErrorKind::OutsideIntegerRange(..)
}
));
assert!(matches!(
$typ::try_from(Number::NegInt(-1i64)).unwrap_err(),
TryFromNumberError {
kind: TryFromNumberErrorKind::NegativeToUnsignedLossyConversion(..)
}
));
for val in [69.69f64, f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
assert!(matches!(
$typ::try_from(Number::Float(val)).unwrap_err(),
TryFromNumberError {
kind: TryFromNumberErrorKind::FloatToIntegerLossyConversion(..)
}
));
}
assert_eq!($typ::try_from(Number::Float(25.0)).unwrap(), 25);
};
}
#[test]
fn to_u64() {
assert_eq!(u64::try_from(Number::PosInt(69u64)).unwrap(), 69u64);
assert!(matches!(
u64::try_from(Number::NegInt(-1i64)).unwrap_err(),
TryFromNumberError {
kind: TryFromNumberErrorKind::NegativeToUnsignedLossyConversion(..)
}
));
for val in [69.69f64, f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
assert!(matches!(
u64::try_from(Number::Float(val)).unwrap_err(),
TryFromNumberError {
kind: TryFromNumberErrorKind::FloatToIntegerLossyConversion(..)
}
));
}
}
#[test]
fn to_u32() {
to_unsigned_converter_tests!(u32);
}
#[test]
fn to_u16() {
to_unsigned_converter_tests!(u16);
}
#[test]
fn to_u8() {
to_unsigned_converter_tests!(u8);
}
macro_rules! to_signed_converter_tests {
($typ:ident) => {
assert_eq!($typ::try_from(Number::PosInt(69u64)).unwrap(), 69);
assert_eq!($typ::try_from(Number::NegInt(-69i64)).unwrap(), -69);
assert!(matches!(
$typ::try_from(Number::PosInt(($typ::MAX as u64) + 1u64)).unwrap_err(),
TryFromNumberError {
kind: TryFromNumberErrorKind::OutsideIntegerRange(..)
}
));
assert!(matches!(
$typ::try_from(Number::NegInt(($typ::MIN as i64) - 1i64)).unwrap_err(),
TryFromNumberError {
kind: TryFromNumberErrorKind::OutsideIntegerRange(..)
}
));
for val in [69.69f64, f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
assert!(matches!(
u64::try_from(Number::Float(val)).unwrap_err(),
TryFromNumberError {
kind: TryFromNumberErrorKind::FloatToIntegerLossyConversion(..)
}
));
}
let range = || ($typ::MIN..=$typ::MAX);
for val in range().take(1024).chain(range().rev().take(1024)) {
assert_eq!(val, $typ::try_from(Number::Float(val as f64)).unwrap());
$typ::try_from(Number::Float((val as f64) + 0.1)).expect_err("not equivalent");
}
};
}
#[test]
fn to_i64() {
assert_eq!(i64::try_from(Number::PosInt(69u64)).unwrap(), 69);
assert_eq!(i64::try_from(Number::NegInt(-69i64)).unwrap(), -69);
for val in [69.69f64, f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
assert!(matches!(
u64::try_from(Number::Float(val)).unwrap_err(),
TryFromNumberError {
kind: TryFromNumberErrorKind::FloatToIntegerLossyConversion(..)
}
));
}
let range = || (i64::MIN..=i64::MAX);
for val in range().take(1024).chain(range().rev().take(1024)) {
if ((val as f64) as i64) == val {
assert_eq!(val, i64::try_from(Number::Float(val as f64)).unwrap());
}
let fval = val as f64;
if (fval + 0.1).fract() != 0.0 {
i64::try_from(Number::Float((val as f64) + 0.1)).expect_err("not equivalent");
}
}
}
#[test]
fn to_i32() {
to_signed_converter_tests!(i32);
}
#[test]
fn to_i16() {
to_signed_converter_tests!(i16);
}
#[test]
fn to_i8() {
to_signed_converter_tests!(i8);
i8::try_from(Number::Float(-3200000.0)).expect_err("overflow");
i8::try_from(Number::Float(32.1)).expect_err("imprecise");
i8::try_from(Number::Float(i8::MAX as f64 + 0.1)).expect_err("imprecise");
i8::try_from(Number::Float(f64::NAN)).expect_err("nan");
i8::try_from(Number::Float(f64::INFINITY)).expect_err("nan");
}
#[test]
fn to_f64() {
assert_eq!(f64::try_from(Number::PosInt(69u64)).unwrap(), 69f64);
assert_eq!(f64::try_from(Number::NegInt(-69i64)).unwrap(), -69f64);
assert_eq!(f64::try_from(Number::Float(-69f64)).unwrap(), -69f64);
assert!(f64::try_from(Number::Float(f64::NAN)).unwrap().is_nan());
assert_eq!(
f64::try_from(Number::Float(f64::INFINITY)).unwrap(),
f64::INFINITY
);
assert_eq!(
f64::try_from(Number::Float(f64::NEG_INFINITY)).unwrap(),
f64::NEG_INFINITY
);
let significand_max_u64: u64 = 1 << 53;
let significand_max_i64: i64 = 1 << 53;
assert_eq!(
f64::try_from(Number::PosInt(significand_max_u64)).unwrap(),
9007199254740992f64
);
assert_eq!(
f64::try_from(Number::NegInt(significand_max_i64)).unwrap(),
9007199254740992f64
);
assert_eq!(
f64::try_from(Number::NegInt(-significand_max_i64)).unwrap(),
-9007199254740992f64
);
assert!(matches!(
f64::try_from(Number::PosInt(significand_max_u64 + 1)).unwrap_err(),
TryFromNumberError {
kind: TryFromNumberErrorKind::U64ToFloatLossyConversion(..)
}
));
assert!(matches!(
f64::try_from(Number::NegInt(significand_max_i64 + 1)).unwrap_err(),
TryFromNumberError {
kind: TryFromNumberErrorKind::I64ToFloatLossyConversion(..)
}
));
assert!(matches!(
f64::try_from(Number::NegInt(-significand_max_i64 - 1)).unwrap_err(),
TryFromNumberError {
kind: TryFromNumberErrorKind::I64ToFloatLossyConversion(..)
}
));
}
#[test]
fn to_f32() {
assert_eq!(f32::try_from(Number::PosInt(69u64)).unwrap(), 69f32);
assert_eq!(f32::try_from(Number::NegInt(-69i64)).unwrap(), -69f32);
let significand_max_u64: u64 = 1 << 24;
let significand_max_i64: i64 = 1 << 24;
assert_eq!(
f32::try_from(Number::PosInt(significand_max_u64)).unwrap(),
16777216f32
);
assert_eq!(
f32::try_from(Number::NegInt(significand_max_i64)).unwrap(),
16777216f32
);
assert_eq!(
f32::try_from(Number::NegInt(-significand_max_i64)).unwrap(),
-16777216f32
);
assert!(matches!(
f32::try_from(Number::PosInt(significand_max_u64 + 1)).unwrap_err(),
TryFromNumberError {
kind: TryFromNumberErrorKind::U64ToFloatLossyConversion(..)
}
));
assert!(matches!(
f32::try_from(Number::NegInt(significand_max_i64 + 1)).unwrap_err(),
TryFromNumberError {
kind: TryFromNumberErrorKind::I64ToFloatLossyConversion(..)
}
));
assert!(matches!(
f32::try_from(Number::NegInt(-significand_max_i64 - 1)).unwrap_err(),
TryFromNumberError {
kind: TryFromNumberErrorKind::I64ToFloatLossyConversion(..)
}
));
for val in [69f64, f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
assert!(matches!(
f32::try_from(Number::Float(val)).unwrap_err(),
TryFromNumberError {
kind: TryFromNumberErrorKind::F64ToF32LossyConversion(..)
}
));
}
}
#[test]
fn to_f64_lossy() {
assert_eq!(Number::PosInt(69u64).to_f64_lossy(), 69f64);
assert_eq!(
Number::PosInt((1 << 53) + 1).to_f64_lossy(),
9007199254740992f64
);
assert_eq!(
Number::NegInt(-(1 << 53) - 1).to_f64_lossy(),
-9007199254740992f64
);
}
#[test]
fn to_f32_lossy() {
assert_eq!(Number::PosInt(69u64).to_f32_lossy(), 69f32);
assert_eq!(Number::PosInt((1 << 24) + 1).to_f32_lossy(), 16777216f32);
assert_eq!(Number::NegInt(-(1 << 24) - 1).to_f32_lossy(), -16777216f32);
assert_eq!(
Number::Float(1452089033.7674935).to_f32_lossy(),
1452089100f32
);
}
#[test]
#[cfg(all(
test,
aws_sdk_unstable,
feature = "serde-deserialize",
feature = "serde-serialize"
))]
fn number_serde() {
let n: Number = serde_json::from_str("1.1").unwrap();
assert_eq!(n, Number::Float(1.1));
let n: Number = serde_json::from_str("1").unwrap();
assert_eq!(n, Number::PosInt(1));
let n: Number = serde_json::from_str("0").unwrap();
assert_eq!(n, Number::PosInt(0));
let n: Number = serde_json::from_str("-1").unwrap();
assert_eq!(n, Number::NegInt(-1));
assert_eq!("1.1", serde_json::to_string(&Number::Float(1.1)).unwrap());
assert_eq!("1", serde_json::to_string(&Number::PosInt(1)).unwrap());
assert_eq!("0", serde_json::to_string(&Number::PosInt(0)).unwrap());
assert_eq!("-1", serde_json::to_string(&Number::NegInt(-1)).unwrap());
}
}