github.com/cockroachdb/apd/v3@v3.2.0/round.go (about) 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 }