github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/sem/tree/decimal.go (about)

     1  // Copyright 2016 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package tree
    12  
    13  import (
    14  	"fmt"
    15  	"math"
    16  
    17  	"github.com/cockroachdb/apd"
    18  	"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode"
    19  	"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror"
    20  )
    21  
    22  var (
    23  	// DecimalCtx is the default context for decimal operations. Any change
    24  	// in the exponent limits must still guarantee a safe conversion to the
    25  	// postgres binary decimal format in the wire protocol, which uses an
    26  	// int16. See pgwire/types.go.
    27  	DecimalCtx = &apd.Context{
    28  		Precision:   20,
    29  		Rounding:    apd.RoundHalfUp,
    30  		MaxExponent: 2000,
    31  		MinExponent: -2000,
    32  		// Don't error on invalid operation, return NaN instead.
    33  		Traps: apd.DefaultTraps &^ apd.InvalidOperation,
    34  	}
    35  	// ExactCtx is a decimal context with exact precision.
    36  	ExactCtx = DecimalCtx.WithPrecision(0)
    37  	// HighPrecisionCtx is a decimal context with high precision.
    38  	HighPrecisionCtx = DecimalCtx.WithPrecision(2000)
    39  	// IntermediateCtx is a decimal context with additional precision for
    40  	// intermediate calculations to protect against order changes that can
    41  	// happen in dist SQL. The additional 5 allows the stress test to pass.
    42  	// See #13689 for more analysis and other algorithms.
    43  	IntermediateCtx = DecimalCtx.WithPrecision(DecimalCtx.Precision + 5)
    44  	// RoundCtx is a decimal context with high precision and RoundHalfEven
    45  	// rounding.
    46  	RoundCtx = func() *apd.Context {
    47  		ctx := *HighPrecisionCtx
    48  		ctx.Rounding = apd.RoundHalfEven
    49  		return &ctx
    50  	}()
    51  
    52  	errScaleOutOfRange = pgerror.New(pgcode.NumericValueOutOfRange, "scale out of range")
    53  )
    54  
    55  // LimitDecimalWidth limits d's precision (total number of digits) and scale
    56  // (number of digits after the decimal point). Note that this any limiting will
    57  // modify the decimal in-place.
    58  func LimitDecimalWidth(d *apd.Decimal, precision, scale int) error {
    59  	if d.Form != apd.Finite || precision <= 0 {
    60  		return nil
    61  	}
    62  	// Use +1 here because it is inverted later.
    63  	if scale < math.MinInt32+1 || scale > math.MaxInt32 {
    64  		return errScaleOutOfRange
    65  	}
    66  	if scale > precision {
    67  		return pgerror.Newf(pgcode.InvalidParameterValue, "scale (%d) must be between 0 and precision (%d)", scale, precision)
    68  	}
    69  
    70  	// http://www.postgresql.org/docs/9.5/static/datatype-numeric.html
    71  	// "If the scale of a value to be stored is greater than
    72  	// the declared scale of the column, the system will round the
    73  	// value to the specified number of fractional digits. Then,
    74  	// if the number of digits to the left of the decimal point
    75  	// exceeds the declared precision minus the declared scale, an
    76  	// error is raised."
    77  
    78  	c := DecimalCtx.WithPrecision(uint32(precision))
    79  	c.Traps = apd.InvalidOperation
    80  
    81  	if _, err := c.Quantize(d, d, -int32(scale)); err != nil {
    82  		var lt string
    83  		switch v := precision - scale; v {
    84  		case 0:
    85  			lt = "1"
    86  		default:
    87  			lt = fmt.Sprintf("10^%d", v)
    88  		}
    89  		return pgerror.Newf(pgcode.NumericValueOutOfRange, "value with precision %d, scale %d must round to an absolute value less than %s", precision, scale, lt)
    90  	}
    91  	return nil
    92  }