Brad Fitzpatrick | 1f41f3c | 2015-09-08 14:49:05 -0700 | [diff] [blame] | 1 | /* |
| 2 | Copyright 2014 The Kubernetes Authors All rights reserved. |
| 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 api |
| 18 | |
| 19 | import ( |
| 20 | "errors" |
| 21 | "fmt" |
| 22 | "math/big" |
| 23 | "regexp" |
| 24 | "strings" |
| 25 | |
Andrew Gerrand | f880940 | 2016-06-02 15:06:41 +1000 | [diff] [blame] | 26 | "gopkg.in/inf.v0" |
Brad Fitzpatrick | 1f41f3c | 2015-09-08 14:49:05 -0700 | [diff] [blame] | 27 | ) |
| 28 | |
| 29 | // Quantity is a fixed-point representation of a number. |
| 30 | // It provides convenient marshaling/unmarshaling in JSON and YAML, |
| 31 | // in addition to String() and Int64() accessors. |
| 32 | // |
| 33 | // The serialization format is: |
| 34 | // |
| 35 | // <quantity> ::= <signedNumber><suffix> |
| 36 | // (Note that <suffix> may be empty, from the "" case in <decimalSI>.) |
| 37 | // <digit> ::= 0 | 1 | ... | 9 |
| 38 | // <digits> ::= <digit> | <digit><digits> |
| 39 | // <number> ::= <digits> | <digits>.<digits> | <digits>. | .<digits> |
| 40 | // <sign> ::= "+" | "-" |
| 41 | // <signedNumber> ::= <number> | <sign><number> |
| 42 | // <suffix> ::= <binarySI> | <decimalExponent> | <decimalSI> |
| 43 | // <binarySI> ::= Ki | Mi | Gi | Ti | Pi | Ei |
| 44 | // (International System of units; See: http://physics.nist.gov/cuu/Units/binary.html) |
| 45 | // <decimalSI> ::= m | "" | k | M | G | T | P | E |
| 46 | // (Note that 1024 = 1Ki but 1000 = 1k; I didn't choose the capitalization.) |
| 47 | // <decimalExponent> ::= "e" <signedNumber> | "E" <signedNumber> |
| 48 | // |
| 49 | // No matter which of the three exponent forms is used, no quantity may represent |
| 50 | // a number greater than 2^63-1 in magnitude, nor may it have more than 3 decimal |
| 51 | // places. Numbers larger or more precise will be capped or rounded up. |
| 52 | // (E.g.: 0.1m will rounded up to 1m.) |
| 53 | // This may be extended in the future if we require larger or smaller quantities. |
| 54 | // |
| 55 | // When a Quantity is parsed from a string, it will remember the type of suffix |
| 56 | // it had, and will use the same type again when it is serialized. |
| 57 | // |
| 58 | // Before serializing, Quantity will be put in "canonical form". |
| 59 | // This means that Exponent/suffix will be adjusted up or down (with a |
| 60 | // corresponding increase or decrease in Mantissa) such that: |
| 61 | // a. No precision is lost |
| 62 | // b. No fractional digits will be emitted |
| 63 | // c. The exponent (or suffix) is as large as possible. |
| 64 | // The sign will be omitted unless the number is negative. |
| 65 | // |
| 66 | // Examples: |
| 67 | // 1.5 will be serialized as "1500m" |
| 68 | // 1.5Gi will be serialized as "1536Mi" |
| 69 | // |
| 70 | // NOTE: We reserve the right to amend this canonical format, perhaps to |
| 71 | // allow 1.5 to be canonical. |
| 72 | // TODO: Remove above disclaimer after all bikeshedding about format is over, |
| 73 | // or after March 2015. |
| 74 | // |
| 75 | // Note that the quantity will NEVER be internally represented by a |
| 76 | // floating point number. That is the whole point of this exercise. |
| 77 | // |
| 78 | // Non-canonical values will still parse as long as they are well formed, |
| 79 | // but will be re-emitted in their canonical form. (So always use canonical |
| 80 | // form, or don't diff.) |
| 81 | // |
| 82 | // This format is intended to make it difficult to use these numbers without |
| 83 | // writing some sort of special handling code in the hopes that that will |
| 84 | // cause implementors to also use a fixed point implementation. |
| 85 | type Quantity struct { |
| 86 | // Amount is public, so you can manipulate it if the accessor |
| 87 | // functions are not sufficient. |
| 88 | Amount *inf.Dec |
| 89 | |
| 90 | // Change Format at will. See the comment for Canonicalize for |
| 91 | // more details. |
| 92 | Format |
| 93 | } |
| 94 | |
| 95 | // Format lists the three possible formattings of a quantity. |
| 96 | type Format string |
| 97 | |
| 98 | const ( |
| 99 | DecimalExponent = Format("DecimalExponent") // e.g., 12e6 |
| 100 | BinarySI = Format("BinarySI") // e.g., 12Mi (12 * 2^20) |
| 101 | DecimalSI = Format("DecimalSI") // e.g., 12M (12 * 10^6) |
| 102 | ) |
| 103 | |
| 104 | // MustParse turns the given string into a quantity or panics; for tests |
| 105 | // or others cases where you know the string is valid. |
| 106 | func MustParse(str string) Quantity { |
| 107 | q, err := ParseQuantity(str) |
| 108 | if err != nil { |
| 109 | panic(fmt.Errorf("cannot parse '%v': %v", str, err)) |
| 110 | } |
| 111 | return *q |
| 112 | } |
| 113 | |
| 114 | const ( |
| 115 | // splitREString is used to separate a number from its suffix; as such, |
| 116 | // this is overly permissive, but that's OK-- it will be checked later. |
| 117 | splitREString = "^([+-]?[0-9.]+)([eEimkKMGTP]*[-+]?[0-9]*)$" |
| 118 | ) |
| 119 | |
| 120 | var ( |
| 121 | // splitRE is used to get the various parts of a number. |
| 122 | splitRE = regexp.MustCompile(splitREString) |
| 123 | |
| 124 | // Errors that could happen while parsing a string. |
| 125 | ErrFormatWrong = errors.New("quantities must match the regular expression '" + splitREString + "'") |
| 126 | ErrNumeric = errors.New("unable to parse numeric part of quantity") |
| 127 | ErrSuffix = errors.New("unable to parse quantity's suffix") |
| 128 | |
| 129 | // Commonly needed big.Int values-- treat as read only! |
| 130 | bigTen = big.NewInt(10) |
| 131 | bigZero = big.NewInt(0) |
| 132 | bigOne = big.NewInt(1) |
| 133 | bigThousand = big.NewInt(1000) |
| 134 | big1024 = big.NewInt(1024) |
| 135 | |
| 136 | // Commonly needed inf.Dec values-- treat as read only! |
| 137 | decZero = inf.NewDec(0, 0) |
| 138 | decOne = inf.NewDec(1, 0) |
| 139 | decMinusOne = inf.NewDec(-1, 0) |
| 140 | decThousand = inf.NewDec(1000, 0) |
| 141 | dec1024 = inf.NewDec(1024, 0) |
| 142 | decMinus1024 = inf.NewDec(-1024, 0) |
| 143 | |
| 144 | // Largest (in magnitude) number allowed. |
| 145 | maxAllowed = inf.NewDec((1<<63)-1, 0) // == max int64 |
| 146 | |
| 147 | // The maximum value we can represent milli-units for. |
| 148 | // Compare with the return value of Quantity.Value() to |
| 149 | // see if it's safe to use Quantity.MilliValue(). |
| 150 | MaxMilliValue = int64(((1 << 63) - 1) / 1000) |
| 151 | ) |
| 152 | |
| 153 | // ParseQuantity turns str into a Quantity, or returns an error. |
| 154 | func ParseQuantity(str string) (*Quantity, error) { |
| 155 | parts := splitRE.FindStringSubmatch(strings.TrimSpace(str)) |
| 156 | // regexp returns are entire match, followed by an entry for each () section. |
| 157 | if len(parts) != 3 { |
| 158 | return nil, ErrFormatWrong |
| 159 | } |
| 160 | |
| 161 | amount := new(inf.Dec) |
| 162 | if _, ok := amount.SetString(parts[1]); !ok { |
| 163 | return nil, ErrNumeric |
| 164 | } |
| 165 | |
| 166 | base, exponent, format, ok := quantitySuffixer.interpret(suffix(parts[2])) |
| 167 | if !ok { |
| 168 | return nil, ErrSuffix |
| 169 | } |
| 170 | |
| 171 | // So that no one but us has to think about suffixes, remove it. |
| 172 | if base == 10 { |
| 173 | amount.SetScale(amount.Scale() + inf.Scale(-exponent)) |
| 174 | } else if base == 2 { |
| 175 | // numericSuffix = 2 ** exponent |
| 176 | numericSuffix := big.NewInt(1).Lsh(bigOne, uint(exponent)) |
| 177 | ub := amount.UnscaledBig() |
| 178 | amount.SetUnscaledBig(ub.Mul(ub, numericSuffix)) |
| 179 | } |
| 180 | |
| 181 | // Cap at min/max bounds. |
| 182 | sign := amount.Sign() |
| 183 | if sign == -1 { |
| 184 | amount.Neg(amount) |
| 185 | } |
| 186 | // This rounds non-zero values up to the minimum representable |
| 187 | // value, under the theory that if you want some resources, you |
| 188 | // should get some resources, even if you asked for way too small |
| 189 | // of an amount. |
| 190 | // Arguably, this should be inf.RoundHalfUp (normal rounding), but |
| 191 | // that would have the side effect of rounding values < .5m to zero. |
| 192 | if v, ok := amount.Unscaled(); v != int64(0) || !ok { |
| 193 | amount.Round(amount, 3, inf.RoundUp) |
| 194 | } |
| 195 | |
| 196 | // The max is just a simple cap. |
| 197 | if amount.Cmp(maxAllowed) > 0 { |
| 198 | amount.Set(maxAllowed) |
| 199 | } |
| 200 | if format == BinarySI && amount.Cmp(decOne) < 0 && amount.Cmp(decZero) > 0 { |
| 201 | // This avoids rounding and hopefully confusion, too. |
| 202 | format = DecimalSI |
| 203 | } |
| 204 | if sign == -1 { |
| 205 | amount.Neg(amount) |
| 206 | } |
| 207 | |
| 208 | return &Quantity{amount, format}, nil |
| 209 | } |
| 210 | |
| 211 | // removeFactors divides in a loop; the return values have the property that |
| 212 | // d == result * factor ^ times |
| 213 | // d may be modified in place. |
| 214 | // If d == 0, then the return values will be (0, 0) |
| 215 | func removeFactors(d, factor *big.Int) (result *big.Int, times int) { |
| 216 | q := big.NewInt(0) |
| 217 | m := big.NewInt(0) |
| 218 | for d.Cmp(bigZero) != 0 { |
| 219 | q.DivMod(d, factor, m) |
| 220 | if m.Cmp(bigZero) != 0 { |
| 221 | break |
| 222 | } |
| 223 | times++ |
| 224 | d, q = q, d |
| 225 | } |
| 226 | return d, times |
| 227 | } |
| 228 | |
| 229 | // Canonicalize returns the canonical form of q and its suffix (see comment on Quantity). |
| 230 | // |
| 231 | // Note about BinarySI: |
| 232 | // * If q.Format is set to BinarySI and q.Amount represents a non-zero value between |
| 233 | // -1 and +1, it will be emitted as if q.Format were DecimalSI. |
| 234 | // * Otherwise, if q.Format is set to BinarySI, frational parts of q.Amount will be |
| 235 | // rounded up. (1.1i becomes 2i.) |
| 236 | func (q *Quantity) Canonicalize() (string, suffix) { |
| 237 | if q.Amount == nil { |
| 238 | return "0", "" |
| 239 | } |
| 240 | |
| 241 | // zero is zero always |
| 242 | if q.Amount.Cmp(&inf.Dec{}) == 0 { |
| 243 | return "0", "" |
| 244 | } |
| 245 | |
| 246 | format := q.Format |
| 247 | switch format { |
| 248 | case DecimalExponent, DecimalSI: |
| 249 | case BinarySI: |
| 250 | if q.Amount.Cmp(decMinus1024) > 0 && q.Amount.Cmp(dec1024) < 0 { |
| 251 | // This avoids rounding and hopefully confusion, too. |
| 252 | format = DecimalSI |
| 253 | } else { |
| 254 | tmp := &inf.Dec{} |
| 255 | tmp.Round(q.Amount, 0, inf.RoundUp) |
| 256 | if tmp.Cmp(q.Amount) != 0 { |
| 257 | // Don't lose precision-- show as DecimalSI |
| 258 | format = DecimalSI |
| 259 | } |
| 260 | } |
| 261 | default: |
| 262 | format = DecimalExponent |
| 263 | } |
| 264 | |
| 265 | // TODO: If BinarySI formatting is requested but would cause rounding, upgrade to |
| 266 | // one of the other formats. |
| 267 | switch format { |
| 268 | case DecimalExponent, DecimalSI: |
| 269 | mantissa := q.Amount.UnscaledBig() |
| 270 | exponent := int(-q.Amount.Scale()) |
| 271 | amount := big.NewInt(0).Set(mantissa) |
| 272 | // move all factors of 10 into the exponent for easy reasoning |
| 273 | amount, times := removeFactors(amount, bigTen) |
| 274 | exponent += times |
| 275 | |
| 276 | // make sure exponent is a multiple of 3 |
| 277 | for exponent%3 != 0 { |
| 278 | amount.Mul(amount, bigTen) |
| 279 | exponent-- |
| 280 | } |
| 281 | |
| 282 | suffix, _ := quantitySuffixer.construct(10, exponent, format) |
| 283 | number := amount.String() |
| 284 | return number, suffix |
| 285 | case BinarySI: |
| 286 | tmp := &inf.Dec{} |
| 287 | tmp.Round(q.Amount, 0, inf.RoundUp) |
| 288 | |
| 289 | amount, exponent := removeFactors(tmp.UnscaledBig(), big1024) |
| 290 | suffix, _ := quantitySuffixer.construct(2, exponent*10, format) |
| 291 | number := amount.String() |
| 292 | return number, suffix |
| 293 | } |
| 294 | return "0", "" |
| 295 | } |
| 296 | |
| 297 | // String formats the Quantity as a string. |
| 298 | func (q *Quantity) String() string { |
| 299 | number, suffix := q.Canonicalize() |
| 300 | return number + string(suffix) |
| 301 | } |
| 302 | |
| 303 | func (q *Quantity) Add(y Quantity) error { |
| 304 | if q.Format != y.Format { |
| 305 | return fmt.Errorf("format mismatch: %v vs. %v", q.Format, y.Format) |
| 306 | } |
| 307 | q.Amount.Add(q.Amount, y.Amount) |
| 308 | return nil |
| 309 | } |
| 310 | |
| 311 | func (q *Quantity) Sub(y Quantity) error { |
| 312 | if q.Format != y.Format { |
| 313 | return fmt.Errorf("format mismatch: %v vs. %v", q.Format, y.Format) |
| 314 | } |
| 315 | q.Amount.Sub(q.Amount, y.Amount) |
| 316 | return nil |
| 317 | } |
| 318 | |
| 319 | // MarshalJSON implements the json.Marshaller interface. |
| 320 | func (q Quantity) MarshalJSON() ([]byte, error) { |
| 321 | return []byte(`"` + q.String() + `"`), nil |
| 322 | } |
| 323 | |
| 324 | // UnmarshalJSON implements the json.Unmarshaller interface. |
| 325 | func (q *Quantity) UnmarshalJSON(value []byte) error { |
| 326 | str := string(value) |
| 327 | parsed, err := ParseQuantity(strings.Trim(str, `"`)) |
| 328 | if err != nil { |
| 329 | return err |
| 330 | } |
| 331 | // This copy is safe because parsed will not be referred to again. |
| 332 | *q = *parsed |
| 333 | return nil |
| 334 | } |
| 335 | |
| 336 | // NewQuantity returns a new Quantity representing the given |
| 337 | // value in the given format. |
| 338 | func NewQuantity(value int64, format Format) *Quantity { |
| 339 | return &Quantity{ |
| 340 | Amount: inf.NewDec(value, 0), |
| 341 | Format: format, |
| 342 | } |
| 343 | } |
| 344 | |
| 345 | // NewMilliQuantity returns a new Quantity representing the given |
| 346 | // value * 1/1000 in the given format. Note that BinarySI formatting |
| 347 | // will round fractional values, and will be changed to DecimalSI for |
| 348 | // values x where (-1 < x < 1) && (x != 0). |
| 349 | func NewMilliQuantity(value int64, format Format) *Quantity { |
| 350 | return &Quantity{ |
| 351 | Amount: inf.NewDec(value, 3), |
| 352 | Format: format, |
| 353 | } |
| 354 | } |
| 355 | |
| 356 | // Value returns the value of q; any fractional part will be lost. |
| 357 | func (q *Quantity) Value() int64 { |
| 358 | if q.Amount == nil { |
| 359 | return 0 |
| 360 | } |
| 361 | tmp := &inf.Dec{} |
| 362 | return tmp.Round(q.Amount, 0, inf.RoundUp).UnscaledBig().Int64() |
| 363 | } |
| 364 | |
| 365 | // MilliValue returns the value of q * 1000; this could overflow an int64; |
| 366 | // if that's a concern, call Value() first to verify the number is small enough. |
| 367 | func (q *Quantity) MilliValue() int64 { |
| 368 | if q.Amount == nil { |
| 369 | return 0 |
| 370 | } |
| 371 | tmp := &inf.Dec{} |
| 372 | return tmp.Round(tmp.Mul(q.Amount, decThousand), 0, inf.RoundUp).UnscaledBig().Int64() |
| 373 | } |
| 374 | |
| 375 | // Set sets q's value to be value. |
| 376 | func (q *Quantity) Set(value int64) { |
| 377 | if q.Amount == nil { |
| 378 | q.Amount = &inf.Dec{} |
| 379 | } |
| 380 | q.Amount.SetUnscaled(value) |
| 381 | q.Amount.SetScale(0) |
| 382 | } |
| 383 | |
| 384 | // SetMilli sets q's value to be value * 1/1000. |
| 385 | func (q *Quantity) SetMilli(value int64) { |
| 386 | if q.Amount == nil { |
| 387 | q.Amount = &inf.Dec{} |
| 388 | } |
| 389 | q.Amount.SetUnscaled(value) |
| 390 | q.Amount.SetScale(3) |
| 391 | } |
| 392 | |
| 393 | // Copy is a convenience function that makes a deep copy for you. Non-deep |
| 394 | // copies of quantities share pointers and you will regret that. |
| 395 | func (q *Quantity) Copy() *Quantity { |
| 396 | if q.Amount == nil { |
| 397 | return NewQuantity(0, q.Format) |
| 398 | } |
| 399 | tmp := &inf.Dec{} |
| 400 | return &Quantity{ |
| 401 | Amount: tmp.Set(q.Amount), |
| 402 | Format: q.Format, |
| 403 | } |
| 404 | } |