// 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.

//go:build go1.18

package kernels

import (
	
	

	
	
	
	
	
	
)

//go:generate stringer -type=RoundMode

type RoundMode int8

const (
	// Round to nearest integer less than or equal in magnitude (aka "floor")
	RoundDown RoundMode = iota
	// Round to nearest integer greater than or equal in magnitude (aka "ceil")
	RoundUp
	// Get integral part without fractional digits (aka "trunc")
	TowardsZero
	// Round negative values with DOWN and positive values with UP
	AwayFromZero
	// Round ties with DOWN (aka "round half towards negative infinity")
	HalfDown
	// Round ties with UP (aka "round half towards positive infinity")
	HalfUp
	// Round ties with TowardsZero (aka "round half away from infinity")
	HalfTowardsZero
	// Round ties with AwayFromZero (aka "round half towards infinity")
	HalfAwayFromZero
	// Round ties to nearest even integer
	HalfToEven
	// Round ties to nearest odd integer
	HalfToOdd
)

type RoundOptions struct {
	NDigits int64
	Mode    RoundMode
}

func (RoundOptions) () string { return "RoundOptions" }

type RoundState struct {
	RoundOptions
	Pow10 float64
}

func ( *exec.KernelCtx,  exec.KernelInitArgs) (exec.KernelState, error) {
	var  RoundState

	,  := .Options.(*RoundOptions)
	if  {
		.RoundOptions = *
	} else {
		if .RoundOptions,  = .Options.(RoundOptions); ! {
			return nil, fmt.Errorf("%w: attempted to initialize kernel state from invalid function options",
				arrow.ErrInvalid)
		}
	}

	// Only positive exponents for powers of 10 are used because combining
	// multiply and division operations produced more stable rounding than
	// using multiply-only.  Refer to NumPy's round implementation:
	// https://github.com/numpy/numpy/blob/7b2f20b406d27364c812f7a81a9c901afbd3600c/numpy/core/src/multiarray/calculation.c#L589
	.Pow10 = math.Pow10(int(math.Abs(float64(.NDigits))))
	return , nil
}

type RoundToMultipleOptions struct {
	// Multiple is the multiple to round to.
	//
	// Should be a positive numeric scalar of a type compatible
	// with the argument to be rounded. The cast kernel is used
	// to convert the rounding multiple to match the result type.
	Multiple scalar.Scalar
	// Mode is the rounding and tie-breaking mode
	Mode RoundMode
}

func (RoundToMultipleOptions) () string { return "RoundToMultipleOptions" }

type RoundToMultipleState = RoundToMultipleOptions

func isPositive( scalar.Scalar) bool {
	switch s := .(type) {
	case *scalar.Decimal128:
		return .Value.Greater(decimal128.Num{})
	case *scalar.Decimal256:
		return .Value.Greater(decimal256.Num{})
	case *scalar.Int8:
		return .Value > 0
	case *scalar.Uint8, *scalar.Uint16, *scalar.Uint32, *scalar.Uint64:
		return true
	case *scalar.Int16:
		return .Value > 0
	case *scalar.Int32:
		return .Value > 0
	case *scalar.Int64:
		return .Value > 0
	case *scalar.Float32:
		return .Value > 0
	case *scalar.Float64:
		return .Value > 0
	default:
		return false
	}
}

func ( *exec.KernelCtx,  exec.KernelInitArgs) (exec.KernelState, error) {
	var  RoundToMultipleState

	,  := .Options.(*RoundToMultipleOptions)
	if  {
		 = *
	} else {
		if ,  = .Options.(RoundToMultipleOptions); ! {
			return nil, fmt.Errorf("%w: attempted to initialize kernel state from invalid function options",
				arrow.ErrInvalid)
		}
	}

	 := .Multiple
	if  == nil || !.IsValid() {
		return nil, fmt.Errorf("%w: rounding multiple must be non-null and valid",
			arrow.ErrInvalid)
	}

	if !isPositive() {
		return nil, fmt.Errorf("%w: rounding multiple must be positive", arrow.ErrInvalid)
	}

	// ensure the rounding multiple option matches the kernel's output type.
	// the output type is not available here, so we use the following rule:
	// if "multiple" is neither a floating-point nor decimal type,
	// then cast to float64, else cast to the kernel's input type.
	var  arrow.DataType
	if !arrow.IsFloating(.DataType().ID()) && !arrow.IsDecimal(.DataType().ID()) {
		 = arrow.PrimitiveTypes.Float64
	} else {
		 = .Inputs[0]
	}

	if !arrow.TypeEqual(.DataType(), ) {
		,  := .CastTo()
		if  != nil {
			return nil, 
		}

		.Multiple = 
	}

	return , nil
}

