blob: b155a62a45afca1df560a3cf94b6ff30ffb144d5 [file] [log] [blame]
Zack Williamse940c7a2019-08-21 14:25:39 -07001/*
2Copyright 2014 The Kubernetes Authors.
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 resource
18
19import (
20 "bytes"
21 "errors"
22 "fmt"
23 "math/big"
24 "strconv"
25 "strings"
26
27 inf "gopkg.in/inf.v0"
28)
29
30// Quantity is a fixed-point representation of a number.
31// It provides convenient marshaling/unmarshaling in JSON and YAML,
32// in addition to String() and Int64() accessors.
33//
34// The serialization format is:
35//
36// <quantity> ::= <signedNumber><suffix>
37// (Note that <suffix> may be empty, from the "" case in <decimalSI>.)
38// <digit> ::= 0 | 1 | ... | 9
39// <digits> ::= <digit> | <digit><digits>
40// <number> ::= <digits> | <digits>.<digits> | <digits>. | .<digits>
41// <sign> ::= "+" | "-"
42// <signedNumber> ::= <number> | <sign><number>
43// <suffix> ::= <binarySI> | <decimalExponent> | <decimalSI>
44// <binarySI> ::= Ki | Mi | Gi | Ti | Pi | Ei
45// (International System of units; See: http://physics.nist.gov/cuu/Units/binary.html)
46// <decimalSI> ::= m | "" | k | M | G | T | P | E
47// (Note that 1024 = 1Ki but 1000 = 1k; I didn't choose the capitalization.)
48// <decimalExponent> ::= "e" <signedNumber> | "E" <signedNumber>
49//
50// No matter which of the three exponent forms is used, no quantity may represent
51// a number greater than 2^63-1 in magnitude, nor may it have more than 3 decimal
52// places. Numbers larger or more precise will be capped or rounded up.
53// (E.g.: 0.1m will rounded up to 1m.)
54// This may be extended in the future if we require larger or smaller quantities.
55//
56// When a Quantity is parsed from a string, it will remember the type of suffix
57// it had, and will use the same type again when it is serialized.
58//
59// Before serializing, Quantity will be put in "canonical form".
60// This means that Exponent/suffix will be adjusted up or down (with a
61// corresponding increase or decrease in Mantissa) such that:
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// The sign will be omitted unless the number is negative.
66//
67// Examples:
68// 1.5 will be serialized as "1500m"
69// 1.5Gi will be serialized as "1536Mi"
70//
71// Note that the quantity will NEVER be internally represented by a
72// floating point number. That is the whole point of this exercise.
73//
74// Non-canonical values will still parse as long as they are well formed,
75// but will be re-emitted in their canonical form. (So always use canonical
76// form, or don't diff.)
77//
78// This format is intended to make it difficult to use these numbers without
79// writing some sort of special handling code in the hopes that that will
80// cause implementors to also use a fixed point implementation.
81//
82// +protobuf=true
83// +protobuf.embed=string
84// +protobuf.options.marshal=false
85// +protobuf.options.(gogoproto.goproto_stringer)=false
86// +k8s:deepcopy-gen=true
87// +k8s:openapi-gen=true
88type Quantity struct {
89 // i is the quantity in int64 scaled form, if d.Dec == nil
90 i int64Amount
91 // d is the quantity in inf.Dec form if d.Dec != nil
92 d infDecAmount
93 // s is the generated value of this quantity to avoid recalculation
94 s string
95
96 // Change Format at will. See the comment for Canonicalize for
97 // more details.
98 Format
99}
100
101// CanonicalValue allows a quantity amount to be converted to a string.
102type CanonicalValue interface {
103 // AsCanonicalBytes returns a byte array representing the string representation
104 // of the value mantissa and an int32 representing its exponent in base-10. Callers may
105 // pass a byte slice to the method to avoid allocations.
106 AsCanonicalBytes(out []byte) ([]byte, int32)
107 // AsCanonicalBase1024Bytes returns a byte array representing the string representation
108 // of the value mantissa and an int32 representing its exponent in base-1024. Callers
109 // may pass a byte slice to the method to avoid allocations.
110 AsCanonicalBase1024Bytes(out []byte) ([]byte, int32)
111}
112
113// Format lists the three possible formattings of a quantity.
114type Format string
115
116const (
117 DecimalExponent = Format("DecimalExponent") // e.g., 12e6
118 BinarySI = Format("BinarySI") // e.g., 12Mi (12 * 2^20)
119 DecimalSI = Format("DecimalSI") // e.g., 12M (12 * 10^6)
120)
121
122// MustParse turns the given string into a quantity or panics; for tests
123// or others cases where you know the string is valid.
124func MustParse(str string) Quantity {
125 q, err := ParseQuantity(str)
126 if err != nil {
127 panic(fmt.Errorf("cannot parse '%v': %v", str, err))
128 }
129 return q
130}
131
132const (
133 // splitREString is used to separate a number from its suffix; as such,
134 // this is overly permissive, but that's OK-- it will be checked later.
135 splitREString = "^([+-]?[0-9.]+)([eEinumkKMGTP]*[-+]?[0-9]*)$"
136)
137
138var (
139 // Errors that could happen while parsing a string.
140 ErrFormatWrong = errors.New("quantities must match the regular expression '" + splitREString + "'")
141 ErrNumeric = errors.New("unable to parse numeric part of quantity")
142 ErrSuffix = errors.New("unable to parse quantity's suffix")
143)
144
145// parseQuantityString is a fast scanner for quantity values.
146func parseQuantityString(str string) (positive bool, value, num, denom, suffix string, err error) {
147 positive = true
148 pos := 0
149 end := len(str)
150
151 // handle leading sign
152 if pos < end {
153 switch str[0] {
154 case '-':
155 positive = false
156 pos++
157 case '+':
158 pos++
159 }
160 }
161
162 // strip leading zeros
163Zeroes:
164 for i := pos; ; i++ {
165 if i >= end {
166 num = "0"
167 value = num
168 return
169 }
170 switch str[i] {
171 case '0':
172 pos++
173 default:
174 break Zeroes
175 }
176 }
177
178 // extract the numerator
179Num:
180 for i := pos; ; i++ {
181 if i >= end {
182 num = str[pos:end]
183 value = str[0:end]
184 return
185 }
186 switch str[i] {
187 case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
188 default:
189 num = str[pos:i]
190 pos = i
191 break Num
192 }
193 }
194
195 // if we stripped all numerator positions, always return 0
196 if len(num) == 0 {
197 num = "0"
198 }
199
200 // handle a denominator
201 if pos < end && str[pos] == '.' {
202 pos++
203 Denom:
204 for i := pos; ; i++ {
205 if i >= end {
206 denom = str[pos:end]
207 value = str[0:end]
208 return
209 }
210 switch str[i] {
211 case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
212 default:
213 denom = str[pos:i]
214 pos = i
215 break Denom
216 }
217 }
218 // TODO: we currently allow 1.G, but we may not want to in the future.
219 // if len(denom) == 0 {
220 // err = ErrFormatWrong
221 // return
222 // }
223 }
224 value = str[0:pos]
225
226 // grab the elements of the suffix
227 suffixStart := pos
228 for i := pos; ; i++ {
229 if i >= end {
230 suffix = str[suffixStart:end]
231 return
232 }
233 if !strings.ContainsAny(str[i:i+1], "eEinumkKMGTP") {
234 pos = i
235 break
236 }
237 }
238 if pos < end {
239 switch str[pos] {
240 case '-', '+':
241 pos++
242 }
243 }
244Suffix:
245 for i := pos; ; i++ {
246 if i >= end {
247 suffix = str[suffixStart:end]
248 return
249 }
250 switch str[i] {
251 case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
252 default:
253 break Suffix
254 }
255 }
256 // we encountered a non decimal in the Suffix loop, but the last character
257 // was not a valid exponent
258 err = ErrFormatWrong
259 return
260}
261
262// ParseQuantity turns str into a Quantity, or returns an error.
263func ParseQuantity(str string) (Quantity, error) {
264 if len(str) == 0 {
265 return Quantity{}, ErrFormatWrong
266 }
267 if str == "0" {
268 return Quantity{Format: DecimalSI, s: str}, nil
269 }
270
271 positive, value, num, denom, suf, err := parseQuantityString(str)
272 if err != nil {
273 return Quantity{}, err
274 }
275
276 base, exponent, format, ok := quantitySuffixer.interpret(suffix(suf))
277 if !ok {
278 return Quantity{}, ErrSuffix
279 }
280
281 precision := int32(0)
282 scale := int32(0)
283 mantissa := int64(1)
284 switch format {
285 case DecimalExponent, DecimalSI:
286 scale = exponent
287 precision = maxInt64Factors - int32(len(num)+len(denom))
288 case BinarySI:
289 scale = 0
290 switch {
291 case exponent >= 0 && len(denom) == 0:
292 // only handle positive binary numbers with the fast path
293 mantissa = int64(int64(mantissa) << uint64(exponent))
294 // 1Mi (2^20) has ~6 digits of decimal precision, so exponent*3/10 -1 is roughly the precision
295 precision = 15 - int32(len(num)) - int32(float32(exponent)*3/10) - 1
296 default:
297 precision = -1
298 }
299 }
300
301 if precision >= 0 {
302 // if we have a denominator, shift the entire value to the left by the number of places in the
303 // denominator
304 scale -= int32(len(denom))
305 if scale >= int32(Nano) {
306 shifted := num + denom
307
308 var value int64
309 value, err := strconv.ParseInt(shifted, 10, 64)
310 if err != nil {
311 return Quantity{}, ErrNumeric
312 }
313 if result, ok := int64Multiply(value, int64(mantissa)); ok {
314 if !positive {
315 result = -result
316 }
317 // if the number is in canonical form, reuse the string
318 switch format {
319 case BinarySI:
320 if exponent%10 == 0 && (value&0x07 != 0) {
321 return Quantity{i: int64Amount{value: result, scale: Scale(scale)}, Format: format, s: str}, nil
322 }
323 default:
324 if scale%3 == 0 && !strings.HasSuffix(shifted, "000") && shifted[0] != '0' {
325 return Quantity{i: int64Amount{value: result, scale: Scale(scale)}, Format: format, s: str}, nil
326 }
327 }
328 return Quantity{i: int64Amount{value: result, scale: Scale(scale)}, Format: format}, nil
329 }
330 }
331 }
332
333 amount := new(inf.Dec)
334 if _, ok := amount.SetString(value); !ok {
335 return Quantity{}, ErrNumeric
336 }
337
338 // So that no one but us has to think about suffixes, remove it.
339 if base == 10 {
340 amount.SetScale(amount.Scale() + Scale(exponent).infScale())
341 } else if base == 2 {
342 // numericSuffix = 2 ** exponent
343 numericSuffix := big.NewInt(1).Lsh(bigOne, uint(exponent))
344 ub := amount.UnscaledBig()
345 amount.SetUnscaledBig(ub.Mul(ub, numericSuffix))
346 }
347
348 // Cap at min/max bounds.
349 sign := amount.Sign()
350 if sign == -1 {
351 amount.Neg(amount)
352 }
353
354 // This rounds non-zero values up to the minimum representable value, under the theory that
355 // if you want some resources, you should get some resources, even if you asked for way too small
356 // of an amount. Arguably, this should be inf.RoundHalfUp (normal rounding), but that would have
357 // the side effect of rounding values < .5n to zero.
358 if v, ok := amount.Unscaled(); v != int64(0) || !ok {
359 amount.Round(amount, Nano.infScale(), inf.RoundUp)
360 }
361
362 // The max is just a simple cap.
363 // TODO: this prevents accumulating quantities greater than int64, for instance quota across a cluster
364 if format == BinarySI && amount.Cmp(maxAllowed.Dec) > 0 {
365 amount.Set(maxAllowed.Dec)
366 }
367
368 if format == BinarySI && amount.Cmp(decOne) < 0 && amount.Cmp(decZero) > 0 {
369 // This avoids rounding and hopefully confusion, too.
370 format = DecimalSI
371 }
372 if sign == -1 {
373 amount.Neg(amount)
374 }
375
376 return Quantity{d: infDecAmount{amount}, Format: format}, nil
377}
378
379// DeepCopy returns a deep-copy of the Quantity value. Note that the method
380// receiver is a value, so we can mutate it in-place and return it.
381func (q Quantity) DeepCopy() Quantity {
382 if q.d.Dec != nil {
383 tmp := &inf.Dec{}
384 q.d.Dec = tmp.Set(q.d.Dec)
385 }
386 return q
387}
388
389// OpenAPISchemaType is used by the kube-openapi generator when constructing
390// the OpenAPI spec of this type.
391//
392// See: https://github.com/kubernetes/kube-openapi/tree/master/pkg/generators
393func (_ Quantity) OpenAPISchemaType() []string { return []string{"string"} }
394
395// OpenAPISchemaFormat is used by the kube-openapi generator when constructing
396// the OpenAPI spec of this type.
397func (_ Quantity) OpenAPISchemaFormat() string { return "" }
398
399// CanonicalizeBytes returns the canonical form of q and its suffix (see comment on Quantity).
400//
401// Note about BinarySI:
402// * If q.Format is set to BinarySI and q.Amount represents a non-zero value between
403// -1 and +1, it will be emitted as if q.Format were DecimalSI.
404// * Otherwise, if q.Format is set to BinarySI, fractional parts of q.Amount will be
405// rounded up. (1.1i becomes 2i.)
406func (q *Quantity) CanonicalizeBytes(out []byte) (result, suffix []byte) {
407 if q.IsZero() {
408 return zeroBytes, nil
409 }
410
411 var rounded CanonicalValue
412 format := q.Format
413 switch format {
414 case DecimalExponent, DecimalSI:
415 case BinarySI:
416 if q.CmpInt64(-1024) > 0 && q.CmpInt64(1024) < 0 {
417 // This avoids rounding and hopefully confusion, too.
418 format = DecimalSI
419 } else {
420 var exact bool
421 if rounded, exact = q.AsScale(0); !exact {
422 // Don't lose precision-- show as DecimalSI
423 format = DecimalSI
424 }
425 }
426 default:
427 format = DecimalExponent
428 }
429
430 // TODO: If BinarySI formatting is requested but would cause rounding, upgrade to
431 // one of the other formats.
432 switch format {
433 case DecimalExponent, DecimalSI:
434 number, exponent := q.AsCanonicalBytes(out)
435 suffix, _ := quantitySuffixer.constructBytes(10, exponent, format)
436 return number, suffix
437 default:
438 // format must be BinarySI
439 number, exponent := rounded.AsCanonicalBase1024Bytes(out)
440 suffix, _ := quantitySuffixer.constructBytes(2, exponent*10, format)
441 return number, suffix
442 }
443}
444
445// AsInt64 returns a representation of the current value as an int64 if a fast conversion
446// is possible. If false is returned, callers must use the inf.Dec form of this quantity.
447func (q *Quantity) AsInt64() (int64, bool) {
448 if q.d.Dec != nil {
449 return 0, false
450 }
451 return q.i.AsInt64()
452}
453
454// ToDec promotes the quantity in place to use an inf.Dec representation and returns itself.
455func (q *Quantity) ToDec() *Quantity {
456 if q.d.Dec == nil {
457 q.d.Dec = q.i.AsDec()
458 q.i = int64Amount{}
459 }
460 return q
461}
462
463// AsDec returns the quantity as represented by a scaled inf.Dec.
464func (q *Quantity) AsDec() *inf.Dec {
465 if q.d.Dec != nil {
466 return q.d.Dec
467 }
468 q.d.Dec = q.i.AsDec()
469 q.i = int64Amount{}
470 return q.d.Dec
471}
472
473// AsCanonicalBytes returns the canonical byte representation of this quantity as a mantissa
474// and base 10 exponent. The out byte slice may be passed to the method to avoid an extra
475// allocation.
476func (q *Quantity) AsCanonicalBytes(out []byte) (result []byte, exponent int32) {
477 if q.d.Dec != nil {
478 return q.d.AsCanonicalBytes(out)
479 }
480 return q.i.AsCanonicalBytes(out)
481}
482
483// IsZero returns true if the quantity is equal to zero.
484func (q *Quantity) IsZero() bool {
485 if q.d.Dec != nil {
486 return q.d.Dec.Sign() == 0
487 }
488 return q.i.value == 0
489}
490
491// Sign returns 0 if the quantity is zero, -1 if the quantity is less than zero, or 1 if the
492// quantity is greater than zero.
493func (q *Quantity) Sign() int {
494 if q.d.Dec != nil {
495 return q.d.Dec.Sign()
496 }
497 return q.i.Sign()
498}
499
500// AsScale returns the current value, rounded up to the provided scale, and returns
501// false if the scale resulted in a loss of precision.
502func (q *Quantity) AsScale(scale Scale) (CanonicalValue, bool) {
503 if q.d.Dec != nil {
504 return q.d.AsScale(scale)
505 }
506 return q.i.AsScale(scale)
507}
508
509// RoundUp updates the quantity to the provided scale, ensuring that the value is at
510// least 1. False is returned if the rounding operation resulted in a loss of precision.
511// Negative numbers are rounded away from zero (-9 scale 1 rounds to -10).
512func (q *Quantity) RoundUp(scale Scale) bool {
513 if q.d.Dec != nil {
514 q.s = ""
515 d, exact := q.d.AsScale(scale)
516 q.d = d
517 return exact
518 }
519 // avoid clearing the string value if we have already calculated it
520 if q.i.scale >= scale {
521 return true
522 }
523 q.s = ""
524 i, exact := q.i.AsScale(scale)
525 q.i = i
526 return exact
527}
528
529// Add adds the provide y quantity to the current value. If the current value is zero,
530// the format of the quantity will be updated to the format of y.
531func (q *Quantity) Add(y Quantity) {
532 q.s = ""
533 if q.d.Dec == nil && y.d.Dec == nil {
534 if q.i.value == 0 {
535 q.Format = y.Format
536 }
537 if q.i.Add(y.i) {
538 return
539 }
540 } else if q.IsZero() {
541 q.Format = y.Format
542 }
543 q.ToDec().d.Dec.Add(q.d.Dec, y.AsDec())
544}
545
546// Sub subtracts the provided quantity from the current value in place. If the current
547// value is zero, the format of the quantity will be updated to the format of y.
548func (q *Quantity) Sub(y Quantity) {
549 q.s = ""
550 if q.IsZero() {
551 q.Format = y.Format
552 }
553 if q.d.Dec == nil && y.d.Dec == nil && q.i.Sub(y.i) {
554 return
555 }
556 q.ToDec().d.Dec.Sub(q.d.Dec, y.AsDec())
557}
558
559// Cmp returns 0 if the quantity is equal to y, -1 if the quantity is less than y, or 1 if the
560// quantity is greater than y.
561func (q *Quantity) Cmp(y Quantity) int {
562 if q.d.Dec == nil && y.d.Dec == nil {
563 return q.i.Cmp(y.i)
564 }
565 return q.AsDec().Cmp(y.AsDec())
566}
567
568// CmpInt64 returns 0 if the quantity is equal to y, -1 if the quantity is less than y, or 1 if the
569// quantity is greater than y.
570func (q *Quantity) CmpInt64(y int64) int {
571 if q.d.Dec != nil {
572 return q.d.Dec.Cmp(inf.NewDec(y, inf.Scale(0)))
573 }
574 return q.i.Cmp(int64Amount{value: y})
575}
576
577// Neg sets quantity to be the negative value of itself.
578func (q *Quantity) Neg() {
579 q.s = ""
580 if q.d.Dec == nil {
581 q.i.value = -q.i.value
582 return
583 }
584 q.d.Dec.Neg(q.d.Dec)
585}
586
587// int64QuantityExpectedBytes is the expected width in bytes of the canonical string representation
588// of most Quantity values.
589const int64QuantityExpectedBytes = 18
590
591// String formats the Quantity as a string, caching the result if not calculated.
592// String is an expensive operation and caching this result significantly reduces the cost of
593// normal parse / marshal operations on Quantity.
594func (q *Quantity) String() string {
595 if len(q.s) == 0 {
596 result := make([]byte, 0, int64QuantityExpectedBytes)
597 number, suffix := q.CanonicalizeBytes(result)
598 number = append(number, suffix...)
599 q.s = string(number)
600 }
601 return q.s
602}
603
604// MarshalJSON implements the json.Marshaller interface.
605func (q Quantity) MarshalJSON() ([]byte, error) {
606 if len(q.s) > 0 {
607 out := make([]byte, len(q.s)+2)
608 out[0], out[len(out)-1] = '"', '"'
609 copy(out[1:], q.s)
610 return out, nil
611 }
612 result := make([]byte, int64QuantityExpectedBytes, int64QuantityExpectedBytes)
613 result[0] = '"'
614 number, suffix := q.CanonicalizeBytes(result[1:1])
615 // if the same slice was returned to us that we passed in, avoid another allocation by copying number into
616 // the source slice and returning that
617 if len(number) > 0 && &number[0] == &result[1] && (len(number)+len(suffix)+2) <= int64QuantityExpectedBytes {
618 number = append(number, suffix...)
619 number = append(number, '"')
620 return result[:1+len(number)], nil
621 }
622 // if CanonicalizeBytes needed more space than our slice provided, we may need to allocate again so use
623 // append
624 result = result[:1]
625 result = append(result, number...)
626 result = append(result, suffix...)
627 result = append(result, '"')
628 return result, nil
629}
630
631// UnmarshalJSON implements the json.Unmarshaller interface.
632// TODO: Remove support for leading/trailing whitespace
633func (q *Quantity) UnmarshalJSON(value []byte) error {
634 l := len(value)
635 if l == 4 && bytes.Equal(value, []byte("null")) {
636 q.d.Dec = nil
637 q.i = int64Amount{}
638 return nil
639 }
640 if l >= 2 && value[0] == '"' && value[l-1] == '"' {
641 value = value[1 : l-1]
642 }
643
644 parsed, err := ParseQuantity(strings.TrimSpace(string(value)))
645 if err != nil {
646 return err
647 }
648
649 // This copy is safe because parsed will not be referred to again.
650 *q = parsed
651 return nil
652}
653
654// NewQuantity returns a new Quantity representing the given
655// value in the given format.
656func NewQuantity(value int64, format Format) *Quantity {
657 return &Quantity{
658 i: int64Amount{value: value},
659 Format: format,
660 }
661}
662
663// NewMilliQuantity returns a new Quantity representing the given
664// value * 1/1000 in the given format. Note that BinarySI formatting
665// will round fractional values, and will be changed to DecimalSI for
666// values x where (-1 < x < 1) && (x != 0).
667func NewMilliQuantity(value int64, format Format) *Quantity {
668 return &Quantity{
669 i: int64Amount{value: value, scale: -3},
670 Format: format,
671 }
672}
673
674// NewScaledQuantity returns a new Quantity representing the given
675// value * 10^scale in DecimalSI format.
676func NewScaledQuantity(value int64, scale Scale) *Quantity {
677 return &Quantity{
678 i: int64Amount{value: value, scale: scale},
679 Format: DecimalSI,
680 }
681}
682
683// Value returns the value of q; any fractional part will be lost.
684func (q *Quantity) Value() int64 {
685 return q.ScaledValue(0)
686}
687
688// MilliValue returns the value of ceil(q * 1000); this could overflow an int64;
689// if that's a concern, call Value() first to verify the number is small enough.
690func (q *Quantity) MilliValue() int64 {
691 return q.ScaledValue(Milli)
692}
693
694// ScaledValue returns the value of ceil(q * 10^scale); this could overflow an int64.
695// To detect overflow, call Value() first and verify the expected magnitude.
696func (q *Quantity) ScaledValue(scale Scale) int64 {
697 if q.d.Dec == nil {
698 i, _ := q.i.AsScaledInt64(scale)
699 return i
700 }
701 dec := q.d.Dec
702 return scaledValue(dec.UnscaledBig(), int(dec.Scale()), int(scale.infScale()))
703}
704
705// Set sets q's value to be value.
706func (q *Quantity) Set(value int64) {
707 q.SetScaled(value, 0)
708}
709
710// SetMilli sets q's value to be value * 1/1000.
711func (q *Quantity) SetMilli(value int64) {
712 q.SetScaled(value, Milli)
713}
714
715// SetScaled sets q's value to be value * 10^scale
716func (q *Quantity) SetScaled(value int64, scale Scale) {
717 q.s = ""
718 q.d.Dec = nil
719 q.i = int64Amount{value: value, scale: scale}
720}
721
722// Copy is a convenience function that makes a deep copy for you. Non-deep
723// copies of quantities share pointers and you will regret that.
724func (q *Quantity) Copy() *Quantity {
725 if q.d.Dec == nil {
726 return &Quantity{
727 s: q.s,
728 i: q.i,
729 Format: q.Format,
730 }
731 }
732 tmp := &inf.Dec{}
733 return &Quantity{
734 s: q.s,
735 d: infDecAmount{tmp.Set(q.d.Dec)},
736 Format: q.Format,
737 }
738}