You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
299 lines
8.6 KiB
299 lines
8.6 KiB
/* |
|
Copyright 2014 The Kubernetes Authors. |
|
|
|
Licensed under the Apache License, Version 2.0 (the "License"); |
|
you may not use this file except in compliance with the License. |
|
You may obtain a copy of the License at |
|
|
|
http://www.apache.org/licenses/LICENSE-2.0 |
|
|
|
Unless required by applicable law or agreed to in writing, software |
|
distributed under the License is distributed on an "AS IS" BASIS, |
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
See the License for the specific language governing permissions and |
|
limitations under the License. |
|
*/ |
|
|
|
package resource |
|
|
|
import ( |
|
"math/big" |
|
"strconv" |
|
|
|
inf "gopkg.in/inf.v0" |
|
) |
|
|
|
// Scale is used for getting and setting the base-10 scaled value. |
|
// Base-2 scales are omitted for mathematical simplicity. |
|
// See Quantity.ScaledValue for more details. |
|
type Scale int32 |
|
|
|
// infScale adapts a Scale value to an inf.Scale value. |
|
func (s Scale) infScale() inf.Scale { |
|
return inf.Scale(-s) // inf.Scale is upside-down |
|
} |
|
|
|
const ( |
|
Nano Scale = -9 |
|
Micro Scale = -6 |
|
Milli Scale = -3 |
|
Kilo Scale = 3 |
|
Mega Scale = 6 |
|
Giga Scale = 9 |
|
Tera Scale = 12 |
|
Peta Scale = 15 |
|
Exa Scale = 18 |
|
) |
|
|
|
var ( |
|
Zero = int64Amount{} |
|
|
|
// Used by quantity strings - treat as read only |
|
zeroBytes = []byte("0") |
|
) |
|
|
|
// int64Amount represents a fixed precision numerator and arbitrary scale exponent. It is faster |
|
// than operations on inf.Dec for values that can be represented as int64. |
|
// +k8s:openapi-gen=true |
|
type int64Amount struct { |
|
value int64 |
|
scale Scale |
|
} |
|
|
|
// Sign returns 0 if the value is zero, -1 if it is less than 0, or 1 if it is greater than 0. |
|
func (a int64Amount) Sign() int { |
|
switch { |
|
case a.value == 0: |
|
return 0 |
|
case a.value > 0: |
|
return 1 |
|
default: |
|
return -1 |
|
} |
|
} |
|
|
|
// AsInt64 returns the current amount as an int64 at scale 0, or false if the value cannot be |
|
// represented in an int64 OR would result in a loss of precision. This method is intended as |
|
// an optimization to avoid calling AsDec. |
|
func (a int64Amount) AsInt64() (int64, bool) { |
|
if a.scale == 0 { |
|
return a.value, true |
|
} |
|
if a.scale < 0 { |
|
// TODO: attempt to reduce factors, although it is assumed that factors are reduced prior |
|
// to the int64Amount being created. |
|
return 0, false |
|
} |
|
return positiveScaleInt64(a.value, a.scale) |
|
} |
|
|
|
// AsScaledInt64 returns an int64 representing the value of this amount at the specified scale, |
|
// rounding up, or false if that would result in overflow. (1e20).AsScaledInt64(1) would result |
|
// in overflow because 1e19 is not representable as an int64. Note that setting a scale larger |
|
// than the current value may result in loss of precision - i.e. (1e-6).AsScaledInt64(0) would |
|
// return 1, because 0.000001 is rounded up to 1. |
|
func (a int64Amount) AsScaledInt64(scale Scale) (result int64, ok bool) { |
|
if a.scale < scale { |
|
result, _ = negativeScaleInt64(a.value, scale-a.scale) |
|
return result, true |
|
} |
|
return positiveScaleInt64(a.value, a.scale-scale) |
|
} |
|
|
|
// AsDec returns an inf.Dec representation of this value. |
|
func (a int64Amount) AsDec() *inf.Dec { |
|
var base inf.Dec |
|
base.SetUnscaled(a.value) |
|
base.SetScale(inf.Scale(-a.scale)) |
|
return &base |
|
} |
|
|
|
// Cmp returns 0 if a and b are equal, 1 if a is greater than b, or -1 if a is less than b. |
|
func (a int64Amount) Cmp(b int64Amount) int { |
|
switch { |
|
case a.scale == b.scale: |
|
// compare only the unscaled portion |
|
case a.scale > b.scale: |
|
result, remainder, exact := divideByScaleInt64(b.value, a.scale-b.scale) |
|
if !exact { |
|
return a.AsDec().Cmp(b.AsDec()) |
|
} |
|
if result == a.value { |
|
switch { |
|
case remainder == 0: |
|
return 0 |
|
case remainder > 0: |
|
return -1 |
|
default: |
|
return 1 |
|
} |
|
} |
|
b.value = result |
|
default: |
|
result, remainder, exact := divideByScaleInt64(a.value, b.scale-a.scale) |
|
if !exact { |
|
return a.AsDec().Cmp(b.AsDec()) |
|
} |
|
if result == b.value { |
|
switch { |
|
case remainder == 0: |
|
return 0 |
|
case remainder > 0: |
|
return 1 |
|
default: |
|
return -1 |
|
} |
|
} |
|
a.value = result |
|
} |
|
|
|
switch { |
|
case a.value == b.value: |
|
return 0 |
|
case a.value < b.value: |
|
return -1 |
|
default: |
|
return 1 |
|
} |
|
} |
|
|
|
// Add adds two int64Amounts together, matching scales. It will return false and not mutate |
|
// a if overflow or underflow would result. |
|
func (a *int64Amount) Add(b int64Amount) bool { |
|
switch { |
|
case b.value == 0: |
|
return true |
|
case a.value == 0: |
|
a.value = b.value |
|
a.scale = b.scale |
|
return true |
|
case a.scale == b.scale: |
|
c, ok := int64Add(a.value, b.value) |
|
if !ok { |
|
return false |
|
} |
|
a.value = c |
|
case a.scale > b.scale: |
|
c, ok := positiveScaleInt64(a.value, a.scale-b.scale) |
|
if !ok { |
|
return false |
|
} |
|
c, ok = int64Add(c, b.value) |
|
if !ok { |
|
return false |
|
} |
|
a.scale = b.scale |
|
a.value = c |
|
default: |
|
c, ok := positiveScaleInt64(b.value, b.scale-a.scale) |
|
if !ok { |
|
return false |
|
} |
|
c, ok = int64Add(a.value, c) |
|
if !ok { |
|
return false |
|
} |
|
a.value = c |
|
} |
|
return true |
|
} |
|
|
|
// Sub removes the value of b from the current amount, or returns false if underflow would result. |
|
func (a *int64Amount) Sub(b int64Amount) bool { |
|
return a.Add(int64Amount{value: -b.value, scale: b.scale}) |
|
} |
|
|
|
// AsScale adjusts this amount to set a minimum scale, rounding up, and returns true iff no precision |
|
// was lost. (1.1e5).AsScale(5) would return 1.1e5, but (1.1e5).AsScale(6) would return 1e6. |
|
func (a int64Amount) AsScale(scale Scale) (int64Amount, bool) { |
|
if a.scale >= scale { |
|
return a, true |
|
} |
|
result, exact := negativeScaleInt64(a.value, scale-a.scale) |
|
return int64Amount{value: result, scale: scale}, exact |
|
} |
|
|
|
// AsCanonicalBytes accepts a buffer to write the base-10 string value of this field to, and returns |
|
// either that buffer or a larger buffer and the current exponent of the value. The value is adjusted |
|
// until the exponent is a multiple of 3 - i.e. 1.1e5 would return "110", 3. |
|
func (a int64Amount) AsCanonicalBytes(out []byte) (result []byte, exponent int32) { |
|
mantissa := a.value |
|
exponent = int32(a.scale) |
|
|
|
amount, times := removeInt64Factors(mantissa, 10) |
|
exponent += int32(times) |
|
|
|
// make sure exponent is a multiple of 3 |
|
var ok bool |
|
switch exponent % 3 { |
|
case 1, -2: |
|
amount, ok = int64MultiplyScale10(amount) |
|
if !ok { |
|
return infDecAmount{a.AsDec()}.AsCanonicalBytes(out) |
|
} |
|
exponent = exponent - 1 |
|
case 2, -1: |
|
amount, ok = int64MultiplyScale100(amount) |
|
if !ok { |
|
return infDecAmount{a.AsDec()}.AsCanonicalBytes(out) |
|
} |
|
exponent = exponent - 2 |
|
} |
|
return strconv.AppendInt(out, amount, 10), exponent |
|
} |
|
|
|
// AsCanonicalBase1024Bytes accepts a buffer to write the base-1024 string value of this field to, and returns |
|
// either that buffer or a larger buffer and the current exponent of the value. 2048 is 2 * 1024 ^ 1 and would |
|
// return []byte("2048"), 1. |
|
func (a int64Amount) AsCanonicalBase1024Bytes(out []byte) (result []byte, exponent int32) { |
|
value, ok := a.AsScaledInt64(0) |
|
if !ok { |
|
return infDecAmount{a.AsDec()}.AsCanonicalBase1024Bytes(out) |
|
} |
|
amount, exponent := removeInt64Factors(value, 1024) |
|
return strconv.AppendInt(out, amount, 10), exponent |
|
} |
|
|
|
// infDecAmount implements common operations over an inf.Dec that are specific to the quantity |
|
// representation. |
|
type infDecAmount struct { |
|
*inf.Dec |
|
} |
|
|
|
// AsScale adjusts this amount to set a minimum scale, rounding up, and returns true iff no precision |
|
// was lost. (1.1e5).AsScale(5) would return 1.1e5, but (1.1e5).AsScale(6) would return 1e6. |
|
func (a infDecAmount) AsScale(scale Scale) (infDecAmount, bool) { |
|
tmp := &inf.Dec{} |
|
tmp.Round(a.Dec, scale.infScale(), inf.RoundUp) |
|
return infDecAmount{tmp}, tmp.Cmp(a.Dec) == 0 |
|
} |
|
|
|
// AsCanonicalBytes accepts a buffer to write the base-10 string value of this field to, and returns |
|
// either that buffer or a larger buffer and the current exponent of the value. The value is adjusted |
|
// until the exponent is a multiple of 3 - i.e. 1.1e5 would return "110", 3. |
|
func (a infDecAmount) AsCanonicalBytes(out []byte) (result []byte, exponent int32) { |
|
mantissa := a.Dec.UnscaledBig() |
|
exponent = int32(-a.Dec.Scale()) |
|
amount := big.NewInt(0).Set(mantissa) |
|
// move all factors of 10 into the exponent for easy reasoning |
|
amount, times := removeBigIntFactors(amount, bigTen) |
|
exponent += times |
|
|
|
// make sure exponent is a multiple of 3 |
|
for exponent%3 != 0 { |
|
amount.Mul(amount, bigTen) |
|
exponent-- |
|
} |
|
|
|
return append(out, amount.String()...), exponent |
|
} |
|
|
|
// AsCanonicalBase1024Bytes accepts a buffer to write the base-1024 string value of this field to, and returns |
|
// either that buffer or a larger buffer and the current exponent of the value. 2048 is 2 * 1024 ^ 1 and would |
|
// return []byte("2048"), 1. |
|
func (a infDecAmount) AsCanonicalBase1024Bytes(out []byte) (result []byte, exponent int32) { |
|
tmp := &inf.Dec{} |
|
tmp.Round(a.Dec, 0, inf.RoundUp) |
|
amount, exponent := removeBigIntFactors(tmp.UnscaledBig(), big1024) |
|
return append(out, amount.String()...), exponent |
|
}
|
|
|