func getFloatRoundImpl[ constraints.Float]( RoundMode) func()  {
	switch  {
	case RoundDown:
		return func( )  { return (math.Floor(float64())) }
	case RoundUp:
		return func( )  { return (math.Ceil(float64())) }
	case TowardsZero: // truncate
		return func( )  { return (math.Trunc(float64())) }
	case AwayFromZero:
		return func( )  {
			 := float64()
			if math.Signbit() {
				return (math.Floor())
			}
			return (math.Ceil())
		}
	// the Half variants are only called when the fractional portion
	// was 0.5
	case HalfDown:
		return func( )  { return (math.Floor(float64())) }
	case HalfUp:
		return func( )  { return (math.Ceil(float64())) }
	case HalfTowardsZero:
		return func( )  { return (math.Trunc(float64())) }
	case HalfAwayFromZero:
		return func( )  {
			 := float64()
			if math.Signbit() {
				return (math.Floor())
			}
			return (math.Ceil())
		}
	case HalfToEven:
		return func( )  { return (math.RoundToEven(float64())) }
	case HalfToOdd:
		return func( )  {
			 := float64()
			return (math.Floor(*0.5) + math.Ceil(*0.5))
		}
	}
	panic("invalid rounding mode")
}

func getDecRounding[ decimal128.Num | decimal256.Num]( RoundMode,  *roundDecImpl[]) func(,  ,  ,  int32)  {
	var (
		   
		 = .fromI64(1)
		 = .fromI64(-1)
	)

	switch  {
	case RoundDown:
		return func(, ,  ,  int32)  {
			 = .Sub(, )
			if .Sign() < 0 {
				 = .Sub(, )
			}
			return 
		}
	case RoundUp:
		return func(, ,  ,  int32)  {
			 = .Sub(, )
			if .Sign() > 0 &&  !=  {
				 = .Add(, )
			}
			return 
		}
	case TowardsZero:
		return func(, ,  ,  int32)  {
			return .Sub(, )
		}
	case AwayFromZero:
		return func(, ,  ,  int32)  {
			 = .Sub(, )
			if .Sign() < 0 {
				 = .Sub(, )
			} else if .Sign() > 0 &&  !=  {
				 = .Add(, )
			}
			return 
		}
	// variants for Half_* modes are only invoked when the fractional part
	// is equal to 0.5
	case HalfDown:
		return func(, ,  ,  int32)  {
			 = .Sub(, )
			if .Sign() < 0 {
				 = .Sub(, )
			}
			return 
		}
	case HalfUp:
		return func(, ,  ,  int32)  {
			 = .Sub(, )
			if .Sign() > 0 &&  !=  {
				 = .Add(, )
			}
			return 
		}
	case HalfTowardsZero:
		return func(, ,  ,  int32)  {
			return .Sub(, )
		}
	case HalfAwayFromZero:
		return func(, ,  ,  int32)  {
			 = .Sub(, )
			if .Sign() < 0 {
				 = .Sub(, )
			} else if .Sign() > 0 &&  !=  {
				 = .Add(, )
			}
			return 
		}
	case HalfToEven:
		return func(, ,  ,  int32)  {
			 := .reduceScale(, , false)
			if .lowBits()%2 != 0 {
				if .Sign() >= 0 {
					 = .Add(, )
				} else {
					 = .Add(, )
				}
			}
			return .increaseScale(, )
		}
	case HalfToOdd:
		return func(, ,  ,  int32)  {
			 := .reduceScale(, , false)
			if .lowBits()%2 == 0 {
				if .Sign() != 0 {
					 = .Add(, )
				} else {
					 = .Add(, )
				}
			}
			return .increaseScale(, )
		}
	}
	panic("invalid rounding mode")
}

