This is an automated email from the ASF dual-hosted git repository. github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-11076-e905793f20b79d4431a678dd3bb0fedb8cfcab90 in repository https://gitbox.apache.org/repos/asf/arrow-rs.git
commit 8f4bb0caa723c9536e59669418d475a6b544d2e9 Author: Anand Hegde <[email protected]> AuthorDate: Fri Oct 16 14:12:17 2026 +0000 Move numeric and boolean cast helpers into a private submodule (#11076) # Which issue does this PR close? - Part of #11032. # Rationale for this change Continues splitting up `cast/mod.rs`. # What changes are included in this PR? Moves the numeric and boolean cast helpers into a new `cast/numeric.rs`. Code is unchanged apart from `pub(crate)` visibility. # Are these changes tested? Covered by existing tests. # Are there any significant user-facing changes? No. The public helpers are re-exported, so their paths are unchanged. --- arrow-cast/src/cast/mod.rs | 186 ++------------------------------------ arrow-cast/src/cast/numeric.rs | 200 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 206 insertions(+), 180 deletions(-) diff --git a/arrow-cast/src/cast/mod.rs b/arrow-cast/src/cast/mod.rs index 65bf6bbc8d..94de6548f4 100644 --- a/arrow-cast/src/cast/mod.rs +++ b/arrow-cast/src/cast/mod.rs @@ -42,6 +42,7 @@ mod decimal; mod dictionary; mod list; mod map; +mod numeric; mod run_array; mod string; mod structs; @@ -53,6 +54,10 @@ use crate::cast::decimal::*; use crate::cast::dictionary::*; use crate::cast::list::*; use crate::cast::map::*; +pub(crate) use crate::cast::numeric::{ + cast_bool_to_numeric, cast_numeric_arrays, cast_numeric_to_bool, +}; +pub use crate::cast::numeric::{cast_num_to_bool, num_cast, single_bool_to_numeric}; use crate::cast::run_array::*; use crate::cast::string::*; use crate::cast::structs::*; @@ -2310,189 +2315,10 @@ where Ok(Arc::new(result)) } -/// Convert Array into a PrimitiveArray of type, and apply numeric cast -fn cast_numeric_arrays<FROM, TO>( - from: &dyn Array, - cast_options: &CastOptions, -) -> Result<ArrayRef, ArrowError> -where - FROM: ArrowPrimitiveType, - TO: ArrowPrimitiveType, - FROM::Native: NumCast + NumericNative, - TO::Native: NumCast + NumericNative, -{ - let from = from.as_primitive::<FROM>(); - let array: PrimitiveArray<TO> = if const { - is_infallible_numeric_cast( - <FROM::Native as NumericNative>::KIND, - <TO::Native as NumericNative>::KIND, - ) - } { - // This cast cannot fail, so the fastest kernel, `unary`, can be used. - from.unary(|v| num_cast(v).expect("numeric cast is infallible")) - } else if cast_options.safe { - // If the value can't be cast to the `TO::Native`, return null - from.unary_opt(num_cast) - } else { - // If the value can't be cast to the `TO::Native`, return error - from.try_unary(|v| { - num_cast(v).ok_or_else(|| { - ArrowError::CastError(format!("Can't cast value {v:?} to type {}", TO::DATA_TYPE)) - }) - })? - }; - Ok(Arc::new(array)) -} - -/// Properties of a numeric native type that decide whether [`num_cast`] to -/// another numeric type can fail. -#[derive(Clone, Copy)] -struct NumericKind { - float: bool, - signed: bool, - bits: u32, -} - -/// A native type with a [`NumericKind`]. -trait NumericNative { - const KIND: NumericKind; -} - -macro_rules! numeric_native { - ($($t:ty => $float:literal, $signed:literal;)*) => {$( - impl NumericNative for $t { - const KIND: NumericKind = NumericKind { - float: $float, - signed: $signed, - bits: 8 * std::mem::size_of::<$t>() as u32, - }; - } - )*}; -} - -numeric_native! { - u8 => false, false; - u16 => false, false; - u32 => false, false; - u64 => false, false; - i8 => false, true; - i16 => false, true; - i32 => false, true; - i64 => false, true; - half::f16 => true, true; - f32 => true, true; - f64 => true, true; -} - -/// Returns true if [`num_cast`] succeeds for every value of `from` when casting -/// to `to`: every cast to a float, and integer casts whose target holds every -/// source value. -/// -/// This must hold for every bit pattern of the source type, not only for the -/// valid values of a particular array, because [`PrimitiveArray::unary`] applies -/// the conversion to null slots as well, and their contents are arbitrary. -const fn is_infallible_numeric_cast(from: NumericKind, to: NumericKind) -> bool { - if to.float { - true - } else if from.float { - false - } else if from.signed == to.signed { - to.bits >= from.bits - } else { - // Only an unsigned source fits a strictly wider signed target. - !from.signed && to.bits > from.bits - } -} - -/// Natural cast between numeric types -/// Return None if the input `value` can't be casted to type `O`. -#[inline] -pub fn num_cast<I, O>(value: I) -> Option<O> -where - I: NumCast, - O: NumCast, -{ - num_traits::cast::cast::<I, O>(value) -} - -/// Cast numeric types to Boolean -/// -/// Any zero value returns `false` while non-zero returns `true` -fn cast_numeric_to_bool<FROM>(from: &dyn Array) -> Result<ArrayRef, ArrowError> -where - FROM: ArrowPrimitiveType, -{ - Ok(Arc::new(BooleanArray::from_unary( - from.as_primitive::<FROM>(), - cast_num_to_bool, - ))) -} - -/// Cast numeric types to boolean -#[inline] -pub fn cast_num_to_bool<I>(value: I) -> bool -where - I: Default + PartialEq, -{ - value != I::default() -} - -/// Cast Boolean types to numeric -/// -/// `false` returns 0 while `true` returns 1 -fn cast_bool_to_numeric<TO>( - from: &dyn Array, - cast_options: &CastOptions, -) -> Result<ArrayRef, ArrowError> -where - TO: ArrowPrimitiveType, -{ - Ok(Arc::new(bool_to_numeric_cast::<TO>( - from.as_any().downcast_ref::<BooleanArray>().unwrap(), - cast_options, - ))) -} - -fn bool_to_numeric_cast<T>(from: &BooleanArray, _cast_options: &CastOptions) -> PrimitiveArray<T> -where - T: ArrowPrimitiveType, -{ - let to_numeric = |bits: u64, i: usize| { - if bits & (1 << i) != 0 { - T::Native::ONE - } else { - T::Native::ZERO - } - }; - // Unpack a 64-bit word at a time: the fixed-size inner loop helps the - // compiler vectorize the conversion. - let chunks = from.values().bit_chunks(); - let mut values = Vec::with_capacity(from.len()); - for bits in &chunks { - values.extend((0..64).map(|i| to_numeric(bits, i))); - } - let bits = chunks.remainder_bits(); - values.extend((0..chunks.remainder_len()).map(|i| to_numeric(bits, i))); - PrimitiveArray::new(values.into(), from.nulls().cloned()) -} - -/// Cast single bool value to numeric value. -#[inline] -pub fn single_bool_to_numeric<O>(value: bool) -> Option<O> -where - O: num_traits::NumCast + Default, -{ - if value { - // a workaround to cast a primitive to type O, infallible - num_traits::cast::cast(1) - } else { - Some(O::default()) - } -} - #[cfg(test)] mod tests { use super::*; + use crate::cast::numeric::{NumericNative, is_infallible_numeric_cast}; use crate::parse::parse_decimal; use DataType::*; use arrow_array::{Int64Array, RunArray, StringArray}; diff --git a/arrow-cast/src/cast/numeric.rs b/arrow-cast/src/cast/numeric.rs new file mode 100644 index 0000000000..6a5cb0a4ab --- /dev/null +++ b/arrow-cast/src/cast/numeric.rs @@ -0,0 +1,200 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +//! Cast support for numeric and boolean arrays. + +use crate::cast::*; + +/// Convert Array into a PrimitiveArray of type, and apply numeric cast +pub(crate) fn cast_numeric_arrays<FROM, TO>( + from: &dyn Array, + cast_options: &CastOptions, +) -> Result<ArrayRef, ArrowError> +where + FROM: ArrowPrimitiveType, + TO: ArrowPrimitiveType, + FROM::Native: NumCast + NumericNative, + TO::Native: NumCast + NumericNative, +{ + let from = from.as_primitive::<FROM>(); + let array: PrimitiveArray<TO> = if const { + is_infallible_numeric_cast( + <FROM::Native as NumericNative>::KIND, + <TO::Native as NumericNative>::KIND, + ) + } { + // This cast cannot fail, so the fastest kernel, `unary`, can be used. + from.unary(|v| num_cast(v).expect("numeric cast is infallible")) + } else if cast_options.safe { + // If the value can't be cast to the `TO::Native`, return null + from.unary_opt(num_cast) + } else { + // If the value can't be cast to the `TO::Native`, return error + from.try_unary(|v| { + num_cast(v).ok_or_else(|| { + ArrowError::CastError(format!("Can't cast value {v:?} to type {}", TO::DATA_TYPE)) + }) + })? + }; + Ok(Arc::new(array)) +} + +/// Properties of a numeric native type that decide whether [`num_cast`] to +/// another numeric type can fail. +#[derive(Clone, Copy)] +pub(crate) struct NumericKind { + float: bool, + signed: bool, + bits: u32, +} + +/// A native type with a [`NumericKind`]. +pub(crate) trait NumericNative { + const KIND: NumericKind; +} + +macro_rules! numeric_native { + ($($t:ty => $float:literal, $signed:literal;)*) => {$( + impl NumericNative for $t { + const KIND: NumericKind = NumericKind { + float: $float, + signed: $signed, + bits: 8 * std::mem::size_of::<$t>() as u32, + }; + } + )*}; +} + +numeric_native! { + u8 => false, false; + u16 => false, false; + u32 => false, false; + u64 => false, false; + i8 => false, true; + i16 => false, true; + i32 => false, true; + i64 => false, true; + half::f16 => true, true; + f32 => true, true; + f64 => true, true; +} + +/// Returns true if [`num_cast`] succeeds for every value of `from` when casting +/// to `to`: every cast to a float, and integer casts whose target holds every +/// source value. +/// +/// This must hold for every bit pattern of the source type, not only for the +/// valid values of a particular array, because [`PrimitiveArray::unary`] applies +/// the conversion to null slots as well, and their contents are arbitrary. +pub(crate) const fn is_infallible_numeric_cast(from: NumericKind, to: NumericKind) -> bool { + if to.float { + true + } else if from.float { + false + } else if from.signed == to.signed { + to.bits >= from.bits + } else { + // Only an unsigned source fits a strictly wider signed target. + !from.signed && to.bits > from.bits + } +} + +/// Natural cast between numeric types +/// Return None if the input `value` can't be casted to type `O`. +#[inline] +pub fn num_cast<I, O>(value: I) -> Option<O> +where + I: NumCast, + O: NumCast, +{ + num_traits::cast::cast::<I, O>(value) +} + +/// Cast numeric types to Boolean +/// +/// Any zero value returns `false` while non-zero returns `true` +pub(crate) fn cast_numeric_to_bool<FROM>(from: &dyn Array) -> Result<ArrayRef, ArrowError> +where + FROM: ArrowPrimitiveType, +{ + Ok(Arc::new(BooleanArray::from_unary( + from.as_primitive::<FROM>(), + cast_num_to_bool, + ))) +} + +/// Cast numeric types to boolean +#[inline] +pub fn cast_num_to_bool<I>(value: I) -> bool +where + I: Default + PartialEq, +{ + value != I::default() +} + +/// Cast Boolean types to numeric +/// +/// `false` returns 0 while `true` returns 1 +pub(crate) fn cast_bool_to_numeric<TO>( + from: &dyn Array, + cast_options: &CastOptions, +) -> Result<ArrayRef, ArrowError> +where + TO: ArrowPrimitiveType, +{ + Ok(Arc::new(bool_to_numeric_cast::<TO>( + from.as_any().downcast_ref::<BooleanArray>().unwrap(), + cast_options, + ))) +} + +fn bool_to_numeric_cast<T>(from: &BooleanArray, _cast_options: &CastOptions) -> PrimitiveArray<T> +where + T: ArrowPrimitiveType, +{ + let to_numeric = |bits: u64, i: usize| { + if bits & (1 << i) != 0 { + T::Native::ONE + } else { + T::Native::ZERO + } + }; + // Unpack a 64-bit word at a time: the fixed-size inner loop helps the + // compiler vectorize the conversion. + let chunks = from.values().bit_chunks(); + let mut values = Vec::with_capacity(from.len()); + for bits in &chunks { + values.extend((0..64).map(|i| to_numeric(bits, i))); + } + let bits = chunks.remainder_bits(); + values.extend((0..chunks.remainder_len()).map(|i| to_numeric(bits, i))); + PrimitiveArray::new(values.into(), from.nulls().cloned()) +} + +/// Cast single bool value to numeric value. +#[inline] +pub fn single_bool_to_numeric<O>(value: bool) -> Option<O> +where + O: num_traits::NumCast + Default, +{ + if value { + // a workaround to cast a primitive to type O, infallible + num_traits::cast::cast(1) + } else { + Some(O::default()) + } +}
