@@ -312,17 +312,40 @@ where
312312 // signed carrier that can represent both the source and target.
313313 let carrier_type = primitive_decimal_carrier_type ( array. ptype ( ) ) . max ( T :: DECIMAL_TYPE ) ;
314314 match_each_decimal_value_type ! ( carrier_type, |W | {
315- let scale_factor = decimal_scale_factor:: <W >( decimal_dtype. scale( ) ) ?;
316- cast_integer_values_to_decimal_buffer( values, decimal_dtype, valid_values, |value| {
317- let value = <W as BigCast >:: from( value) ?;
318- let quotient = value / scale_factor;
319- let value = ( quotient * scale_factor == value) . then_some( quotient) ?;
320- let value = <T as BigCast >:: from( value) ?;
321- decimal_value_fits_precision( value, decimal_dtype) . then_some( value)
322- } )
315+ // `S` and `T` fix the carrier, so only one arm can match. The const condition lets the
316+ // compiler skip codegen of the kernel for the other arms.
317+ if const { is_scaled_down_carrier:: <S , T , W >( ) } {
318+ let scale_factor = decimal_scale_factor:: <W >( decimal_dtype. scale( ) ) ?;
319+ cast_integer_values_to_decimal_buffer( values, decimal_dtype, valid_values, |value| {
320+ let value = <W as BigCast >:: from( value) ?;
321+ let quotient = value / scale_factor;
322+ let value = ( quotient * scale_factor == value) . then_some( quotient) ?;
323+ let value = <T as BigCast >:: from( value) ?;
324+ decimal_value_fits_precision( value, decimal_dtype) . then_some( value)
325+ } )
326+ } else {
327+ unreachable!(
328+ "{carrier_type} is not the scaled-down carrier for {}" ,
329+ S :: PTYPE
330+ )
331+ }
323332 } )
324333}
325334
335+ /// Whether `W` is the carrier that a scaled-down cast from `S` to `T` selects.
336+ const fn is_scaled_down_carrier < S , T , W > ( ) -> bool
337+ where
338+ S : NativePType ,
339+ T : NativeDecimalType ,
340+ W : NativeDecimalType ,
341+ {
342+ let carrier = primitive_decimal_carrier_type ( S :: PTYPE ) as u8 ;
343+ let target = T :: DECIMAL_TYPE as u8 ;
344+ let selected = if carrier > target { carrier } else { target } ;
345+
346+ W :: DECIMAL_TYPE as u8 == selected
347+ }
348+
326349fn cast_integer_values_to_decimal_buffer < S , T > (
327350 values : & [ S ] ,
328351 decimal_dtype : DecimalDType ,
@@ -352,15 +375,19 @@ fn signed_primitive_decimal_type(ptype: PType) -> Option<DecimalType> {
352375}
353376
354377/// The smallest signed decimal physical type that represents every `ptype` value.
355- fn primitive_decimal_carrier_type ( ptype : PType ) -> DecimalType {
378+ #[ expect(
379+ clippy:: panic,
380+ reason = "`unreachable!` with a message is not available in a const fn"
381+ ) ]
382+ const fn primitive_decimal_carrier_type ( ptype : PType ) -> DecimalType {
356383 match ptype {
357384 PType :: I8 => DecimalType :: I8 ,
358385 PType :: U8 | PType :: I16 => DecimalType :: I16 ,
359386 PType :: U16 | PType :: I32 => DecimalType :: I32 ,
360387 PType :: U32 | PType :: I64 => DecimalType :: I64 ,
361388 PType :: U64 => DecimalType :: I128 ,
362389 PType :: F16 | PType :: F32 | PType :: F64 => {
363- unreachable ! ( "floating primitives are rejected before selecting a decimal carrier" )
390+ panic ! ( "floating primitives are rejected before selecting a decimal carrier" )
364391 }
365392 }
366393}
@@ -687,6 +714,7 @@ mod test {
687714 use crate :: dtype:: PType ;
688715 use crate :: dtype:: i256;
689716 use crate :: expr:: stats:: Stat ;
717+ use crate :: match_each_decimal_value_type;
690718 use crate :: validity:: Validity ;
691719
692720 static SESSION : LazyLock < VortexSession > = LazyLock :: new ( VortexSession :: empty) ;
@@ -1013,6 +1041,39 @@ mod test {
10131041 Ok ( ( ) )
10141042 }
10151043
1044+ #[ rstest]
1045+ fn cast_integer_to_negative_scale_decimal_every_width (
1046+ #[ values(
1047+ PType :: U8 , PType :: U16 , PType :: U32 , PType :: U64 , PType :: I8 , PType :: I16 , PType :: I32 ,
1048+ PType :: I64
1049+ ) ]
1050+ ptype : PType ,
1051+ #[ values( 2 , 4 , 9 , 18 , 38 , 76 ) ] precision : u8 ,
1052+ ) -> VortexResult < ( ) > {
1053+ let mut ctx = SESSION . create_execution_ctx ( ) ;
1054+ let decimal_dtype = DecimalDType :: new ( precision, -1 ) ;
1055+ let casted = PrimitiveArray :: from_iter ( [ 100u8 , 30 ] )
1056+ . into_array ( )
1057+ . cast ( DType :: Primitive ( ptype, Nullability :: NonNullable ) ) ?
1058+ . cast ( DType :: Decimal ( decimal_dtype, Nullability :: NonNullable ) ) ?
1059+ . execute :: < DecimalArray > ( & mut ctx) ?;
1060+
1061+ assert_eq ! (
1062+ casted. values_type( ) ,
1063+ DecimalType :: smallest_decimal_value_type( & decimal_dtype)
1064+ ) ;
1065+ match_each_decimal_value_type ! ( casted. values_type( ) , |T | {
1066+ let expected = [ 10i8 , 3 ] . map( <T as crate :: dtype:: BigCast >:: from) ;
1067+ let actual = casted
1068+ . buffer:: <T >( )
1069+ . iter( )
1070+ . map( |& value| Some ( value) )
1071+ . collect:: <Vec <_>>( ) ;
1072+ assert_eq!( actual, expected) ;
1073+ } ) ;
1074+ Ok ( ( ) )
1075+ }
1076+
10161077 #[ test]
10171078 fn cast_zero_to_large_negative_scale_decimal ( ) -> VortexResult < ( ) > {
10181079 let mut ctx = SESSION . create_execution_ctx ( ) ;
0 commit comments