sslobodr | d046be8 | 2019-01-16 10:02:22 -0500 | [diff] [blame] | 1 | /* |
| 2 | Copyright 2014 The Kubernetes Authors. |
| 3 | |
| 4 | Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | you may not use this file except in compliance with the License. |
| 6 | You may obtain a copy of the License at |
| 7 | |
| 8 | http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | |
| 10 | Unless required by applicable law or agreed to in writing, software |
| 11 | distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | See the License for the specific language governing permissions and |
| 14 | limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package resource |
| 18 | |
| 19 | import ( |
| 20 | "math/big" |
| 21 | "strconv" |
| 22 | |
| 23 | inf "gopkg.in/inf.v0" |
| 24 | ) |
| 25 | |
| 26 | // Scale is used for getting and setting the base-10 scaled value. |
| 27 | // Base-2 scales are omitted for mathematical simplicity. |
| 28 | // See Quantity.ScaledValue for more details. |
| 29 | type Scale int32 |
| 30 | |
| 31 | // infScale adapts a Scale value to an inf.Scale value. |
| 32 | func (s Scale) infScale() inf.Scale { |
| 33 | return inf.Scale(-s) // inf.Scale is upside-down |
| 34 | } |
| 35 | |
| 36 | const ( |
| 37 | Nano Scale = -9 |
| 38 | Micro Scale = -6 |
| 39 | Milli Scale = -3 |
| 40 | Kilo Scale = 3 |
| 41 | Mega Scale = 6 |
| 42 | Giga Scale = 9 |
| 43 | Tera Scale = 12 |
| 44 | Peta Scale = 15 |
| 45 | Exa Scale = 18 |
| 46 | ) |
| 47 | |
| 48 | var ( |
| 49 | Zero = int64Amount{} |
| 50 | |
| 51 | // Used by quantity strings - treat as read only |
| 52 | zeroBytes = []byte("0") |
| 53 | ) |
| 54 | |
| 55 | // int64Amount represents a fixed precision numerator and arbitrary scale exponent. It is faster |
| 56 | // than operations on inf.Dec for values that can be represented as int64. |
| 57 | // +k8s:openapi-gen=true |
| 58 | type int64Amount struct { |
| 59 | value int64 |
| 60 | scale Scale |
| 61 | } |
| 62 | |
| 63 | // Sign returns 0 if the value is zero, -1 if it is less than 0, or 1 if it is greater than 0. |
| 64 | func (a int64Amount) Sign() int { |
| 65 | switch { |
| 66 | case a.value == 0: |
| 67 | return 0 |
| 68 | case a.value > 0: |
| 69 | return 1 |
| 70 | default: |
| 71 | return -1 |
| 72 | } |
| 73 | } |
| 74 | |
| 75 | // AsInt64 returns the current amount as an int64 at scale 0, or false if the value cannot be |
| 76 | // represented in an int64 OR would result in a loss of precision. This method is intended as |
| 77 | // an optimization to avoid calling AsDec. |
| 78 | func (a int64Amount) AsInt64() (int64, bool) { |
| 79 | if a.scale == 0 { |
| 80 | return a.value, true |
| 81 | } |
| 82 | if a.scale < 0 { |
| 83 | // TODO: attempt to reduce factors, although it is assumed that factors are reduced prior |
| 84 | // to the int64Amount being created. |
| 85 | return 0, false |
| 86 | } |
| 87 | return positiveScaleInt64(a.value, a.scale) |
| 88 | } |
| 89 | |
| 90 | // AsScaledInt64 returns an int64 representing the value of this amount at the specified scale, |
| 91 | // rounding up, or false if that would result in overflow. (1e20).AsScaledInt64(1) would result |
| 92 | // in overflow because 1e19 is not representable as an int64. Note that setting a scale larger |
| 93 | // than the current value may result in loss of precision - i.e. (1e-6).AsScaledInt64(0) would |
| 94 | // return 1, because 0.000001 is rounded up to 1. |
| 95 | func (a int64Amount) AsScaledInt64(scale Scale) (result int64, ok bool) { |
| 96 | if a.scale < scale { |
| 97 | result, _ = negativeScaleInt64(a.value, scale-a.scale) |
| 98 | return result, true |
| 99 | } |
| 100 | return positiveScaleInt64(a.value, a.scale-scale) |
| 101 | } |
| 102 | |
| 103 | // AsDec returns an inf.Dec representation of this value. |
| 104 | func (a int64Amount) AsDec() *inf.Dec { |
| 105 | var base inf.Dec |
| 106 | base.SetUnscaled(a.value) |
| 107 | base.SetScale(inf.Scale(-a.scale)) |
| 108 | return &base |
| 109 | } |
| 110 | |
| 111 | // Cmp returns 0 if a and b are equal, 1 if a is greater than b, or -1 if a is less than b. |
| 112 | func (a int64Amount) Cmp(b int64Amount) int { |
| 113 | switch { |
| 114 | case a.scale == b.scale: |
| 115 | // compare only the unscaled portion |
| 116 | case a.scale > b.scale: |
| 117 | result, remainder, exact := divideByScaleInt64(b.value, a.scale-b.scale) |
| 118 | if !exact { |
| 119 | return a.AsDec().Cmp(b.AsDec()) |
| 120 | } |
| 121 | if result == a.value { |
| 122 | switch { |
| 123 | case remainder == 0: |
| 124 | return 0 |
| 125 | case remainder > 0: |
| 126 | return -1 |
| 127 | default: |
| 128 | return 1 |
| 129 | } |
| 130 | } |
| 131 | b.value = result |
| 132 | default: |
| 133 | result, remainder, exact := divideByScaleInt64(a.value, b.scale-a.scale) |
| 134 | if !exact { |
| 135 | return a.AsDec().Cmp(b.AsDec()) |
| 136 | } |
| 137 | if result == b.value { |
| 138 | switch { |
| 139 | case remainder == 0: |
| 140 | return 0 |
| 141 | case remainder > 0: |
| 142 | return 1 |
| 143 | default: |
| 144 | return -1 |
| 145 | } |
| 146 | } |
| 147 | a.value = result |
| 148 | } |
| 149 | |
| 150 | switch { |
| 151 | case a.value == b.value: |
| 152 | return 0 |
| 153 | case a.value < b.value: |
| 154 | return -1 |
| 155 | default: |
| 156 | return 1 |
| 157 | } |
| 158 | } |
| 159 | |
| 160 | // Add adds two int64Amounts together, matching scales. It will return false and not mutate |
| 161 | // a if overflow or underflow would result. |
| 162 | func (a *int64Amount) Add(b int64Amount) bool { |
| 163 | switch { |
| 164 | case b.value == 0: |
| 165 | return true |
| 166 | case a.value == 0: |
| 167 | a.value = b.value |
| 168 | a.scale = b.scale |
| 169 | return true |
| 170 | case a.scale == b.scale: |
| 171 | c, ok := int64Add(a.value, b.value) |
| 172 | if !ok { |
| 173 | return false |
| 174 | } |
| 175 | a.value = c |
| 176 | case a.scale > b.scale: |
| 177 | c, ok := positiveScaleInt64(a.value, a.scale-b.scale) |
| 178 | if !ok { |
| 179 | return false |
| 180 | } |
| 181 | c, ok = int64Add(c, b.value) |
| 182 | if !ok { |
| 183 | return false |
| 184 | } |
| 185 | a.scale = b.scale |
| 186 | a.value = c |
| 187 | default: |
| 188 | c, ok := positiveScaleInt64(b.value, b.scale-a.scale) |
| 189 | if !ok { |
| 190 | return false |
| 191 | } |
| 192 | c, ok = int64Add(a.value, c) |
| 193 | if !ok { |
| 194 | return false |
| 195 | } |
| 196 | a.value = c |
| 197 | } |
| 198 | return true |
| 199 | } |
| 200 | |
| 201 | // Sub removes the value of b from the current amount, or returns false if underflow would result. |
| 202 | func (a *int64Amount) Sub(b int64Amount) bool { |
| 203 | return a.Add(int64Amount{value: -b.value, scale: b.scale}) |
| 204 | } |
| 205 | |
| 206 | // AsScale adjusts this amount to set a minimum scale, rounding up, and returns true iff no precision |
| 207 | // was lost. (1.1e5).AsScale(5) would return 1.1e5, but (1.1e5).AsScale(6) would return 1e6. |
| 208 | func (a int64Amount) AsScale(scale Scale) (int64Amount, bool) { |
| 209 | if a.scale >= scale { |
| 210 | return a, true |
| 211 | } |
| 212 | result, exact := negativeScaleInt64(a.value, scale-a.scale) |
| 213 | return int64Amount{value: result, scale: scale}, exact |
| 214 | } |
| 215 | |
| 216 | // AsCanonicalBytes accepts a buffer to write the base-10 string value of this field to, and returns |
| 217 | // either that buffer or a larger buffer and the current exponent of the value. The value is adjusted |
| 218 | // until the exponent is a multiple of 3 - i.e. 1.1e5 would return "110", 3. |
| 219 | func (a int64Amount) AsCanonicalBytes(out []byte) (result []byte, exponent int32) { |
| 220 | mantissa := a.value |
| 221 | exponent = int32(a.scale) |
| 222 | |
| 223 | amount, times := removeInt64Factors(mantissa, 10) |
| 224 | exponent += int32(times) |
| 225 | |
| 226 | // make sure exponent is a multiple of 3 |
| 227 | var ok bool |
| 228 | switch exponent % 3 { |
| 229 | case 1, -2: |
| 230 | amount, ok = int64MultiplyScale10(amount) |
| 231 | if !ok { |
| 232 | return infDecAmount{a.AsDec()}.AsCanonicalBytes(out) |
| 233 | } |
| 234 | exponent = exponent - 1 |
| 235 | case 2, -1: |
| 236 | amount, ok = int64MultiplyScale100(amount) |
| 237 | if !ok { |
| 238 | return infDecAmount{a.AsDec()}.AsCanonicalBytes(out) |
| 239 | } |
| 240 | exponent = exponent - 2 |
| 241 | } |
| 242 | return strconv.AppendInt(out, amount, 10), exponent |
| 243 | } |
| 244 | |
| 245 | // AsCanonicalBase1024Bytes accepts a buffer to write the base-1024 string value of this field to, and returns |
| 246 | // either that buffer or a larger buffer and the current exponent of the value. 2048 is 2 * 1024 ^ 1 and would |
| 247 | // return []byte("2048"), 1. |
| 248 | func (a int64Amount) AsCanonicalBase1024Bytes(out []byte) (result []byte, exponent int32) { |
| 249 | value, ok := a.AsScaledInt64(0) |
| 250 | if !ok { |
| 251 | return infDecAmount{a.AsDec()}.AsCanonicalBase1024Bytes(out) |
| 252 | } |
| 253 | amount, exponent := removeInt64Factors(value, 1024) |
| 254 | return strconv.AppendInt(out, amount, 10), exponent |
| 255 | } |
| 256 | |
| 257 | // infDecAmount implements common operations over an inf.Dec that are specific to the quantity |
| 258 | // representation. |
| 259 | type infDecAmount struct { |
| 260 | *inf.Dec |
| 261 | } |
| 262 | |
| 263 | // AsScale adjusts this amount to set a minimum scale, rounding up, and returns true iff no precision |
| 264 | // was lost. (1.1e5).AsScale(5) would return 1.1e5, but (1.1e5).AsScale(6) would return 1e6. |
| 265 | func (a infDecAmount) AsScale(scale Scale) (infDecAmount, bool) { |
| 266 | tmp := &inf.Dec{} |
| 267 | tmp.Round(a.Dec, scale.infScale(), inf.RoundUp) |
| 268 | return infDecAmount{tmp}, tmp.Cmp(a.Dec) == 0 |
| 269 | } |
| 270 | |
| 271 | // AsCanonicalBytes accepts a buffer to write the base-10 string value of this field to, and returns |
| 272 | // either that buffer or a larger buffer and the current exponent of the value. The value is adjusted |
| 273 | // until the exponent is a multiple of 3 - i.e. 1.1e5 would return "110", 3. |
| 274 | func (a infDecAmount) AsCanonicalBytes(out []byte) (result []byte, exponent int32) { |
| 275 | mantissa := a.Dec.UnscaledBig() |
| 276 | exponent = int32(-a.Dec.Scale()) |
| 277 | amount := big.NewInt(0).Set(mantissa) |
| 278 | // move all factors of 10 into the exponent for easy reasoning |
| 279 | amount, times := removeBigIntFactors(amount, bigTen) |
| 280 | exponent += times |
| 281 | |
| 282 | // make sure exponent is a multiple of 3 |
| 283 | for exponent%3 != 0 { |
| 284 | amount.Mul(amount, bigTen) |
| 285 | exponent-- |
| 286 | } |
| 287 | |
| 288 | return append(out, amount.String()...), exponent |
| 289 | } |
| 290 | |
| 291 | // AsCanonicalBase1024Bytes accepts a buffer to write the base-1024 string value of this field to, and returns |
| 292 | // either that buffer or a larger buffer and the current exponent of the value. 2048 is 2 * 1024 ^ 1 and would |
| 293 | // return []byte("2048"), 1. |
| 294 | func (a infDecAmount) AsCanonicalBase1024Bytes(out []byte) (result []byte, exponent int32) { |
| 295 | tmp := &inf.Dec{} |
| 296 | tmp.Round(a.Dec, 0, inf.RoundUp) |
| 297 | amount, exponent := removeBigIntFactors(tmp.UnscaledBig(), big1024) |
| 298 | return append(out, amount.String()...), exponent |
| 299 | } |