...

Source file src/github.com/cockroachdb/apd/v3/round.go

Documentation: github.com/cockroachdb/apd/v3

     1  // Copyright 2016 The Cockroach Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
    12  // implied. See the License for the specific language governing
    13  // permissions and limitations under the License.
    14  
    15  package apd
    16  
    17  // Round sets d to rounded x, rounded to the precision specified by c. If c
    18  // has zero precision, no rounding will occur. If c has no Rounding specified,
    19  // RoundHalfUp is used.
    20  func (c *Context) Round(d, x *Decimal) (Condition, error) {
    21  	return c.goError(c.round(d, x))
    22  }
    23  
    24  //gcassert:inline
    25  func (c *Context) round(d, x *Decimal) Condition {
    26  	return c.Rounding.Round(c, d, x, true /* disableIfPrecisionZero */)
    27  }
    28  
    29  // Rounder specifies the behavior of rounding.
    30  type Rounder string
    31  
    32  // ShouldAddOne returns true if 1 should be added to the absolute value
    33  // of a number being rounded. result is the result to which the 1 would
    34  // be added. neg is true if the number is negative. half is -1 if the
    35  // discarded digits are < 0.5, 0 if = 0.5, or 1 if > 0.5.
    36  func (r Rounder) ShouldAddOne(result *BigInt, neg bool, half int) bool {
    37  	// NOTE: this is written using a switch statement instead of some
    38  	// other form of dynamic dispatch to assist Go's escape analysis.
    39  	switch r {
    40  	case RoundDown:
    41  		return roundDown(result, neg, half)
    42  	case RoundHalfUp:
    43  		return roundHalfUp(result, neg, half)
    44  	case RoundHalfEven:
    45  		return roundHalfEven(result, neg, half)
    46  	case RoundCeiling:
    47  		return roundCeiling(result, neg, half)
    48  	case RoundFloor:
    49  		return roundFloor(result, neg, half)
    50  	case RoundHalfDown:
    51  		return roundHalfDown(result, neg, half)
    52  	case RoundUp:
    53  		return roundUp(result, neg, half)
    54  	case Round05Up:
    55  		return round05Up(result, neg, half)
    56  	default:
    57  		return roundHalfUp(result, neg, half)
    58  	}
    59  }
    60  
    61  // Round sets d to rounded x.
    62  func (r Rounder) Round(c *Context, d, x *Decimal, disableIfPrecisionZero bool) Condition {
    63  	d.Set(x)
    64  	nd := x.NumDigits()
    65  	xs := x.Sign()
    66  	var res Condition
    67  
    68  	if disableIfPrecisionZero && c.Precision == 0 {
    69  		// Rounding has been disabled.
    70  		return d.setExponent(c, nd, res, int64(d.Exponent))
    71  	}
    72  
    73  	// adj is the adjusted exponent: exponent + clength - 1
    74  	if adj := int64(x.Exponent) + nd - 1; xs != 0 && adj < int64(c.MinExponent) {
    75  		// Subnormal is defined before rounding.
    76  		res |= Subnormal
    77  		// setExponent here to prevent double-rounded subnormals.
    78  		res |= d.setExponent(c, nd, res, int64(d.Exponent))
    79  		return res
    80  	}
    81  
    82  	diff := nd - int64(c.Precision)
    83  	if diff > 0 {
    84  		if diff > MaxExponent {
    85  			return SystemOverflow | Overflow
    86  		}
    87  		if diff < MinExponent {
    88  			return SystemUnderflow | Underflow
    89  		}
    90  		res |= Rounded
    91  		var y, m BigInt
    92  		e := tableExp10(diff, &y)
    93  		y.QuoRem(&d.Coeff, e, &m)
    94  		if m.Sign() != 0 {
    95  			res |= Inexact
    96  			var discard Decimal
    97  			discard.Coeff.Set(&m)
    98  			discard.Exponent = int32(-diff)
    99  			if r.ShouldAddOne(&y, x.Negative, discard.Cmp(decimalHalf)) {
   100  				roundAddOne(&y, &diff)
   101  			}
   102  		}
   103  		d.Coeff.Set(&y)
   104  		// The coefficient changed, so recompute num digits in setExponent.
   105  		nd = unknownNumDigits
   106  	} else {
   107  		diff = 0
   108  	}
   109  	res |= d.setExponent(c, nd, res, int64(d.Exponent), diff)
   110  	return res
   111  }
   112  
   113  // roundAddOne adds 1 to abs(b).
   114  func roundAddOne(b *BigInt, diff *int64) {
   115  	if b.Sign() < 0 {
   116  		panic("unexpected negative")
   117  	}
   118  	nd := NumDigits(b)
   119  	b.Add(b, bigOne)
   120  	nd2 := NumDigits(b)
   121  	if nd2 > nd {
   122  		b.Quo(b, bigTen)
   123  		*diff++
   124  	}
   125  }
   126  
   127  // roundings is a set containing all available Rounders.
   128  var roundings = map[Rounder]struct{}{
   129  	RoundDown:     {},
   130  	RoundHalfUp:   {},
   131  	RoundHalfEven: {},
   132  	RoundCeiling:  {},
   133  	RoundFloor:    {},
   134  	RoundHalfDown: {},
   135  	RoundUp:       {},
   136  	Round05Up:     {},
   137  }
   138  
   139  const (
   140  	// RoundDown rounds toward 0; truncate.
   141  	RoundDown Rounder = "down"
   142  	// RoundHalfUp rounds up if the digits are >= 0.5.
   143  	RoundHalfUp Rounder = "half_up"
   144  	// RoundHalfEven rounds up if the digits are > 0.5. If the digits are equal
   145  	// to 0.5, it rounds up if the previous digit is odd, always producing an
   146  	// even digit.
   147  	RoundHalfEven Rounder = "half_even"
   148  	// RoundCeiling towards +Inf: rounds up if digits are > 0 and the number
   149  	// is positive.
   150  	RoundCeiling Rounder = "ceiling"
   151  	// RoundFloor towards -Inf: rounds up if digits are > 0 and the number
   152  	// is negative.
   153  	RoundFloor Rounder = "floor"
   154  	// RoundHalfDown rounds up if the digits are > 0.5.
   155  	RoundHalfDown Rounder = "half_down"
   156  	// RoundUp rounds away from 0.
   157  	RoundUp Rounder = "up"
   158  	// Round05Up rounds zero or five away from 0; same as round-up, except that
   159  	// rounding up only occurs if the digit to be rounded up is 0 or 5.
   160  	Round05Up Rounder = "05up"
   161  )
   162  
   163  func roundDown(result *BigInt, neg bool, half int) bool {
   164  	return false
   165  }
   166  
   167  func roundUp(result *BigInt, neg bool, half int) bool {
   168  	return true
   169  }
   170  
   171  func round05Up(result *BigInt, neg bool, half int) bool {
   172  	var z BigInt
   173  	z.Rem(result, bigFive)
   174  	if z.Sign() == 0 {
   175  		return true
   176  	}
   177  	z.Rem(result, bigTen)
   178  	return z.Sign() == 0
   179  }
   180  
   181  func roundHalfUp(result *BigInt, neg bool, half int) bool {
   182  	return half >= 0
   183  }
   184  
   185  func roundHalfEven(result *BigInt, neg bool, half int) bool {
   186  	if half > 0 {
   187  		return true
   188  	}
   189  	if half < 0 {
   190  		return false
   191  	}
   192  	return result.Bit(0) == 1
   193  }
   194  
   195  func roundHalfDown(result *BigInt, neg bool, half int) bool {
   196  	return half > 0
   197  }
   198  
   199  func roundFloor(result *BigInt, neg bool, half int) bool {
   200  	return neg
   201  }
   202  
   203  func roundCeiling(result *BigInt, neg bool, half int) bool {
   204  	return !neg
   205  }
   206  

View as plain text