300 lines
8.6 KiB
Go
300 lines
8.6 KiB
Go
/*
|
|
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
|
|
}
|