mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-01 15:58:37 +00:00
Merge pull request #17548 from timstclair/suffix
Auto commit by PR queue bot
This commit is contained in:
commit
2106c72133
@ -117,10 +117,27 @@ func MustParse(str string) Quantity {
|
||||
return *q
|
||||
}
|
||||
|
||||
// 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 int
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
const (
|
||||
// splitREString is used to separate a number from its suffix; as such,
|
||||
// this is overly permissive, but that's OK-- it will be checked later.
|
||||
splitREString = "^([+-]?[0-9.]+)([eEimkKMGTP]*[-+]?[0-9]*)$"
|
||||
splitREString = "^([+-]?[0-9.]+)([eEinumkKMGTP]*[-+]?[0-9]*)$"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -176,7 +193,7 @@ func ParseQuantity(str string) (*Quantity, error) {
|
||||
|
||||
// So that no one but us has to think about suffixes, remove it.
|
||||
if base == 10 {
|
||||
amount.SetScale(amount.Scale() + inf.Scale(-exponent))
|
||||
amount.SetScale(amount.Scale() + Scale(exponent).infScale())
|
||||
} else if base == 2 {
|
||||
// numericSuffix = 2 ** exponent
|
||||
numericSuffix := big.NewInt(1).Lsh(bigOne, uint(exponent))
|
||||
@ -189,14 +206,13 @@ func ParseQuantity(str string) (*Quantity, error) {
|
||||
if sign == -1 {
|
||||
amount.Neg(amount)
|
||||
}
|
||||
// This rounds non-zero values up to the minimum representable
|
||||
// value, under the theory that if you want some resources, you
|
||||
// should get some resources, even if you asked for way too small
|
||||
// of an amount.
|
||||
// Arguably, this should be inf.RoundHalfUp (normal rounding), but
|
||||
// that would have the side effect of rounding values < .5m to zero.
|
||||
|
||||
// This rounds non-zero values up to the minimum representable value, under the theory that
|
||||
// if you want some resources, you should get some resources, even if you asked for way too small
|
||||
// of an amount. Arguably, this should be inf.RoundHalfUp (normal rounding), but that would have
|
||||
// the side effect of rounding values < .5n to zero.
|
||||
if v, ok := amount.Unscaled(); v != int64(0) || !ok {
|
||||
amount.Round(amount, 3, inf.RoundUp)
|
||||
amount.Round(amount, Nano.infScale(), inf.RoundUp)
|
||||
}
|
||||
|
||||
// The max is just a simple cap.
|
||||
@ -313,18 +329,16 @@ func (q *Quantity) String() string {
|
||||
// +1 if q > y
|
||||
//
|
||||
func (q *Quantity) Cmp(y Quantity) int {
|
||||
num1 := q.Value()
|
||||
num2 := y.Value()
|
||||
if num1 < MaxMilliValue && num2 < MaxMilliValue {
|
||||
num1 = q.MilliValue()
|
||||
num2 = y.MilliValue()
|
||||
if q.Amount == nil {
|
||||
if y.Amount == nil {
|
||||
return 0
|
||||
}
|
||||
return -y.Amount.Sign()
|
||||
}
|
||||
if num1 < num2 {
|
||||
return -1
|
||||
} else if num1 > num2 {
|
||||
return 1
|
||||
if y.Amount == nil {
|
||||
return q.Amount.Sign()
|
||||
}
|
||||
return 0
|
||||
return q.Amount.Cmp(y.Amount)
|
||||
}
|
||||
|
||||
func (q *Quantity) Add(y Quantity) error {
|
||||
@ -390,39 +404,52 @@ func NewMilliQuantity(value int64, format Format) *Quantity {
|
||||
}
|
||||
}
|
||||
|
||||
// Value returns the value of q; any fractional part will be lost.
|
||||
func (q *Quantity) Value() int64 {
|
||||
if q.Amount == nil {
|
||||
return 0
|
||||
// NewScaledQuantity returns a new Quantity representing the given
|
||||
// value * 10^scale in DecimalSI format.
|
||||
func NewScaledQuantity(value int64, scale Scale) *Quantity {
|
||||
return &Quantity{
|
||||
Amount: inf.NewDec(value, scale.infScale()),
|
||||
Format: DecimalSI,
|
||||
}
|
||||
return scaledValue(q.Amount.UnscaledBig(), int(q.Amount.Scale()), 0)
|
||||
}
|
||||
|
||||
// MilliValue returns the value of q * 1000; this could overflow an int64;
|
||||
// Value returns the value of q; any fractional part will be lost.
|
||||
func (q *Quantity) Value() int64 {
|
||||
return q.ScaledValue(0)
|
||||
}
|
||||
|
||||
// MilliValue returns the value of ceil(q * 1000); this could overflow an int64;
|
||||
// if that's a concern, call Value() first to verify the number is small enough.
|
||||
func (q *Quantity) MilliValue() int64 {
|
||||
return q.ScaledValue(Milli)
|
||||
}
|
||||
|
||||
// ScaledValue returns the value of ceil(q * 10^scale); this could overflow an int64.
|
||||
// To detect overflow, call Value() first and verify the expected magnitude.
|
||||
func (q *Quantity) ScaledValue(scale Scale) int64 {
|
||||
if q.Amount == nil {
|
||||
return 0
|
||||
}
|
||||
return scaledValue(q.Amount.UnscaledBig(), int(q.Amount.Scale()), 3)
|
||||
return scaledValue(q.Amount.UnscaledBig(), int(q.Amount.Scale()), int(scale.infScale()))
|
||||
}
|
||||
|
||||
// Set sets q's value to be value.
|
||||
func (q *Quantity) Set(value int64) {
|
||||
if q.Amount == nil {
|
||||
q.Amount = &inf.Dec{}
|
||||
}
|
||||
q.Amount.SetUnscaled(value)
|
||||
q.Amount.SetScale(0)
|
||||
q.SetScaled(value, 0)
|
||||
}
|
||||
|
||||
// SetMilli sets q's value to be value * 1/1000.
|
||||
func (q *Quantity) SetMilli(value int64) {
|
||||
q.SetScaled(value, Milli)
|
||||
}
|
||||
|
||||
// SetScaled sets q's value to be value * 10^scale
|
||||
func (q *Quantity) SetScaled(value int64, scale Scale) {
|
||||
if q.Amount == nil {
|
||||
q.Amount = &inf.Dec{}
|
||||
}
|
||||
q.Amount.SetUnscaled(value)
|
||||
q.Amount.SetScale(3)
|
||||
q.Amount.SetScale(scale.infScale())
|
||||
}
|
||||
|
||||
// Copy is a convenience function that makes a deep copy for you. Non-deep
|
||||
@ -477,3 +504,8 @@ func QuantityFlag(flagName, defaultValue, description string) *Quantity {
|
||||
func NewQuantityFlagValue(q *Quantity) flag.Value {
|
||||
return qFlag{q}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
@ -95,6 +95,28 @@ func TestQuantityCmp(t *testing.T) {
|
||||
t.Errorf("X: %v, Y: %v, Expected: %v, Actual: %v", testCase.x, testCase.y, testCase.expect, result)
|
||||
}
|
||||
}
|
||||
|
||||
nils := []struct {
|
||||
x *inf.Dec
|
||||
y *inf.Dec
|
||||
expect int
|
||||
}{
|
||||
{dec(0, 0), dec(0, 0), 0},
|
||||
{nil, dec(0, 0), 0},
|
||||
{dec(0, 0), nil, 0},
|
||||
{nil, nil, 0},
|
||||
{nil, dec(10, 0), -1},
|
||||
{nil, dec(-10, 0), 1},
|
||||
{dec(10, 0), nil, 1},
|
||||
{dec(-10, 0), nil, -1},
|
||||
}
|
||||
for _, nilCase := range nils {
|
||||
q1 := Quantity{nilCase.x, DecimalSI}
|
||||
q2 := Quantity{nilCase.y, DecimalSI}
|
||||
if result := q1.Cmp(q2); result != nilCase.expect {
|
||||
t.Errorf("X: %v, Y: %v, Expected: %v, Actual: %v", nilCase.x, nilCase.y, nilCase.expect, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuantityParse(t *testing.T) {
|
||||
@ -103,6 +125,8 @@ func TestQuantityParse(t *testing.T) {
|
||||
expect Quantity
|
||||
}{
|
||||
{"0", Quantity{dec(0, 0), DecimalSI}},
|
||||
{"0n", Quantity{dec(0, 0), DecimalSI}},
|
||||
{"0u", Quantity{dec(0, 0), DecimalSI}},
|
||||
{"0m", Quantity{dec(0, 0), DecimalSI}},
|
||||
{"0Ki", Quantity{dec(0, 0), BinarySI}},
|
||||
{"0k", Quantity{dec(0, 0), DecimalSI}},
|
||||
@ -126,6 +150,8 @@ func TestQuantityParse(t *testing.T) {
|
||||
{"100Ti", Quantity{dec(100*1024*1024*1024*1024, 0), BinarySI}},
|
||||
|
||||
// Decimal suffixes
|
||||
{"5n", Quantity{dec(5, -9), DecimalSI}},
|
||||
{"4u", Quantity{dec(4, -6), DecimalSI}},
|
||||
{"3m", Quantity{dec(3, -3), DecimalSI}},
|
||||
{"9", Quantity{dec(9, 0), DecimalSI}},
|
||||
{"8k", Quantity{dec(8, 3), DecimalSI}},
|
||||
@ -186,15 +212,15 @@ func TestQuantityParse(t *testing.T) {
|
||||
{"1.01E", Quantity{dec(101, 16), DecimalSI}},
|
||||
|
||||
// Things that saturate/round
|
||||
{"3.001m", Quantity{dec(4, -3), DecimalSI}},
|
||||
{"1.1E-3", Quantity{dec(2, -3), DecimalExponent}},
|
||||
{"0.0001", Quantity{dec(1, -3), DecimalSI}},
|
||||
{"0.0005", Quantity{dec(1, -3), DecimalSI}},
|
||||
{"0.00050", Quantity{dec(1, -3), DecimalSI}},
|
||||
{"0.5e-3", Quantity{dec(1, -3), DecimalExponent}},
|
||||
{"0.9m", Quantity{dec(1, -3), DecimalSI}},
|
||||
{"0.12345", Quantity{dec(124, -3), DecimalSI}},
|
||||
{"0.12354", Quantity{dec(124, -3), DecimalSI}},
|
||||
{"3.001n", Quantity{dec(4, -9), DecimalSI}},
|
||||
{"1.1E-9", Quantity{dec(2, -9), DecimalExponent}},
|
||||
{"0.0000000001", Quantity{dec(1, -9), DecimalSI}},
|
||||
{"0.0000000005", Quantity{dec(1, -9), DecimalSI}},
|
||||
{"0.00000000050", Quantity{dec(1, -9), DecimalSI}},
|
||||
{"0.5e-9", Quantity{dec(1, -9), DecimalExponent}},
|
||||
{"0.9n", Quantity{dec(1, -9), DecimalSI}},
|
||||
{"0.00000012345", Quantity{dec(124, -9), DecimalSI}},
|
||||
{"0.00000012354", Quantity{dec(124, -9), DecimalSI}},
|
||||
{"9Ei", Quantity{maxAllowed, BinarySI}},
|
||||
{"9223372036854775807Ki", Quantity{maxAllowed, BinarySI}},
|
||||
{"12E", Quantity{maxAllowed, DecimalSI}},
|
||||
@ -206,7 +232,7 @@ func TestQuantityParse(t *testing.T) {
|
||||
{"0.025Ti", Quantity{dec(274877906944, -1), BinarySI}},
|
||||
|
||||
// Things written by trolls
|
||||
{"0.000001Ki", Quantity{dec(2, -3), DecimalSI}}, // rounds up, changes format
|
||||
{"0.000000000001Ki", Quantity{dec(2, -9), DecimalSI}}, // rounds up, changes format
|
||||
{".001", Quantity{dec(1, -3), DecimalSI}},
|
||||
{".0001k", Quantity{dec(100, -3), DecimalSI}},
|
||||
{"1.", Quantity{dec(1, 0), DecimalSI}},
|
||||
@ -308,6 +334,7 @@ func TestQuantityString(t *testing.T) {
|
||||
{Quantity{dec(0, 0), BinarySI}, "0"},
|
||||
{Quantity{dec(1, 9), DecimalExponent}, "1e9"},
|
||||
{Quantity{dec(1, -3), DecimalExponent}, "1e-3"},
|
||||
{Quantity{dec(1, -9), DecimalExponent}, "1e-9"},
|
||||
{Quantity{dec(80, -3), DecimalExponent}, "80e-3"},
|
||||
{Quantity{dec(300, 6), DecimalExponent}, "300e6"},
|
||||
{Quantity{dec(1, 12), DecimalExponent}, "1e12"},
|
||||
@ -315,6 +342,14 @@ func TestQuantityString(t *testing.T) {
|
||||
{Quantity{dec(3, 3), DecimalExponent}, "3e3"},
|
||||
{Quantity{dec(3, 3), DecimalSI}, "3k"},
|
||||
{Quantity{dec(0, 0), DecimalExponent}, "0"},
|
||||
{Quantity{dec(1, -9), DecimalSI}, "1n"},
|
||||
{Quantity{dec(80, -9), DecimalSI}, "80n"},
|
||||
{Quantity{dec(1080, -9), DecimalSI}, "1080n"},
|
||||
{Quantity{dec(108, -8), DecimalSI}, "1080n"},
|
||||
{Quantity{dec(10800, -10), DecimalSI}, "1080n"},
|
||||
{Quantity{dec(1, -6), DecimalSI}, "1u"},
|
||||
{Quantity{dec(80, -6), DecimalSI}, "80u"},
|
||||
{Quantity{dec(1080, -6), DecimalSI}, "1080u"},
|
||||
}
|
||||
for _, item := range table {
|
||||
got := item.in.String()
|
||||
@ -346,7 +381,10 @@ func TestQuantityParseEmit(t *testing.T) {
|
||||
{"1Gi", "1Gi"},
|
||||
{"1024Mi", "1Gi"},
|
||||
{"1000M", "1G"},
|
||||
{".000001Ki", "2m"},
|
||||
{".001Ki", "1024m"},
|
||||
{".000001Ki", "1024u"},
|
||||
{".000000001Ki", "1024n"},
|
||||
{".000000000001Ki", "2n"},
|
||||
}
|
||||
|
||||
for _, item := range table {
|
||||
@ -502,6 +540,77 @@ func TestNewSet(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewScaledSet(t *testing.T) {
|
||||
table := []struct {
|
||||
value int64
|
||||
scale Scale
|
||||
expect string
|
||||
}{
|
||||
{1, Nano, "1n"},
|
||||
{1000, Nano, "1u"},
|
||||
{1, Micro, "1u"},
|
||||
{1000, Micro, "1m"},
|
||||
{1, Milli, "1m"},
|
||||
{1000, Milli, "1"},
|
||||
{1, 0, "1"},
|
||||
{0, Nano, "0"},
|
||||
{0, Micro, "0"},
|
||||
{0, Milli, "0"},
|
||||
{0, 0, "0"},
|
||||
}
|
||||
|
||||
for _, item := range table {
|
||||
q := NewScaledQuantity(item.value, item.scale)
|
||||
if e, a := item.expect, q.String(); e != a {
|
||||
t.Errorf("Expected %v, got %v; %#v", e, a, q)
|
||||
}
|
||||
q2, err := ParseQuantity(q.String())
|
||||
if err != nil {
|
||||
t.Errorf("Round trip failed on %v", q)
|
||||
}
|
||||
if e, a := item.value, q2.ScaledValue(item.scale); e != a {
|
||||
t.Errorf("Expected %v, got %v", e, a)
|
||||
}
|
||||
q3 := NewQuantity(0, DecimalSI)
|
||||
q3.SetScaled(item.value, item.scale)
|
||||
if q.Cmp(*q3) != 0 {
|
||||
t.Errorf("Expected %v and %v to be equal", q, q3)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestScaledValue(t *testing.T) {
|
||||
table := []struct {
|
||||
fromScale Scale
|
||||
toScale Scale
|
||||
expected int64
|
||||
}{
|
||||
{Nano, Nano, 1},
|
||||
{Nano, Micro, 1},
|
||||
{Nano, Milli, 1},
|
||||
{Nano, 0, 1},
|
||||
{Micro, Nano, 1000},
|
||||
{Micro, Micro, 1},
|
||||
{Micro, Milli, 1},
|
||||
{Micro, 0, 1},
|
||||
{Milli, Nano, 1000 * 1000},
|
||||
{Milli, Micro, 1000},
|
||||
{Milli, Milli, 1},
|
||||
{Milli, 0, 1},
|
||||
{0, Nano, 1000 * 1000 * 1000},
|
||||
{0, Micro, 1000 * 1000},
|
||||
{0, Milli, 1000},
|
||||
{0, 0, 1},
|
||||
}
|
||||
|
||||
for _, item := range table {
|
||||
q := NewScaledQuantity(1, item.fromScale)
|
||||
if e, a := item.expected, q.ScaledValue(item.toScale); e != a {
|
||||
t.Errorf("%v to %v: Expected %v, got %v", item.fromScale, item.toScale, e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUninitializedNoCrash(t *testing.T) {
|
||||
var q Quantity
|
||||
|
||||
|
@ -22,7 +22,7 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestScaledValue(t *testing.T) {
|
||||
func TestScaledValueInternal(t *testing.T) {
|
||||
tests := []struct {
|
||||
unscaled *big.Int
|
||||
scale int
|
||||
|
@ -83,6 +83,8 @@ func newSuffixer() suffixer {
|
||||
// a suffix for 2^0.
|
||||
sh.decSuffixes.addSuffix("", bePair{2, 0})
|
||||
|
||||
sh.decSuffixes.addSuffix("n", bePair{10, -9})
|
||||
sh.decSuffixes.addSuffix("u", bePair{10, -6})
|
||||
sh.decSuffixes.addSuffix("m", bePair{10, -3})
|
||||
sh.decSuffixes.addSuffix("", bePair{10, 0})
|
||||
sh.decSuffixes.addSuffix("k", bePair{10, 3})
|
||||
|
Loading…
Reference in New Issue
Block a user