1/*
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	"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.
29type Scale int32
30
31// infScale adapts a Scale value to an inf.Scale value.
32func (s Scale) infScale() inf.Scale {
33	return inf.Scale(-s) // inf.Scale is upside-down
34}
35
36const (
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
48var (
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
58type 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.
64func (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.
78func (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.
95func (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.
104func (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.
112func (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.
162func (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.
202func (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.
208func (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.
219func (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.
248func (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.
259type 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.
265func (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.
274func (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.
294func (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}
300