blob: 8205f65f82b78abeb7554279d269c3e65132a4b2 [file] [log] [blame]
Brad Fitzpatrick1f41f3c2015-09-08 14:49:05 -07001/*
2Copyright 2014 The Kubernetes Authors All rights reserved.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package api
18
19import (
20 "errors"
21 "fmt"
22 "math/big"
23 "regexp"
24 "strings"
25
Andrew Gerrandf8809402016-06-02 15:06:41 +100026 "gopkg.in/inf.v0"
Brad Fitzpatrick1f41f3c2015-09-08 14:49:05 -070027)
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.
85type 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.
96type Format string
97
98const (
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.
106func 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
114const (
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
120var (
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.
154func 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)
215func 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.)
236func (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.
298func (q *Quantity) String() string {
299 number, suffix := q.Canonicalize()
300 return number + string(suffix)
301}
302
303func (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
311func (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.
320func (q Quantity) MarshalJSON() ([]byte, error) {
321 return []byte(`"` + q.String() + `"`), nil
322}
323
324// UnmarshalJSON implements the json.Unmarshaller interface.
325func (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.
338func 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).
349func 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.
357func (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.
367func (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.
376func (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.
385func (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.
395func (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}