You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@arrow.apache.org by tu...@apache.org on 2022/11/07 00:05:21 UTC
[arrow-rs] branch master updated: Check overflow when casting floating point value to decimal256 (#3033)
This is an automated email from the ASF dual-hosted git repository.
tustvold pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git
The following commit(s) were added to refs/heads/master by this push:
new 6dd9dae1c Check overflow when casting floating point value to decimal256 (#3033)
6dd9dae1c is described below
commit 6dd9dae1cea7618a7e136285e7927e4d802ec058
Author: Liang-Chi Hsieh <vi...@gmail.com>
AuthorDate: Sun Nov 6 16:05:16 2022 -0800
Check overflow when casting floating point value to decimal256 (#3033)
* Check overflow when casting floating point value to decimal256
* Add from_f64
---
arrow-buffer/src/bigint.rs | 15 +++++++++++-
arrow-cast/src/cast.rs | 59 ++++++++++++++++++++++++++++++++++++++++++----
2 files changed, 69 insertions(+), 5 deletions(-)
diff --git a/arrow-buffer/src/bigint.rs b/arrow-buffer/src/bigint.rs
index e87c05826..8dd57d2c4 100644
--- a/arrow-buffer/src/bigint.rs
+++ b/arrow-buffer/src/bigint.rs
@@ -16,7 +16,7 @@
// under the License.
use num::cast::AsPrimitive;
-use num::BigInt;
+use num::{BigInt, FromPrimitive};
use std::cmp::Ordering;
/// A signed 256-bit integer
@@ -102,6 +102,19 @@ impl i256 {
Self::from_parts(v as u128, v >> 127)
}
+ /// Create an optional i256 from the provided `f64`. Returning `None`
+ /// if overflow occurred
+ pub fn from_f64(v: f64) -> Option<Self> {
+ BigInt::from_f64(v).and_then(|i| {
+ let (integer, overflow) = i256::from_bigint_with_overflow(i);
+ if overflow {
+ None
+ } else {
+ Some(integer)
+ }
+ })
+ }
+
/// Create an i256 from the provided low u128 and high i128
#[inline]
pub const fn from_parts(low: u128, high: i128) -> Self {
diff --git a/arrow-cast/src/cast.rs b/arrow-cast/src/cast.rs
index 3e23a059b..5bf8c19c5 100644
--- a/arrow-cast/src/cast.rs
+++ b/arrow-cast/src/cast.rs
@@ -387,16 +387,38 @@ fn cast_floating_point_to_decimal256<T: ArrowPrimitiveType>(
array: &PrimitiveArray<T>,
precision: u8,
scale: u8,
+ cast_options: &CastOptions,
) -> Result<ArrayRef, ArrowError>
where
<T as ArrowPrimitiveType>::Native: AsPrimitive<f64>,
{
let mul = 10_f64.powi(scale as i32);
- array
- .unary::<_, Decimal256Type>(|v| i256::from_i128((v.as_() * mul).round() as i128))
- .with_precision_and_scale(precision, scale)
- .map(|a| Arc::new(a) as ArrayRef)
+ if cast_options.safe {
+ let iter = array
+ .iter()
+ .map(|v| v.and_then(|v| i256::from_f64((v.as_() * mul).round())));
+ let casted_array =
+ unsafe { PrimitiveArray::<Decimal256Type>::from_trusted_len_iter(iter) };
+ casted_array
+ .with_precision_and_scale(precision, scale)
+ .map(|a| Arc::new(a) as ArrayRef)
+ } else {
+ array
+ .try_unary::<_, Decimal256Type, _>(|v| {
+ i256::from_f64((v.as_() * mul).round()).ok_or_else(|| {
+ ArrowError::CastError(format!(
+ "Cannot cast to {}({}, {}). Overflowing on {:?}",
+ Decimal256Type::PREFIX,
+ precision,
+ scale,
+ v
+ ))
+ })
+ })
+ .and_then(|a| a.with_precision_and_scale(precision, scale))
+ .map(|a| Arc::new(a) as ArrayRef)
+ }
}
/// Cast the primitive array using [`PrimitiveArray::reinterpret_cast`]
@@ -666,11 +688,13 @@ pub fn cast_with_options(
as_primitive_array::<Float32Type>(array),
*precision,
*scale,
+ cast_options,
),
Float64 => cast_floating_point_to_decimal256(
as_primitive_array::<Float64Type>(array),
*precision,
*scale,
+ cast_options,
),
Null => Ok(new_null_array(to_type, array.len())),
_ => Err(ArrowError::CastError(format!(
@@ -6166,4 +6190,31 @@ mod tests {
err
);
}
+
+ #[test]
+ fn test_cast_floating_point_to_decimal256_overflow() {
+ let array = Float64Array::from(vec![f64::MAX]);
+ let array = Arc::new(array) as ArrayRef;
+ let casted_array = cast_with_options(
+ &array,
+ &DataType::Decimal256(76, 50),
+ &CastOptions { safe: true },
+ );
+ assert!(casted_array.is_ok());
+ assert!(casted_array.unwrap().is_null(0));
+
+ let casted_array = cast_with_options(
+ &array,
+ &DataType::Decimal256(76, 50),
+ &CastOptions { safe: false },
+ );
+ let err = casted_array.unwrap_err().to_string();
+ let expected_error = "Cast error: Cannot cast to Decimal256(76, 50)";
+ assert!(
+ err.contains(expected_error),
+ "did not find expected error '{}' in actual error '{}'",
+ expected_error,
+ err
+ );
+ }
}