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