Skip to content

Commit

Permalink
Allow lossless conversions from float into integral types
Browse files Browse the repository at this point in the history
  • Loading branch information
rcoh committed Dec 7, 2023
1 parent 82b190c commit 76378d1
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 12 deletions.
1 change: 1 addition & 0 deletions rust-runtime/aws-smithy-json/src/deserialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,7 @@ fn must_not_be_finite(f: f64) -> Result<f64, ()> {
#[cfg(test)]
mod tests {
use crate::deserialize::error::{DeserializeError as Error, DeserializeErrorKind as ErrorKind};
use crate::deserialize::token::expect_start_array;
use crate::deserialize::token::test::{
end_array, end_object, object_key, start_array, start_object, value_bool, value_null,
value_number, value_string,
Expand Down
49 changes: 37 additions & 12 deletions rust-runtime/aws-smithy-types/src/number.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,7 @@ macro_rules! to_unsigned_integer_converter {
Number::NegInt(v) => {
Err(TryFromNumberErrorKind::NegativeToUnsignedLossyConversion(v).into())
}
Number::Float(v) => {
Err(TryFromNumberErrorKind::FloatToIntegerLossyConversion(v).into())
}
Number::Float(v) => attempt_lossless!(v, $typ),
}
}
}
Expand All @@ -102,9 +100,7 @@ macro_rules! to_signed_integer_converter {
match value {
Number::PosInt(v) => Ok(Self::try_from(v)?),
Number::NegInt(v) => Ok(Self::try_from(v)?),
Number::Float(v) => {
Err(TryFromNumberErrorKind::FloatToIntegerLossyConversion(v).into())
}
Number::Float(v) => attempt_lossless!(v, $typ),
}
}
}
Expand All @@ -115,6 +111,17 @@ macro_rules! to_signed_integer_converter {
};
}

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())
}
}};
}

/// Converts to a `u64`. The conversion fails if it is lossy.
impl TryFrom<Number> for u64 {
type Error = TryFromNumberError;
Expand All @@ -125,9 +132,7 @@ impl TryFrom<Number> for u64 {
Number::NegInt(v) => {
Err(TryFromNumberErrorKind::NegativeToUnsignedLossyConversion(v).into())
}
Number::Float(v) => {
Err(TryFromNumberErrorKind::FloatToIntegerLossyConversion(v).into())
}
Number::Float(v) => attempt_lossless!(v, u64),
}
}
}
Expand All @@ -142,9 +147,7 @@ impl TryFrom<Number> for i64 {
match value {
Number::PosInt(v) => Ok(Self::try_from(v)?),
Number::NegInt(v) => Ok(v),
Number::Float(v) => {
Err(TryFromNumberErrorKind::FloatToIntegerLossyConversion(v).into())
}
Number::Float(v) => attempt_lossless!(v, i64),
}
}
}
Expand Down Expand Up @@ -236,6 +239,7 @@ mod test {
}
));
}
assert_eq!($typ::try_from(Number::Float(25.0)).unwrap(), 25);
};
}

Expand Down Expand Up @@ -302,6 +306,13 @@ mod test {
}
));
}

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");
}
};
}

Expand All @@ -318,6 +329,19 @@ mod test {
}
));
}
let range = || (i64::MIN..=i64::MAX);

for val in range().take(1024).chain(range().rev().take(1024)) {
// if we can actually represent the value
if ((val as f64) as i64) == val {
assert_eq!(val, i64::try_from(Number::Float(val as f64)).unwrap());
}
let fval = val as f64;
// at the limits of the range, we don't have this precision
if (fval + 0.1).fract() != 0.0 {
i64::try_from(Number::Float((val as f64) + 0.1)).expect_err("not equivalent");
}
}
}

#[test]
Expand All @@ -333,6 +357,7 @@ mod test {
#[test]
fn to_i8() {
to_signed_converter_tests!(i8);
i8::try_from(Number::Float(-3200000.0)).expect_err("overflow");
}

#[test]
Expand Down

0 comments on commit 76378d1

Please sign in to comment.