diff --git a/staging/src/k8s.io/apimachinery/pkg/api/resource/amount.go b/staging/src/k8s.io/apimachinery/pkg/api/resource/amount.go index a8866a43e10..298c45fc852 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/resource/amount.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/resource/amount.go @@ -203,6 +203,50 @@ func (a *int64Amount) Sub(b int64Amount) bool { return a.Add(int64Amount{value: -b.value, scale: b.scale}) } +// Mul multiplies two int64Amounts together, matching scales. +// It will return false and not mutate a if overflow or underflow would result. +// Mul adds the value of a to itself n times. +// It will return false and not mutate a if overflow or underflow would result. +func (a *int64Amount) Mul(b int64Amount) bool { + switch { + case b.value == 0: + a.value = 0 + return true + case a.value == 0: + a.value = 0 + a.scale = b.scale + return true + case a.scale == b.scale: + c, ok := int64Multiply(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 = int64Multiply(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 = int64Multiply(a.value, c) + if !ok { + return false + } + a.value = c + } + return true +} + // 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) { diff --git a/staging/src/k8s.io/apimachinery/pkg/api/resource/amount_test.go b/staging/src/k8s.io/apimachinery/pkg/api/resource/amount_test.go index 8217cb1399c..942b6f83637 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/resource/amount_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/resource/amount_test.go @@ -88,6 +88,54 @@ func TestInt64AmountAdd(t *testing.T) { } } } + +func TestInt64AmountMul(t *testing.T) { + for _, test := range []struct { + a, b, c int64Amount + ok bool + }{ + {int64Amount{value: 100, scale: 1}, int64Amount{value: 10, scale: 2}, int64Amount{value: 10000, scale: 1}, true}, + {int64Amount{value: 100, scale: 1}, int64Amount{value: 1, scale: 2}, int64Amount{value: 1000, scale: 1}, true}, + {int64Amount{value: 100, scale: 1}, int64Amount{value: 1, scale: 100}, int64Amount{value: 1, scale: 100}, false}, + {int64Amount{value: -5, scale: 2}, int64Amount{value: 50, scale: 1}, int64Amount{value: -2500, scale: 1}, true}, + {int64Amount{value: -5, scale: 2}, int64Amount{value: 5, scale: 2}, int64Amount{value: -25, scale: 2}, true}, + + {int64Amount{value: mostPositive, scale: -1}, int64Amount{value: 1, scale: -1}, int64Amount{value: 9223372036854775807, scale: -1}, true}, + {int64Amount{value: mostPositive, scale: -1}, int64Amount{value: 0, scale: -1}, int64Amount{value: 0, scale: -1}, true}, + {int64Amount{value: mostPositive / 10, scale: 1}, int64Amount{value: 10, scale: 0}, int64Amount{value: mostPositive, scale: -1}, false}, + } { + c := test.a + ok := c.Mul(test.b) + if ok != test.ok { + t.Errorf("%v: unexpected ok: %t", test, ok) + } + if ok { + if c != test.c { + t.Errorf("%v: unexpected result: %d", test, c) + } + } else { + if c != test.a { + t.Errorf("%v: overflow multiplication mutated source: %d", test, c) + } + } + + // multiplication is commutative + c = test.b + if ok = c.Mul(test.a); ok != test.ok { + t.Errorf("%v: unexpected ok: %t", test, ok) + } + if ok { + if c != test.c { + t.Errorf("%v: unexpected result: %d", test, c) + } + } else { + if c != test.b { + t.Errorf("%v: overflow multiplication mutated source: %d", test, c) + } + } + } +} + func TestInt64AsCanonicalString(t *testing.T) { for _, test := range []struct { value int64 diff --git a/staging/src/k8s.io/apimachinery/pkg/api/resource/quantity.go b/staging/src/k8s.io/apimachinery/pkg/api/resource/quantity.go index b47d554b3c5..4368cb5ca54 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/resource/quantity.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/resource/quantity.go @@ -592,6 +592,19 @@ func (q *Quantity) Sub(y Quantity) { q.ToDec().d.Dec.Sub(q.d.Dec, y.AsDec()) } +// Mul multiplies the provided y quantity to the current value. If the current value is zero, +// the format of the quantity will be updated to the format of y. +func (q *Quantity) Mul(y Quantity) { + q.s = "" + if q.IsZero() { + q.Format = y.Format + } + if q.d.Dec == nil && y.d.Dec == nil && q.i.Mul(y.i) { + return + } + q.ToDec().d.Dec.Mul(q.d.Dec, y.AsDec()) +} + // Cmp returns 0 if the quantity is equal to y, -1 if the quantity is less than y, or 1 if the // quantity is greater than y. func (q *Quantity) Cmp(y Quantity) int { diff --git a/staging/src/k8s.io/apimachinery/pkg/api/resource/quantity_test.go b/staging/src/k8s.io/apimachinery/pkg/api/resource/quantity_test.go index 320477358c8..b0f3a4481b5 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/resource/quantity_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/resource/quantity_test.go @@ -1137,6 +1137,28 @@ func TestAdd(t *testing.T) { } } +func TestMul(t *testing.T) { + tests := []struct { + a Quantity + b Quantity + expected Quantity + }{ + {decQuantity(10, 0, DecimalSI), decQuantity(1, 1, DecimalSI), decQuantity(100, 0, DecimalSI)}, + {decQuantity(10, 0, DecimalSI), decQuantity(1, 0, BinarySI), decQuantity(10, 0, DecimalSI)}, + {decQuantity(10, 0, BinarySI), decQuantity(1, 0, DecimalSI), decQuantity(10, 0, BinarySI)}, + {Quantity{Format: DecimalSI}, decQuantity(50, 0, DecimalSI), decQuantity(0, 0, DecimalSI)}, + {decQuantity(50, 0, DecimalSI), Quantity{Format: DecimalSI}, decQuantity(0, 0, DecimalSI)}, + {Quantity{Format: DecimalSI}, Quantity{Format: DecimalSI}, decQuantity(0, 0, DecimalSI)}, + } + + for i, test := range tests { + test.a.Mul(test.b) + if test.a.Cmp(test.expected) != 0 { + t.Errorf("[%d] Expected %q, got %q", i, test.expected.String(), test.a.String()) + } + } +} + func TestAddSubRoundTrip(t *testing.T) { for k := -10; k <= 10; k++ { q := Quantity{Format: DecimalSI}