Merge pull request #17548 from timstclair/suffix

Auto commit by PR queue bot
This commit is contained in:
k8s-merge-robot 2016-01-08 01:52:16 -08:00
commit 2106c72133
4 changed files with 187 additions and 44 deletions

View File

@ -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
}

View File

@ -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

View File

@ -22,7 +22,7 @@ import (
"testing"
)
func TestScaledValue(t *testing.T) {
func TestScaledValueInternal(t *testing.T) {
tests := []struct {
unscaled *big.Int
scale int

View File

@ -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})