@@ -23,7 +23,7 @@ use crate::arrays::scalar_fn::ExactScalarFn;
2323use crate :: arrays:: scalar_fn:: ScalarFnArrayExt ;
2424use crate :: arrays:: scalar_fn:: ScalarFnArrayView ;
2525use crate :: arrays:: widen:: WidenArraySlotsExt ;
26- use crate :: arrays:: widen:: compute:: cast_to_ptype ;
26+ use crate :: arrays:: widen:: compute:: cast_keeping_nullability ;
2727use crate :: arrays:: widen:: compute:: wrap_values;
2828use crate :: arrays:: widen:: encode:: integer_value;
2929use crate :: arrays:: widen:: encode:: smallest_integer_ptype;
@@ -39,11 +39,17 @@ use crate::scalar_fn::fns::operators::Operator;
3939/// Runs checked add, subtract, multiply and divide of a [`Widen`] with another `Widen` or a
4040/// constant at the smallest integer type that holds both operands and every result.
4141///
42- /// Narrow values read and write less memory. [`arithmetic_ptype`] picks the type from the extrema
43- /// of the valid rows, so the type can be too wide but never too narrow. When no narrower type is
44- /// known to hold the result, the operation runs at the logical type, where the checked kernel
45- /// reports an overflow or a division by zero with the same error as canonical execution. Any other
46- /// operand declines, so the default path widens it once.
42+ /// Narrow values read and write less memory. [`smallest_arithmetic_ptype`] picks the type from the
43+ /// extrema of the valid rows, so the type can be too wide but never too narrow. When no narrower
44+ /// type is known to hold the result, the operation runs at the logical type, where the checked
45+ /// kernel reports an overflow or a division by zero with the same error as canonical execution. Any
46+ /// other operand declines, so the default path widens it once.
47+ ///
48+ /// **Cost:** the extrema come from statistics. [`valid_value_interval`] reads the exact cached
49+ /// `Min` and `Max` of each executed operand when they exist, and otherwise scans it once before the
50+ /// operation runs. Executing `Primitive` values returns the same array, so its cached statistics
51+ /// count and the result stays cached. Encoded values execute to a new array, so neither their
52+ /// cached statistics nor the new result carry over.
4753#[ derive( Debug ) ]
4854pub ( crate ) struct WidenNumericKernel ;
4955
@@ -77,7 +83,7 @@ impl ExecuteParentKernel<Widen> for WidenNumericKernel {
7783 1 => binary. get_child ( 0 ) ,
7884 _ => return Ok ( None ) ,
7985 } ;
80- let other_stored = if let Some ( other) = other. as_opt :: < Widen > ( ) {
86+ let other_values = if let Some ( other) = other. as_opt :: < Widen > ( ) {
8187 other. values ( ) . clone ( )
8288 } else if other. is :: < Constant > ( ) {
8389 other. clone ( )
@@ -90,33 +96,35 @@ impl ExecuteParentKernel<Widen> for WidenNumericKernel {
9096 return Ok ( Some ( Canonical :: empty ( dtype) . into_array ( ) ) ) ;
9197 }
9298
93- // Execute each stored operand once. `Columnar` keeps a constant constant.
94- let stored = array
99+ // Execute the values of each operand once. `Columnar` keeps a constant constant.
100+ let values = array
95101 . values ( )
96102 . clone ( )
97103 . execute :: < Columnar > ( ctx) ?
98104 . into_array ( ) ;
99- let other_stored = other_stored . execute :: < Columnar > ( ctx) ?. into_array ( ) ;
105+ let other_values = other_values . execute :: < Columnar > ( ctx) ?. into_array ( ) ;
100106 let ( lhs, rhs) = if child_idx == 0 {
101- ( stored , other_stored )
107+ ( values , other_values )
102108 } else {
103- ( other_stored , stored )
109+ ( other_values , values )
104110 } ;
105111
106112 // An operand without a valid row makes every row null.
107- let ( Some ( lhs_bounds) , Some ( rhs_bounds) ) =
108- ( valid_bounds ( & lhs, ctx) ?, valid_bounds ( & rhs, ctx) ?)
109- else {
113+ let ( Some ( lhs_interval) , Some ( rhs_interval) ) = (
114+ valid_value_interval ( & lhs, ctx) ?,
115+ valid_value_interval ( & rhs, ctx) ?,
116+ ) else {
110117 let null = Scalar :: null ( dtype. clone ( ) ) ;
111118 return Ok ( Some ( ConstantArray :: new ( null, parent. len ( ) ) . into_array ( ) ) ) ;
112119 } ;
113120
114- let ptype = arithmetic_ptype ( & lhs_bounds, & rhs_bounds, operator, * logical_ptype) ;
115- let lhs = cast_to_ptype ( & lhs, ptype) ?;
116- let rhs = cast_to_ptype ( & rhs, ptype) ?;
117- let values = execute_numeric ( & lhs, & rhs, operator, ctx) ?;
121+ let ptype =
122+ smallest_arithmetic_ptype ( & lhs_interval, & rhs_interval, operator, * logical_ptype) ;
123+ let lhs = cast_keeping_nullability ( & lhs, ptype) ?;
124+ let rhs = cast_keeping_nullability ( & rhs, ptype) ?;
125+ let result = execute_numeric ( & lhs, & rhs, operator, ctx) ?;
118126
119- wrap_values ( values , dtype. clone ( ) ) . map ( Some )
127+ wrap_values ( result , dtype. clone ( ) ) . map ( Some )
120128 }
121129}
122130
@@ -126,7 +134,7 @@ impl ExecuteParentKernel<Widen> for WidenNumericKernel {
126134///
127135/// Null rows do not count. A cast to the narrower type can change their payloads, and the checked
128136/// kernel skips them.
129- fn arithmetic_ptype (
137+ fn smallest_arithmetic_ptype (
130138 lhs : & RangeInclusive < i128 > ,
131139 rhs : & RangeInclusive < i128 > ,
132140 operator : NumericOperator ,
@@ -143,7 +151,10 @@ fn arithmetic_ptype(
143151}
144152
145153/// Returns the extrema of the valid values of an integer array, or `None` when no row is valid.
146- fn valid_bounds (
154+ ///
155+ /// [`min_max`] reads the exact cached `Min` and `Max` statistics of `array`. Without them it scans
156+ /// `array` once and caches the result on it.
157+ fn valid_value_interval (
147158 array : & ArrayRef ,
148159 ctx : & mut ExecutionCtx ,
149160) -> VortexResult < Option < RangeInclusive < i128 > > > {
0 commit comments