Repository: osmosis-labs/osmosis
Pull request #3678 (2022-12-09 16:40:41)

feat(osmomath): BigDec Clone() and MulMut methods
<!-- < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < ☺
v                               ✰  Thanks for creating a PR! ✰    
v    Before smashing the submit button please review the checkboxes.
v    If a checkbox is n/a - please still include it but + a little note why
v    If your PR doesn't close an issue, that's OK!  Just remove the Closes: #XXX line!
☺ > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >  -->

Closes: #XXX

## What is the purpose of the change

Context: https://github.com/osmosis-labs/osmosis/pull/3676#discussion_r1044555061

We would like to have a mutative and nonmutatuve `Power` function. This requires implementing a mutative multiplication function first. This PR does so by introducing `Clone`, `MulMut` functions defined on `BigDec` and `Mul` utilizing the new functions.

## Testing and Verifying

- covered by existing `Mut` tests.
- added tests for `Clone`
- added test to check `MulMut` mutates and `Mut` does not

## Documentation and Release Note

  - Does this pull request introduce a new feature or user-facing behavior changes? no
  - Is a relevant changelog entry added to the `Unreleased` section in `CHANGELOG.md`? yes
  - How is the feature or change documented? not applicable


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 780046818..2db6b4efb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -46,6 +46,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 * [#3609](https://github.com/osmosis-labs/osmosis/pull/3609) Add Downtime-detection module.
 * [#2788](https://github.com/osmosis-labs/osmosis/pull/2788) Add logarithm base 2 implementation.
+* [#3677](https://github.com/osmosis-labs/osmosis/pull/3677) Add methods for cloning and mutative multiplication on osmomath.BigDec.
 
 ### Bug fixes
 
diff --git a/osmomath/decimal.go b/osmomath/decimal.go
index 821b0f5b4..55dbd399f 100644
--- a/osmomath/decimal.go
+++ b/osmomath/decimal.go
@@ -260,15 +260,32 @@ func (d BigDec) Sub(d2 BigDec) BigDec {
 	return BigDec{res}
 }
 
-// multiplication
+// Clone performs a deep copy of the receiver
+// and returns the new result.
+func (d BigDec) Clone() BigDec {
+	copy := BigDec{new(big.Int)}
+	copy.i.Set(d.i)
+	return copy
+}
+
+// Mut performs non-mutative multiplication.
+// The receiver is not modifier but the result is.
 func (d BigDec) Mul(d2 BigDec) BigDec {
-	mul := new(big.Int).Mul(d.i, d2.i)
-	chopped := chopPrecisionAndRound(mul)
+	copy := d.Clone()
+	copy.MulMut(d2)
+	return copy
+}
 
-	if chopped.BitLen() > maxDecBitLen {
+// Mut performs non-mutative multiplication.
+// The receiver is not modifier but the result is.
+func (d BigDec) MulMut(d2 BigDec) BigDec {
+	d.i.Mul(d.i, d2.i)
+	d.i = chopPrecisionAndRound(d.i)
+
+	if d.i.BitLen() > maxDecBitLen {
 		panic("Int overflow")
 	}
-	return BigDec{chopped}
+	return BigDec{d.i}
 }
 
 // multiplication truncate


Test patch:
diff --git a/osmomath/decimal_test.go b/osmomath/decimal_test.go
index 08cdc7f52..1b09c9f0e 100644
--- a/osmomath/decimal_test.go
+++ b/osmomath/decimal_test.go
@@ -1030,3 +1030,85 @@ func (s *decimalTestSuite) TestCustomBaseLog() {
 		})
 	}
 }
+
+func (s *decimalTestSuite) TestClone() {
+
+	// The value to change the underlying copy's
+	// internal value to assert on the original BigDec
+	// remaining unchanged.
+	changeValue := big.NewInt(10)
+
+	tests := map[string]struct {
+		startValue BigDec
+	}{
+		"1.1": {
+			startValue: MustNewDecFromStr("1.1"),
+		},
+		"-3": {
+			startValue: MustNewDecFromStr("-3"),
+		},
+		"0": {
+			startValue: MustNewDecFromStr("-3"),
+		},
+	}
+
+	for name, tc := range tests {
+		tc := tc
+		s.Run(name, func() {
+
+			copy := tc.startValue.Clone()
+
+			s.Require().Equal(tc.startValue, copy)
+
+			copy.i.Set(changeValue)
+			// copy and startValue do not share internals.
+			s.Require().NotEqual(tc.startValue, copy)
+		})
+	}
+}
+
+// TestMul_Mutation tests that MulMut mutates the receiver
+// while Mut is not.
+func (s *decimalTestSuite) TestMul_Mutation() {
+
+	mulBy := MustNewDecFromStr("2")
+
+	tests := map[string]struct {
+		startValue        BigDec
+		expectedMulResult BigDec
+	}{
+		"1.1": {
+			startValue:        MustNewDecFromStr("1.1"),
+			expectedMulResult: MustNewDecFromStr("2.2"),
+		},
+		"-3": {
+			startValue:        MustNewDecFromStr("-3"),
+			expectedMulResult: MustNewDecFromStr("-6"),
+		},
+		"0": {
+			startValue:        ZeroDec(),
+			expectedMulResult: ZeroDec(),
+		},
+	}
+
+	for name, tc := range tests {
+		tc := tc
+		s.Run(name, func() {
+
+			startMut := tc.startValue.Clone()
+			startNonMut := tc.startValue.Clone()
+
+			resultMut := startMut.MulMut(mulBy)
+			result := startNonMut.Mul(mulBy)
+
+			// assert both results are as expectde.
+			s.Require().Equal(tc.expectedMulResult, resultMut)
+			s.Require().Equal(tc.expectedMulResult, result)
+
+			// assert MulMut mutated the receiver
+			s.Require().Equal(tc.expectedMulResult, startMut)
+			// assert Mul did not mutate the receiver
+			s.Require().Equal(tc.startValue, startNonMut)
+		})
+	}
+}