type round[ constraints.Float] struct {
	pow10   
	ndigits int64
	mode    RoundMode

	fn func() 
}

func ( *round[]) ( *exec.KernelCtx,  ,  *error)  {
	 := float64()
	// do not process INF or NaN because they will trigger overflow errors
	// at the end of this
	if math.IsInf(, 0) || math.IsNaN() {
		return 
	}

	var  
	if .ndigits >= 0 {
		 =  * .pow10
	} else {
		 =  / .pow10
	}

	 :=  - (math.Floor(float64()))
	if  == 0 {
		// scaled value has no fractional component
		// no rounding is needed.
		return 
	}

	if .mode >= HalfDown &&  != 0.5 {
		 = (math.Round(float64()))
	} else {
		 = .fn()
	}

	// equality check is omitted so that the common case of 10^0
	// (integer rounding) uses multiply-only
	if .ndigits > 0 {
		 /= .pow10
	} else {
		 *= .pow10
	}
	if math.IsInf(float64(), 0) || math.IsNaN(float64()) {
		* = errOverflow
		return 
	}

	return 
}

func roundKernelFloating[ constraints.Float]( *exec.KernelCtx,  *exec.ExecSpan,  *exec.ExecResult) error {
	 := .State.(RoundState)
	 := round[]{
		pow10:   (.Pow10),
		ndigits: .NDigits,
		mode:    .Mode,
		fn:      getFloatRoundImpl[](.Mode),
	}

	return ScalarUnaryNotNull(.call)(, , )
}

func roundToMultipleFloating[ constraints.Float]( *exec.KernelCtx,  *exec.ExecSpan,  *exec.ExecResult) error {
	 := .State.(RoundToMultipleState)
	 := roundToMultiple[]{
		mode:     .Mode,
		multiple: UnboxScalar[](.Multiple.(scalar.PrimitiveScalar)),
		fn:       getFloatRoundImpl[](.Mode),
	}

	return ScalarUnaryNotNull(.call)(, , )
}

type roundDecImpl[ decimal128.Num | decimal256.Num] struct {
	*decOps[]
	scaleMultiplier     func(int) 
	halfScaleMultiplier func(int) 
	divide              func(a, b ) (res, rem )
	fitsInPrec          func(, int32) bool
	less                func(a, b ) bool
	reduceScale         func(, int32, bool) 
	increaseScale       func(, int32) 
	lowBits             func() uint64
	fromI64             func(int64) 
	str                 func(, int32) string
}

var (
	roundDec128 = roundDecImpl[decimal128.Num]{
		decOps:              &dec128Ops,
		scaleMultiplier:     decimal128.GetScaleMultiplier,
		halfScaleMultiplier: decimal128.GetHalfScaleMultiplier,
		divide:              func(,  decimal128.Num) (,  decimal128.Num) { return .Div() },
		fitsInPrec:          func( decimal128.Num,  int32) bool { return .FitsInPrecision() },
		less:                func(,  decimal128.Num) bool { return .Less() },
		reduceScale:         func( decimal128.Num,  int32,  bool) decimal128.Num { return .ReduceScaleBy(, ) },
		increaseScale:       func( decimal128.Num,  int32) decimal128.Num { return .IncreaseScaleBy() },
		lowBits:             func( decimal128.Num) uint64 { return .LowBits() },
		fromI64:             func( int64) decimal128.Num { return decimal128.FromI64() },
		str:                 func( decimal128.Num,  int32) string { return .ToString() },
	}
	roundDec256 = roundDecImpl[decimal256.Num]{
		decOps:              &dec256Ops,
		scaleMultiplier:     decimal256.GetScaleMultiplier,
		halfScaleMultiplier: decimal256.GetHalfScaleMultiplier,
		divide:              func(,  decimal256.Num) (,  decimal256.Num) { return .Div() },
		fitsInPrec:          func( decimal256.Num,  int32) bool { return .FitsInPrecision() },
		less:                func(,  decimal256.Num) bool { return .Less() },
		reduceScale:         func( decimal256.Num,  int32,  bool) decimal256.Num { return .ReduceScaleBy(, ) },
		increaseScale:       func( decimal256.Num,  int32) decimal256.Num { return .IncreaseScaleBy() },
		lowBits:             func( decimal256.Num) uint64 { return .LowBits() },
		fromI64:             func( int64) decimal256.Num { return decimal256.FromI64() },
		str:                 func( decimal256.Num,  int32) string { return .ToString() },
	}
)

