blob: a8866a43e10b0172842b302efb0a9e8179f54a9e [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 "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}