123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299 |
- /*
- 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
- }
|