type roundDec[ decimal128.Num | decimal256.Num] struct {
	ty      arrow.DecimalType
	mode    RoundMode
	ndigits int64
	pow     int32
	// pow10 is "1" for the given decimal scale. Similarly halfPow10 is "0.5"
	pow10, halfPow10, negHalfPow10 

	opsImpl *roundDecImpl[]
	fn      func(, , , int32) 
}

func ( *roundDec[]) ( *exec.KernelCtx,  ,  *error)  {
	var  
	if .pow >= .ty.GetPrecision() {
		* = fmt.Errorf("%w: rounding to %d digits will not fit in precision of %s",
			arrow.ErrInvalid, .ndigits, .ty)
		return 
	} else if .pow < 0 {
		// no-op copy output to input
		return 
	}

	,  := .opsImpl.divide(, .pow10)
	// the remainder is effectively the scaled fractional part after division
	if  ==  {
		return 
	}

	if .mode >= HalfDown {
		if  == .halfPow10 ||  == .negHalfPow10 {
			// on the halfway point, use tiebreaker
			 = .fn(, , .pow10, .pow)
		} else if .opsImpl.Sign() >= 0 {
			// positive, round up/down
			 = .opsImpl.Sub(, )
			if .opsImpl.less(.halfPow10, ) {
				 = .opsImpl.Add(, .pow10)
			}
		} else {
			// negative, round up/down
			 = .opsImpl.Sub(, )
			if .opsImpl.less(, .negHalfPow10) {
				 = .opsImpl.Sub(, .pow10)
			}
		}
	} else {
		 = .fn(, , .pow10, .pow)
	}

	if !.opsImpl.fitsInPrec(, .ty.GetPrecision()) {
		* = fmt.Errorf("%w: rounded value %s does not fit in precision of %s",
			arrow.ErrInvalid, .opsImpl.str(, .ty.GetScale()), .ty)
		return 
	}
	return 
}

func getRoundKernelDecimal[ decimal128.Num | decimal256.Num]() exec.ArrayKernelExec {
	var  
	switch any().(type) {
	case decimal128.Num:
		return func( *exec.KernelCtx,  *exec.ExecSpan,  *exec.ExecResult) error {
			return roundKernelDecimal(&roundDec128, , , )
		}
	case decimal256.Num:
		return func( *exec.KernelCtx,  *exec.ExecSpan,  *exec.ExecResult) error {
			return roundKernelDecimal(&roundDec256, , , )
		}
	}
	panic("should never get here")
}

func roundKernelDecimal[ decimal128.Num | decimal256.Num]( *roundDecImpl[],  *exec.KernelCtx,  *exec.ExecSpan,  *exec.ExecResult) error {
	 := .State.(RoundState)
	 := roundDec[]{
		ty:      .Type.(arrow.DecimalType),
		ndigits: .NDigits,
		mode:    .Mode,
		opsImpl: ,
		fn:      getDecRounding(.Mode, ),
	}

	.pow = .ty.GetScale() - int32(.ndigits)
	if .pow < .ty.GetPrecision() && .pow >= 0 {
		.pow10 = .scaleMultiplier(int(.pow))
		.halfPow10 = .halfScaleMultiplier(int(.pow))
		.negHalfPow10 = .Neg(.halfPow10)
	}

	return ScalarUnaryNotNull(.call)(, , )
}

func getRoundToMultipleKernelDecimal[ decimal128.Num | decimal256.Num]() exec.ArrayKernelExec {
	var  
	switch any().(type) {
	case decimal128.Num:
		return func( *exec.KernelCtx,  *exec.ExecSpan,  *exec.ExecResult) error {
			return roundToMultipleDecimal(&roundDec128, , , )
		}
	case decimal256.Num:
		return func( *exec.KernelCtx,  *exec.ExecSpan,  *exec.ExecResult) error {
			return roundToMultipleDecimal(&roundDec256, , , )
		}
	}
	panic("should never get here")
}

func roundToMultipleDecimal[ decimal128.Num | decimal256.Num]( *roundDecImpl[],  *exec.KernelCtx,  *exec.ExecSpan,  *exec.ExecResult) error {
	 := .State.(RoundToMultipleState)
	 := roundToMultipleDec[]{
		ty:      .Type.(arrow.DecimalType),
		mode:    .Mode,
		opsImpl: ,
		fn:      getDecRounding(.Mode, ),
		mult:    UnboxScalar[](.Multiple.(scalar.PrimitiveScalar)),
	}

	.halfMult = .Div(.mult, .fromI64(2))
	.negHalfMult = .Neg(.halfMult)
	.hasHalfwayPoint = .lowBits(.mult)%2 == 0

	return ScalarUnaryNotNull(.call)(, , )
}

type roundToMultiple[ constraints.Float] struct {
	multiple 
	mode     RoundMode

	fn func() 
}

func ( *roundToMultiple[]) ( *exec.KernelCtx,  ,  *error)  {
	 := float64()
	// do not process Inf or NaN because they will trigger the overflow error
	// at the end of this.
	if math.IsInf(, 0) || math.IsNaN() {
		return 
	}

	 :=  / .multiple
	 :=  - (math.Floor(float64()))
	if  == 0 {
		// scaled value is an integer, no rounding needed
		return 
	}

	if .mode >= HalfDown &&  != 0.5 {
		 = (math.Round(float64()))
	} else {
		 = .fn()
	}
	 *= .multiple

	if math.IsInf(float64(), 0) || math.IsNaN(float64()) {
		* = errOverflow
		return 
	}

	return 
}

type roundToMultipleDec[ decimal128.Num | decimal256.Num] struct {
	ty   arrow.DecimalType
	mode RoundMode

	mult, halfMult, negHalfMult 
	hasHalfwayPoint             bool

	opsImpl *roundDecImpl[]
	fn      func(, , , int32) 
}

func ( *roundToMultipleDec[]) ( *exec.KernelCtx,  ,  *error)  {
	var  

	,  := .opsImpl.divide(, .mult)
	if  ==  {
		return 
	}

	 := .opsImpl.fromI64(1)
	if .mode >= HalfDown {
		if .hasHalfwayPoint && ( == .halfMult ||  == .negHalfMult) {
			// on the halfway point, use tiebreaker
			// manually implement rounding since we aren't actually rounding
			// a decimal value, but rather manipulating the multiple
			switch .mode {
			case HalfDown:
				if .opsImpl.Sign() < 0 {
					 = .opsImpl.Sub(, )
				}
			case HalfUp:
				if .opsImpl.Sign() >= 0 {
					 = .opsImpl.Add(, )
				}
			case HalfTowardsZero:
			case HalfAwayFromZero:
				if .opsImpl.Sign() >= 0 {
					 = .opsImpl.Add(, )
				} else {
					 = .opsImpl.Sub(, )
				}
			case HalfToEven:
				if .opsImpl.lowBits()%2 != 0 {
					if .opsImpl.Sign() >= 0 {
						 = .opsImpl.Add(, )
					} else {
						 = .opsImpl.Sub(, )
					}
				}
			case HalfToOdd:
				if .opsImpl.lowBits()%2 == 0 {
					if .opsImpl.Sign() >= 0 {
						 = .opsImpl.Add(, )
					} else {
						 = .opsImpl.Sub(, )
					}
				}
			}
		} else if .opsImpl.Sign() >= 0 {
			// positive, round up/down
			if .opsImpl.less(.halfMult, ) {
				 = .opsImpl.Add(, )
			}
		} else {
			// negative, round up/down
			if .opsImpl.less(, .negHalfMult) {
				 = .opsImpl.Sub(, )
			}
		}
	} else {
		// manually implement rounding since we aren't actually rounding
		// a decimal value, but rather manipulating the multiple
		switch .mode {
		case RoundDown:
			if .opsImpl.Sign() < 0 {
				 = .opsImpl.Sub(, )
			}
		case RoundUp:
			if .opsImpl.Sign() >= 0 {
				 = .opsImpl.Add(, )
			}
		case TowardsZero:
		case AwayFromZero:
			if .opsImpl.Sign() >= 0 {
				 = .opsImpl.Add(, )
			} else {
				 = .opsImpl.Sub(, )
			}
		}
	}

	 := .opsImpl.Mul(, .mult)
	if !.opsImpl.fitsInPrec(, .ty.GetPrecision()) {
		* = fmt.Errorf("%w: rounded value %s does not fit in precision of %s",
			arrow.ErrInvalid, .opsImpl.str(, .ty.GetScale()), .ty)
		return 
	}
	return 
}

func ( arrow.Type) exec.ArrayKernelExec {
	switch  {
	case arrow.FLOAT32:
		return roundKernelFloating[float32]
	case arrow.FLOAT64:
		return roundKernelFloating[float64]
	case arrow.DECIMAL128:
		return getRoundKernelDecimal[decimal128.Num]()
	case arrow.DECIMAL256:
		return getRoundKernelDecimal[decimal256.Num]()
	}
	panic("should never get here")
}

func ( arrow.Type) exec.ArrayKernelExec {
	switch  {
	case arrow.FLOAT32:
		return roundToMultipleFloating[float32]
	case arrow.FLOAT64:
		return roundToMultipleFloating[float64]
	case arrow.DECIMAL128:
		return getRoundToMultipleKernelDecimal[decimal128.Num]()
	case arrow.DECIMAL256:
		return getRoundToMultipleKernelDecimal[decimal256.Num]()
	}
	panic("should never get here")
}

func ( exec.KernelInitFn,  func(arrow.Type) exec.ArrayKernelExec) []exec.ScalarKernel {
	 := make([]exec.ScalarKernel, 0)
	for ,  := range []arrow.DataType{arrow.PrimitiveTypes.Float32, arrow.PrimitiveTypes.Float64,
		&arrow.Decimal128Type{Precision: 1}, &arrow.Decimal256Type{Precision: 1}} {
		 := .ID()

		var  exec.OutputType
		if arrow.IsDecimal() {
			 = OutputFirstType
		} else {
			 = exec.NewOutputType()
		}

		 = append(, exec.NewScalarKernel(
			[]exec.InputType{exec.NewIDInput()}, , (), ))
	}

	return append(, NullExecKernel(1))
}

func ( RoundMode) []exec.ScalarKernel {
	 := make([]exec.ScalarKernel, 0)
	for ,  := range floatingTypes {
		var  exec.ArrayKernelExec
		switch .ID() {
		case arrow.FLOAT32:
			 := getFloatRoundImpl[float32]()
			 = ScalarUnary(func( *exec.KernelCtx,  []float32,  []float32) error {
				for ,  := range  {
					[] = ()
				}
				return nil
			})
		case arrow.FLOAT64:
			 := getFloatRoundImpl[float64]()
			 = ScalarUnary(func( *exec.KernelCtx,  []float64,  []float64) error {
				for ,  := range  {
					[] = ()
				}
				return nil
			})
		}
		 = append(, exec.NewScalarKernel(
			[]exec.InputType{exec.NewExactInput()}, exec.NewOutputType(),
			, nil))
	}
	return append(, NullExecKernel(1))
}

func fixedRoundDecimalExec[ decimal128.Num | decimal256.Num]( *roundDecImpl[],  RoundMode) exec.ArrayKernelExec {
	return func( *exec.KernelCtx,  *exec.ExecSpan,  *exec.ExecResult) error {
		 := roundDec[]{
			ty:      .Type.(arrow.DecimalType),
			mode:    ,
			opsImpl: ,
			fn:      getDecRounding(, ),
		}

		.pow = .ty.GetScale() - int32(.ndigits)
		if .pow < .ty.GetPrecision() && .pow >= 0 {
			.pow10 = .scaleMultiplier(int(.pow))
			.halfPow10 = .halfScaleMultiplier(int(.pow))
			.negHalfPow10 = .Neg(.halfPow10)
		}

		return ScalarUnaryNotNull(.call)(, , )
	}
}

func [ decimal128.Num | decimal256.Num]( RoundMode) exec.ArrayKernelExec {
	var  
	switch any().(type) {
	case decimal128.Num:
		return func() exec.ArrayKernelExec {
			return fixedRoundDecimalExec(&roundDec128, )
		}()
	case decimal256.Num:
		return func() exec.ArrayKernelExec {
			return fixedRoundDecimalExec(&roundDec256, )
		}()
	}
	panic("should never get here")
}