From cb4830127e2614d8bdd14a6e0317d1fe3c20b926 Mon Sep 17 00:00:00 2001 From: christopherganda Date: Sat, 9 Aug 2025 19:55:41 +0700 Subject: [PATCH 01/11] add decimal Add --- helper.go | 17 ++++++++++++++++ operations.go | 35 ++++++++++++++++++++++++++++++++ operations_test.go | 50 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 helper.go create mode 100644 operations.go create mode 100644 operations_test.go diff --git a/helper.go b/helper.go new file mode 100644 index 0000000..81617e3 --- /dev/null +++ b/helper.go @@ -0,0 +1,17 @@ +package decimal + +import ( + "math/big" +) + +func bigIntFromString(s string) *big.Int { + if s == "" { + return nil + } + val := new(big.Int) + _, ok := val.SetString(s, 10) + if !ok { + return nil + } + return val +} diff --git a/operations.go b/operations.go new file mode 100644 index 0000000..c12ae78 --- /dev/null +++ b/operations.go @@ -0,0 +1,35 @@ +package decimal + +import "math/big" + +func (d Decimal) Add(other Decimal) Decimal { + // Determine the final scale. + finalScale := d.scale + if other.scale > d.scale { + finalScale = other.scale + } + + // Create new Decimals with aligned scales. + d1 := d // In a real implementation, you'd call a rescale function here. + d2 := other + + // Example of a naive rescale implementation for demonstration. + if d1.scale < finalScale { + pow10 := big.NewInt(0).Exp(big.NewInt(10), big.NewInt(int64(finalScale-d1.scale)), nil) + d1.unscaledValue.Mul(d1.unscaledValue, pow10) + d1.scale = finalScale + } + if d2.scale < finalScale { + pow10 := big.NewInt(0).Exp(big.NewInt(10), big.NewInt(int64(finalScale-d2.scale)), nil) + d2.unscaledValue.Mul(d2.unscaledValue, pow10) + d2.scale = finalScale + } + + // Perform the addition on the unscaled values. + result := new(big.Int).Add(d1.unscaledValue, d2.unscaledValue) + + return Decimal{ + unscaledValue: result, + scale: finalScale, + } +} diff --git a/operations_test.go b/operations_test.go new file mode 100644 index 0000000..7406055 --- /dev/null +++ b/operations_test.go @@ -0,0 +1,50 @@ +package decimal + +import ( + "testing" +) + +func TestOperations_Add(t *testing.T) { + tests := []struct { + name string + a Decimal + b Decimal + expected Decimal + }{ + {"AddPositive", New(5, 0), New(3, 0), New(8, 0)}, + {"AddNegative", New(-5, 0), New(-3, 0), New(-8, 0)}, + {"AddMixed", New(5, 0), New(-3, 0), New(2, 0)}, + {"AddZero", New(5, 0), New(0, 0), New(5, 0)}, + {"AddDifferentScales_A_Smaller", New(1234, 2), New(567, 1), New(6904, 2)}, // 12.34 + 56.70 = 69.04 + {"AddDifferentScales_B_Smaller", New(567, 1), New(1234, 2), New(6904, 2)}, // 56.70 + 12.34 = 69.04 + {"AddDifferentScales_With_Negatives", New(-123, 1), New(456, 2), New(-774, 2)}, // -12.30 + 4.56 = -7.74 + {"AddDifferentScales_ManyPlaces_Case1", New(999, 3), New(1, 1), New(1099, 3)}, // 0.999 + 0.100 = 1.099 + {"AddDifferentScales_ManyPlaces_Case2", New(999, 3), New(1, 2), New(1009, 3)}, // 0.999 + 0.010 = 1.009 + {"AddWithCarryOver", New(99, 2), New(2, 1), New(119, 2)}, // Corrected: 0.99 + 0.20 = 1.19 + {"AddWithDifferentScales_MoreZeros", New(12500, 4), New(35, 1), New(47500, 4)}, // 1.2500 + 3.5000 = 4.7500 + {"AddWithNegativeResult", New(100, 2), New(-250, 2), New(-150, 2)}, // 1.00 + (-2.50) = -1.50 + {"AddScalesToZero", New(100, 2), New(-100, 2), New(0, 2)}, // 1.00 + (-1.00) = 0.00 + {"AddWithLargeScaleDifference", New(1, 6), New(1, 1), New(100001, 6)}, // 0.000001 + 0.100000 = 0.100001 + {"AddLargeNumbers", + Decimal{unscaledValue: bigIntFromString("9223372036854775807"), scale: 0}, + Decimal{unscaledValue: bigIntFromString("1"), scale: 0}, + Decimal{unscaledValue: bigIntFromString("9223372036854775808"), scale: 0}}, + {" AddLargeNegativeNumbers", + Decimal{unscaledValue: bigIntFromString("-9223372036854775807"), scale: 0}, + Decimal{unscaledValue: bigIntFromString("-1"), scale: 0}, + Decimal{unscaledValue: bigIntFromString("-9223372036854775808"), scale: 0}}, + {"AddLargeMixedNumbers", + Decimal{unscaledValue: bigIntFromString("9223372036854775807"), scale: 0}, + Decimal{unscaledValue: bigIntFromString("-9223372036854775807"), scale: 0}, + Decimal{unscaledValue: bigIntFromString("0"), scale: 0}}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.a.Add(tt.b) + if result.unscaledValue.Cmp(tt.expected.unscaledValue) != 0 || result.scale != tt.expected.scale { + t.Errorf("Add(%v, %v) = %v, want %v", tt.a, tt.b, result, tt.expected) + } + }) + } +} From 832732cf04caa5c4a69cb46ff9cb734cf58f4821 Mon Sep 17 00:00:00 2001 From: christopherganda Date: Sat, 9 Aug 2025 20:17:13 +0700 Subject: [PATCH 02/11] tidy up add --- operations.go | 57 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/operations.go b/operations.go index c12ae78..df841bb 100644 --- a/operations.go +++ b/operations.go @@ -1,35 +1,50 @@ package decimal -import "math/big" +import ( + "math/big" +) -func (d Decimal) Add(other Decimal) Decimal { - // Determine the final scale. - finalScale := d.scale - if other.scale > d.scale { - finalScale = other.scale +// rescale returns a new Decimal with its scale adjusted to a target scale. +// If the target scale is smaller than the current scale, it truncates the +// unscaled value, resulting in a loss of precision. It will never return an error. +func (d Decimal) rescale(targetScale int32) Decimal { + // If scales are the same, return the original. + if d.scale == targetScale { + return d } - // Create new Decimals with aligned scales. - d1 := d // In a real implementation, you'd call a rescale function here. - d2 := other + var deltaScale int32 + var newUnscaled *big.Int - // Example of a naive rescale implementation for demonstration. - if d1.scale < finalScale { - pow10 := big.NewInt(0).Exp(big.NewInt(10), big.NewInt(int64(finalScale-d1.scale)), nil) - d1.unscaledValue.Mul(d1.unscaledValue, pow10) - d1.scale = finalScale + if d.scale > targetScale { + // Scaling down: division. + deltaScale = d.scale - targetScale + pow10 := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(deltaScale)), nil) + newUnscaled = new(big.Int).Div(d.unscaledValue, pow10) + } else { + // Scaling up: multiplication. + deltaScale = targetScale - d.scale + pow10 := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(deltaScale)), nil) + newUnscaled = new(big.Int).Mul(d.unscaledValue, pow10) + } + + return Decimal{ + unscaledValue: newUnscaled, + scale: targetScale, } - if d2.scale < finalScale { - pow10 := big.NewInt(0).Exp(big.NewInt(10), big.NewInt(int64(finalScale-d2.scale)), nil) - d2.unscaledValue.Mul(d2.unscaledValue, pow10) - d2.scale = finalScale +} + +func (d Decimal) Add(other Decimal) Decimal { + finalScale := d.scale + if other.scale > d.scale { + finalScale = other.scale } - // Perform the addition on the unscaled values. - result := new(big.Int).Add(d1.unscaledValue, d2.unscaledValue) + d1 := d.rescale(finalScale) + d2 := other.rescale(finalScale) return Decimal{ - unscaledValue: result, + unscaledValue: new(big.Int).Add(d1.unscaledValue, d2.unscaledValue), scale: finalScale, } } From 414cd1d3686ee756940a16c2f540b9ec510cb13d Mon Sep 17 00:00:00 2001 From: christopherganda Date: Wed, 13 Aug 2025 19:51:26 +0700 Subject: [PATCH 03/11] remove panic --- rounding.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/rounding.go b/rounding.go index 94042bf..6840877 100644 --- a/rounding.go +++ b/rounding.go @@ -105,12 +105,9 @@ func (rm RoundingMode) shouldRoundUp(rem, denom *big.Int) bool { return rem.Bit(0) == 1 case RoundUnnecessary: - if rem.Sign() != 0 { - panic("rounding necessary but RoundUnnecessary specified") - } return false default: - panic("unknown rounding mode") + return false // Unknown rounding mode } } From 9442c5b87b3215830475562105c1bd91cf1137e3 Mon Sep 17 00:00:00 2001 From: christopherganda Date: Thu, 14 Aug 2025 11:53:33 +0700 Subject: [PATCH 04/11] subtract feature --- operations.go | 15 +++ operations_test.go | 232 +++++++++++++++++++++++++++++++++++++++------ 2 files changed, 220 insertions(+), 27 deletions(-) diff --git a/operations.go b/operations.go index df841bb..60c7b63 100644 --- a/operations.go +++ b/operations.go @@ -48,3 +48,18 @@ func (d Decimal) Add(other Decimal) Decimal { scale: finalScale, } } + +func (d Decimal) Sub(other Decimal) Decimal { + finalScale := d.scale + if other.scale > d.scale { + finalScale = other.scale + } + + d1 := d.rescale(finalScale) + d2 := other.rescale(finalScale) + + return Decimal{ + unscaledValue: new(big.Int).Sub(d1.unscaledValue, d2.unscaledValue), + scale: finalScale, + } +} diff --git a/operations_test.go b/operations_test.go index 7406055..daa84e4 100644 --- a/operations_test.go +++ b/operations_test.go @@ -11,39 +11,217 @@ func TestOperations_Add(t *testing.T) { b Decimal expected Decimal }{ - {"AddPositive", New(5, 0), New(3, 0), New(8, 0)}, - {"AddNegative", New(-5, 0), New(-3, 0), New(-8, 0)}, - {"AddMixed", New(5, 0), New(-3, 0), New(2, 0)}, - {"AddZero", New(5, 0), New(0, 0), New(5, 0)}, - {"AddDifferentScales_A_Smaller", New(1234, 2), New(567, 1), New(6904, 2)}, // 12.34 + 56.70 = 69.04 - {"AddDifferentScales_B_Smaller", New(567, 1), New(1234, 2), New(6904, 2)}, // 56.70 + 12.34 = 69.04 - {"AddDifferentScales_With_Negatives", New(-123, 1), New(456, 2), New(-774, 2)}, // -12.30 + 4.56 = -7.74 - {"AddDifferentScales_ManyPlaces_Case1", New(999, 3), New(1, 1), New(1099, 3)}, // 0.999 + 0.100 = 1.099 - {"AddDifferentScales_ManyPlaces_Case2", New(999, 3), New(1, 2), New(1009, 3)}, // 0.999 + 0.010 = 1.009 - {"AddWithCarryOver", New(99, 2), New(2, 1), New(119, 2)}, // Corrected: 0.99 + 0.20 = 1.19 - {"AddWithDifferentScales_MoreZeros", New(12500, 4), New(35, 1), New(47500, 4)}, // 1.2500 + 3.5000 = 4.7500 - {"AddWithNegativeResult", New(100, 2), New(-250, 2), New(-150, 2)}, // 1.00 + (-2.50) = -1.50 - {"AddScalesToZero", New(100, 2), New(-100, 2), New(0, 2)}, // 1.00 + (-1.00) = 0.00 - {"AddWithLargeScaleDifference", New(1, 6), New(1, 1), New(100001, 6)}, // 0.000001 + 0.100000 = 0.100001 - {"AddLargeNumbers", - Decimal{unscaledValue: bigIntFromString("9223372036854775807"), scale: 0}, - Decimal{unscaledValue: bigIntFromString("1"), scale: 0}, - Decimal{unscaledValue: bigIntFromString("9223372036854775808"), scale: 0}}, - {" AddLargeNegativeNumbers", - Decimal{unscaledValue: bigIntFromString("-9223372036854775807"), scale: 0}, - Decimal{unscaledValue: bigIntFromString("-1"), scale: 0}, - Decimal{unscaledValue: bigIntFromString("-9223372036854775808"), scale: 0}}, - {"AddLargeMixedNumbers", - Decimal{unscaledValue: bigIntFromString("9223372036854775807"), scale: 0}, - Decimal{unscaledValue: bigIntFromString("-9223372036854775807"), scale: 0}, - Decimal{unscaledValue: bigIntFromString("0"), scale: 0}}, + { + "AddPositive", + New(5, 0), + New(3, 0), + New(8, 0), + }, + { + "AddNegative", + New(-5, 0), + New(-3, 0), + New(-8, 0), + }, + { + "AddMixed", + New(5, 0), + New(-3, 0), + New(2, 0), + }, + { + "AddZero", + New(5, 0), + New(0, 0), + New(5, 0), + }, + { + "AddDifferentScales_A_Smaller", + New(1234, 2), + New(567, 1), + New(6904, 2), + }, // 12.34 + 56.70 = 69.04 + { + "AddDifferentScales_B_Smaller", + New(567, 1), + New(1234, 2), + New(6904, 2), + }, // 56.70 + 12.34 = 69.04 + { + "AddDifferentScales_With_Negatives", + New(-123, 1), + New(456, 2), + New(-774, 2), + }, // -12.30 + 4.56 = -7.74 + { + "AddDifferentScales_ManyPlaces_Case1", + New(999, 3), + New(1, 1), + New(1099, 3), + }, // 0.999 + 0.100 = 1.099 + { + "AddDifferentScales_ManyPlaces_Case2", + New(999, 3), + New(1, 2), + New(1009, 3), + }, // 0.999 + 0.010 = 1.009 + { + "AddWithCarryOver", + New(99, 2), + New(2, 1), + New(119, 2), + }, // Corrected: 0.99 + 0.20 = 1.19 + { + "AddWithDifferentScales_MoreZeros", + New(12500, 4), + New(35, 1), + New(47500, 4), + }, // 1.2500 + 3.5000 = 4.7500 + { + "AddWithNegativeResult", + New(100, 2), + New(-250, 2), + New(-150, 2), + }, // 1.00 + (-2.50) = -1.50 + { + "AddScalesToZero", + New(100, 2), + New(-100, 2), + New(0, 2), + }, // 1.00 + (-1.00) = 0.00 + { + "AddWithLargeScaleDifference", + New(1, 6), + New(1, 1), + New(100001, 6), + }, // 0.000001 + 0.100000 = 0.100001 + { + "AddLargeNumbers", + Decimal{unscaledValue: bigIntFromString("9223372036854775807"), + scale: 0}, + Decimal{unscaledValue: bigIntFromString("1"), + scale: 0}, + Decimal{unscaledValue: bigIntFromString("9223372036854775808"), + scale: 0}}, + { + " AddLargeNegativeNumbers", + Decimal{unscaledValue: bigIntFromString("-9223372036854775807"), + scale: 0}, + Decimal{unscaledValue: bigIntFromString("-1"), + scale: 0}, + Decimal{unscaledValue: bigIntFromString("-9223372036854775808"), + scale: 0}}, + { + "AddLargeMixedNumbers", + Decimal{unscaledValue: bigIntFromString("9223372036854775807"), + scale: 0}, + Decimal{unscaledValue: bigIntFromString("-9223372036854775807"), + scale: 0}, + Decimal{unscaledValue: bigIntFromString("0"), + scale: 0}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := tt.a.Add(tt.b) if result.unscaledValue.Cmp(tt.expected.unscaledValue) != 0 || result.scale != tt.expected.scale { - t.Errorf("Add(%v, %v) = %v, want %v", tt.a, tt.b, result, tt.expected) + t.Errorf("Add(%v, %v) = %v, want %v", + tt.a, tt.b, result, tt.expected) + } + }) + } +} + +func TestOperations_Sub(t *testing.T) { + tests := []struct { + name string + a Decimal + b Decimal + expected Decimal + }{ + { + "SubtractPositive", + New(8, 0), + New(3, 0), + New(5, 0), + }, + { + "SubtractNegative", + New(-8, 0), + New(-3, 0), + New(-5, 0), + }, + { + "SubtractMixed", + New(5, 0), + New(-3, 0), + New(8, 0), + }, + { + "SubtractZero", + New(5, 0), + New(0, 0), + New(5, 0), + }, + { + "SubtractDifferentScales_A_Smaller", + New(567, 1), // 56.7 + New(1234, 2), // 12.34 + New(4436, 2), // 44.36 + }, + { + "SubtractDifferentScales_B_Smaller", + New(1234, 2), // 12.34 + New(567, 1), // 56.7 + New(-4436, 2), // -44.36 + }, + { + "SubtractDifferentScales_With_Negatives", + New(-123, 1), // -12.3 + New(-456, 2), // -4.56 + New(-774, 2), // -7.74 + }, + { + "SubtractWithBorrow", + New(119, 2), // 1.19 + New(2, 1), // 0.2 + New(99, 2), // 0.99 + }, + { + "SubtractToZero", + New(1250, 2), // 12.50 + New(125, 1), // 12.5 + New(0, 2), + }, + { + "SubtractWithLargeScaleDifference", + New(1, 1), // 0.1 + New(1, 6), // 0.000001 + New(99999, 6), // 0.099999 + }, + { + "SubtractLargeNumbers", + Decimal{unscaledValue: bigIntFromString("9223372036854775808"), scale: 0}, + Decimal{unscaledValue: bigIntFromString("1"), scale: 0}, + Decimal{unscaledValue: bigIntFromString("9223372036854775807"), scale: 0}, + }, + { + "SubtractLargeNegativeNumbers", + Decimal{unscaledValue: bigIntFromString("-9223372036854775808"), scale: 0}, + Decimal{unscaledValue: bigIntFromString("-1"), scale: 0}, + Decimal{unscaledValue: bigIntFromString("-9223372036854775807"), scale: 0}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + result := tc.a.Sub(tc.b) + + if result.unscaledValue.Cmp(tc.expected.unscaledValue) != 0 { + t.Errorf("unscaledValue mismatch for %s: got %s, want %s", tc.name, result.unscaledValue, tc.expected.unscaledValue) + } + if result.scale != tc.expected.scale { + t.Errorf("scale mismatch for %s: got %d, want %d", tc.name, result.scale, tc.expected.scale) } }) } From fe1f87a4217855988c5d0546cd47c4c246a81d2a Mon Sep 17 00:00:00 2001 From: christopherganda Date: Thu, 14 Aug 2025 14:16:45 +0700 Subject: [PATCH 05/11] add multiplication feature --- operations.go | 9 ++++- operations_test.go | 85 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 92 insertions(+), 2 deletions(-) diff --git a/operations.go b/operations.go index 60c7b63..6157c2b 100644 --- a/operations.go +++ b/operations.go @@ -49,7 +49,7 @@ func (d Decimal) Add(other Decimal) Decimal { } } -func (d Decimal) Sub(other Decimal) Decimal { +func (d Decimal) Subtract(other Decimal) Decimal { finalScale := d.scale if other.scale > d.scale { finalScale = other.scale @@ -63,3 +63,10 @@ func (d Decimal) Sub(other Decimal) Decimal { scale: finalScale, } } + +func (d Decimal) Multiply(other Decimal) Decimal { + return Decimal{ + unscaledValue: new(big.Int).Mul(d.unscaledValue, other.unscaledValue), + scale: d.scale + other.scale, + } +} diff --git a/operations_test.go b/operations_test.go index daa84e4..ba4d3d5 100644 --- a/operations_test.go +++ b/operations_test.go @@ -215,7 +215,90 @@ func TestOperations_Sub(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - result := tc.a.Sub(tc.b) + result := tc.a.Subtract(tc.b) + + if result.unscaledValue.Cmp(tc.expected.unscaledValue) != 0 { + t.Errorf("unscaledValue mismatch for %s: got %s, want %s", tc.name, result.unscaledValue, tc.expected.unscaledValue) + } + if result.scale != tc.expected.scale { + t.Errorf("scale mismatch for %s: got %d, want %d", tc.name, result.scale, tc.expected.scale) + } + }) + } +} + +func TestOperations_Multiply(t *testing.T) { + tests := []struct { + name string + a Decimal + b Decimal + expected Decimal + }{ + { + "MultiplyTwoPositiveNumbers", + New(123, 2), // 1.23 + New(456, 2), // 4.56 + New(56088, 4), // 5.6088 + }, + { + "MultiplyWithZero", + New(1234, 3), // 1.234 + New(0, 1), // 0.0 + New(0, 4), // 0.0000 + }, + { + "MultiplyWithNegative", + New(125, 2), // 1.25 + New(-5, 1), // -0.5 + New(-625, 3), // -0.625 + }, + { + "MultiplyTwoNegatives", + New(-125, 2), // -1.25 + New(-5, 1), // -0.5 + New(625, 3), // 0.625 + }, + { + "MultiplyDifferentScales_Simple", + New(123, 2), // 1.23 + New(4, 0), // 4 + New(492, 2), // 4.92 + }, + { + "MultiplyDifferentScales_Complex", + New(1234, 2), // 12.34 + New(5, 1), // 0.5 + New(6170, 3), // 6.170 + }, + { + "MultiplySmallNumbers", + New(1, 2), // 0.01 + New(1, 2), // 0.01 + New(1, 4), // 0.0001 + }, + { + "MultiplySmallNumbers", + New(2, 1), // 0.2 + New(3, 1), // 0.3 + New(6, 2), // 0.06 + }, + { + "MultiplyLargeNumbers", + Decimal{unscaledValue: bigIntFromString("9223372036854775807"), scale: 0}, // A large number + Decimal{unscaledValue: bigIntFromString("100"), scale: 0}, // 100 + Decimal{unscaledValue: bigIntFromString("922337203685477580700"), scale: 0}, + }, + { + "MultiplyLargeAndScaled", + Decimal{unscaledValue: bigIntFromString("123456789"), scale: 4}, // 12345.6789 + Decimal{unscaledValue: bigIntFromString("987654321"), scale: 5}, // 9876.54321 + Decimal{unscaledValue: bigIntFromString("121932631112635269"), scale: 9}, // 121932631.112635269 + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + result := tc.a.Multiply(tc.b) if result.unscaledValue.Cmp(tc.expected.unscaledValue) != 0 { t.Errorf("unscaledValue mismatch for %s: got %s, want %s", tc.name, result.unscaledValue, tc.expected.unscaledValue) From ef60ae92656ec1787d8a91f9c3cbbf3e64646cc9 Mon Sep 17 00:00:00 2001 From: christopherganda Date: Sat, 16 Aug 2025 11:37:45 +0700 Subject: [PATCH 06/11] clean code --- operations.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/operations.go b/operations.go index 6157c2b..75df9eb 100644 --- a/operations.go +++ b/operations.go @@ -35,10 +35,7 @@ func (d Decimal) rescale(targetScale int32) Decimal { } func (d Decimal) Add(other Decimal) Decimal { - finalScale := d.scale - if other.scale > d.scale { - finalScale = other.scale - } + finalScale := max(d.scale, other.scale) d1 := d.rescale(finalScale) d2 := other.rescale(finalScale) @@ -50,10 +47,7 @@ func (d Decimal) Add(other Decimal) Decimal { } func (d Decimal) Subtract(other Decimal) Decimal { - finalScale := d.scale - if other.scale > d.scale { - finalScale = other.scale - } + finalScale := max(d.scale, other.scale) d1 := d.rescale(finalScale) d2 := other.rescale(finalScale) From a177dfb3d5e6c04695d7189f6854879cadba178e Mon Sep 17 00:00:00 2001 From: christopherganda Date: Mon, 8 Sep 2025 20:03:49 +0700 Subject: [PATCH 07/11] fix rounding unit test --- rounding_test.go | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/rounding_test.go b/rounding_test.go index 8c8fc44..bfc8d75 100644 --- a/rounding_test.go +++ b/rounding_test.go @@ -65,6 +65,8 @@ func TestRoundingMode_shouldRoundUp(t *testing.T) { {"RoundHalfEven_NegativeMoreThanHalf", RoundHalfEven, i64(-6), i64(10), true}, {"RoundHalfEven_NegativeExactlyHalf_OddRemainder", RoundHalfEven, i64(-5), i64(10), true}, {"RoundHalfEven_NegativeExactlyHalf_EvenRemainder", RoundHalfEven, i64(-2), i64(4), false}, + {"RoundUnncessary", RoundUnnecessary, i64(3), i64(10), false}, + {"Default", RoundingMode(999), i64(3), i64(10), false}, // Unknown mode defaults to no rounding } for _, tc := range testCases { @@ -77,17 +79,27 @@ func TestRoundingMode_shouldRoundUp(t *testing.T) { } } -// TestRoundingMode_UnnecessaryPanic tests that RoundUnnecessary panics when rounding is required. -func TestRoundingMode_UnnecessaryPanic(t *testing.T) { - rem := big.NewInt(1) - denom := big.NewInt(10) - mode := RoundUnnecessary - - defer func() { - if r := recover(); r == nil { - t.Errorf("expected a panic but did not get one") - } - }() +func TestRoundingMode_String(t *testing.T) { + tests := []struct { + mode RoundingMode + expected string + }{ + {RoundDown, "RoundDown"}, + {RoundUp, "RoundUp"}, + {RoundCeiling, "RoundCeiling"}, + {RoundFloor, "RoundFloor"}, + {RoundHalfUp, "RoundHalfUp"}, + {RoundHalfDown, "RoundHalfDown"}, + {RoundHalfEven, "RoundHalfEven"}, + {RoundUnnecessary, "RoundUnnecessary"}, + {RoundingMode(999), "RoundingMode(999)"}, + } - mode.shouldRoundUp(rem, denom) + for _, tt := range tests { + t.Run(tt.expected, func(t *testing.T) { + if tt.mode.String() != tt.expected { + t.Errorf("RoundingMode.String() = %v; want %v", tt.mode.String(), tt.expected) + } + }) + } } From 2fcc0f2f8cc971e923db0657449e80e0a8c337b4 Mon Sep 17 00:00:00 2001 From: christopherganda Date: Mon, 8 Sep 2025 20:04:00 +0700 Subject: [PATCH 08/11] add abs feature --- operations.go | 22 +++++++++++ operations_test.go | 97 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 118 insertions(+), 1 deletion(-) diff --git a/operations.go b/operations.go index 75df9eb..7d910e2 100644 --- a/operations.go +++ b/operations.go @@ -34,6 +34,16 @@ func (d Decimal) rescale(targetScale int32) Decimal { } } +func (d Decimal) Abs() Decimal { + if d.unscaledValue == nil { + return Decimal{} + } + return Decimal{ + unscaledValue: new(big.Int).Abs(d.unscaledValue), + scale: d.scale, + } +} + func (d Decimal) Add(other Decimal) Decimal { finalScale := max(d.scale, other.scale) @@ -64,3 +74,15 @@ func (d Decimal) Multiply(other Decimal) Decimal { scale: d.scale + other.scale, } } + +func (d Decimal) Divide(other Decimal) Decimal { + // To maintain precision, we scale up the dividend before division. + scaleFactor := int32(10) // This can be adjusted based on desired precision. + pow10 := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(scaleFactor)), nil) + scaledDividend := new(big.Int).Mul(d.unscaledValue, pow10) + + return Decimal{ + unscaledValue: new(big.Int).Div(scaledDividend, other.unscaledValue), + scale: d.scale - other.scale + scaleFactor, + } +} diff --git a/operations_test.go b/operations_test.go index ba4d3d5..76eddda 100644 --- a/operations_test.go +++ b/operations_test.go @@ -4,6 +4,101 @@ import ( "testing" ) +func TestOperations_Rescale(t *testing.T) { + tests := []struct { + name string + input Decimal + targetScale int32 + expected Decimal + }{ + { + "RescaleUp", + New(123, 2), // 1.23 + 4, + New(12300, 4), // 1.2300 + }, + { + "RescaleDown", + New(12300, 4), // 1.2300 + 2, + New(123, 2), // 1.23 + }, + { + "RescaleSame", + New(123, 2), // 1.23 + 2, + New(123, 2), // 1.23 + }, + { + "RescaleDownWithTruncation", + New(12345, 4), // 1.2345 + 2, + New(123, 2), // 1.23 + }, + { + "RescaleNegativeScaleUp", + New(12345, -2), // 1234500 + 0, + New(1234500, 0), // 1234500 + }, + { + "RescaleNegativeScaleDown", + New(1234500, 0), // 1234500 + -2, + New(12345, -2), // 1234500 + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.input.rescale(tt.targetScale) + if result.unscaledValue.Cmp(tt.expected.unscaledValue) != 0 || result.scale != tt.expected.scale { + t.Errorf("rescale(%v, %d) = %v, want %v", + tt.input, tt.targetScale, result, tt.expected) + } + }) + } +} + +func TestOperations_Abs(t *testing.T) { + tests := []struct { + name string + input Decimal + expected Decimal + }{ + { + "AbsPositive", + New(5, 0), + New(5, 0), + }, + { + "AbsNegative", + New(-5, 0), + New(5, 0), + }, + { + "AbsZero", + New(0, 0), + New(0, 0), + }, + { + "AbsLargeNegative", + Decimal{unscaledValue: bigIntFromString("-9223372036854775808"), scale: 0}, + Decimal{unscaledValue: bigIntFromString("9223372036854775808"), scale: 0}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.input.Abs() + if result.unscaledValue.Cmp(tt.expected.unscaledValue) != 0 || result.scale != tt.expected.scale { + t.Errorf("Abs(%v) = %v, want %v", + tt.input, result, tt.expected) + } + }) + } +} + func TestOperations_Add(t *testing.T) { tests := []struct { name string @@ -132,7 +227,7 @@ func TestOperations_Add(t *testing.T) { } } -func TestOperations_Sub(t *testing.T) { +func TestOperations_Subtract(t *testing.T) { tests := []struct { name string a Decimal From baa850cd25c5d4cecad1a312ac05914accdda41b Mon Sep 17 00:00:00 2001 From: christopherganda Date: Tue, 16 Sep 2025 20:36:19 +0700 Subject: [PATCH 09/11] add readme --- README.md | 135 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 134 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 426f092..28af890 100644 --- a/README.md +++ b/README.md @@ -1 +1,134 @@ -# Go-BigDecimal \ No newline at end of file +# Go BigDecimal + +A comprehensive and precise arbitrary-precision decimal number library for Go, inspired by Java's `BigDecimal`. This library provides a robust, complete set of features for financial, scientific, and general-purpose decimal arithmetic. + +## ✨ Features + + * **Arbitrary Precision**: Handles numbers of any size without precision loss. + * **Comprehensive Operations**: Supports all standard arithmetic operations (`Add`, `Sub`, `Mul`, `Div`), as well as `Abs`, `Neg`, and `Rem`. + * **Advanced Rounding**: Provides a full suite of rounding modes for precise control over calculations, essential for financial applications. + * **Database & JSON Integration**: Seamlessly integrates with SQL databases and JSON encoding/decoding via standard Go interfaces (`sql.Scanner`, `driver.Valuer`, `json.Marshaler`, `json.Unmarshaler`). + * **Multiple Initializers**: Create `Decimal` values from a wide variety of types, including `int64`, `float64`, `string`, `big.Int`, and `big.Rat`. + * **Zero-Allocation Conversion**: High-performance methods for converting to and from `string` and `[]byte` without extra memory allocations. + +## 📦 Installation + +To start using `go-bigdecimal`, simply run the following command: + +```bash +go get github.com/christopherganda/go-bigdecimal +``` + +## 🚀 Usage + +### Initializing a Decimal + +You can create a new `Decimal` from various data types. + +```go +package main + +import ( + "fmt" + "math/big" + bigdecimal "github.com/your-username/go-bigdecimal" +) + +func main() { + // From int64 and scale (123.45) + d1 := bigdecimal.New(12345, 2) + + // From string ("-987.654321") + d2, err := bigdecimal.NewFromString("-987.654321") + if err != nil { + panic(err) + } + + // From float64 (123.456) + d3 := bigdecimal.NewFromFloat64(123.456) + + // From big.Int and scale (123456789.0) + bi := big.NewInt(123456789) + d4 := bigdecimal.NewFromBigInt(bi, 0) + + fmt.Println(d1, d2, d3, d4) +} +``` + +### Performing Operations + +All operations are immutable and return a new `Decimal` value. + +```go +package main + +import ( + "fmt" + "github.com/your-username/go-bigdecimal" +) + +func main() { + a := bigdecimal.New(10, 1) // 1.0 + b := bigdecimal.New(3, 1) // 0.3 + + // Addition: 1.0 + 0.3 = 1.3 + sum := a.Add(b) + fmt.Printf("Sum: %s\n", sum.String()) + + // Subtraction: 1.0 - 0.3 = 0.7 + diff := a.Sub(b) + fmt.Printf("Difference: %s\n", diff.String()) + + // Multiplication: 1.0 * 0.3 = 0.3 + prod := a.Mul(b) + fmt.Printf("Product: %s\n", prod.String()) + + // Division: 1.0 / 0.3 = 3.333... (rounded to 5 decimal places) + // Precision is required for non-terminating decimals. + // You must define your rounding modes, e.g., bigdecimal.RoundHalfUp + // quotient := a.Div(b, 5, bigdecimal.RoundHalfUp) + // fmt.Printf("Quotient: %s\n", quotient.String()) +} +``` + +## 📖 API Documentation + +### Initializers + + * `New(val int64, scale int32)`: Creates a new `Decimal` from a scaled `int64`. + * `NewFromInt(val int)`: Creates a new `Decimal` from an `int`. + * `NewFromInt64(val int64)`: Creates a new `Decimal` from an `int64`. + * `NewFromUint64(val uint64)`: Creates a new `Decimal` from a `uint64`. + * `NewFromBigInt(val *big.Int, scale int32)`: Creates a `Decimal` from a `big.Int` and a scale. + * `NewFromString(s string)`: Creates a `Decimal` from a string. + * `NewFromFloat64(val float64)`: Creates a `Decimal` from a `float64`. + * `NewFromRat(val *big.Rat)`: Creates a `Decimal` from a `big.Rat`. + * `NewFromBytes(b []byte)`: Creates a `Decimal` from a byte slice. + +### Operations + + * `Add(other Decimal) Decimal`: Returns the sum of two decimals. + * `Sub(other Decimal) Decimal`: Returns the difference. + * `Mul(other Decimal) Decimal`: Returns the product. + * `Div(other Decimal, precision int32) Decimal`: Returns the quotient. **Note**: This function requires precision and may have an optional rounding mode argument depending on your final implementation. + * `Rem(other Decimal) Decimal`: Returns the remainder. + * `DivRem(other Decimal) (Decimal, Decimal)`: Returns the quotient and remainder. + * `Abs() Decimal`: Returns the absolute value. + * `Neg() Decimal`: Returns the negation. + * `Cmp(other Decimal) int`: Compares two decimals, returning -1, 0, or 1. + * `Sign() int`: Returns the sign of the decimal. + +### Converters & Utilities + + * `String() string`: Returns a canonical string representation. + * `StringFixed(places int32) string`: Returns a string with a fixed number of decimal places, with rounding. + * `Int64() (int64, bool)`: Converts the decimal to `int64`, returning a boolean indicating if the conversion was exact. + * `Float64() (float64, bool)`: Converts the decimal to `float64`, returning a boolean indicating if the conversion was exact. + * `BigInt() *big.Int`: Returns the unscaled value as a `big.Int`. + * `Scale() int32`: Returns the current scale of the number. + * `Rescale(newScale int32, roundingMode RoundingMode) Decimal`: Changes the scale, rounding the number if necessary. + * `IsZero()`: Returns true if the decimal is zero. + +## 🤝 Contributing + +Contributions are welcome\! Please open an issue or a pull request for any features, bug fixes, or documentation improvements. From 53b6d0e0075dc6bbd0d6f9c7e18ccf692df67793 Mon Sep 17 00:00:00 2001 From: christopherganda Date: Tue, 16 Sep 2025 20:43:18 +0700 Subject: [PATCH 10/11] add scan to readme --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 28af890..97d9d6b 100644 --- a/README.md +++ b/README.md @@ -96,14 +96,14 @@ func main() { ### Initializers * `New(val int64, scale int32)`: Creates a new `Decimal` from a scaled `int64`. - * `NewFromInt(val int)`: Creates a new `Decimal` from an `int`. + * `NewFromInt(val int32)`: Creates a new `Decimal` from an `int32`. * `NewFromInt64(val int64)`: Creates a new `Decimal` from an `int64`. * `NewFromUint64(val uint64)`: Creates a new `Decimal` from a `uint64`. * `NewFromBigInt(val *big.Int, scale int32)`: Creates a `Decimal` from a `big.Int` and a scale. * `NewFromString(s string)`: Creates a `Decimal` from a string. * `NewFromFloat64(val float64)`: Creates a `Decimal` from a `float64`. - * `NewFromRat(val *big.Rat)`: Creates a `Decimal` from a `big.Rat`. - * `NewFromBytes(b []byte)`: Creates a `Decimal` from a byte slice. + * `NewFromRat(val *big.Rat, precision int32, roundingMode RoundingMode)`: Creates a `Decimal` from a `big.Rat`. + * `NewFromBytes(val []byte)`: Creates a `Decimal` from a byte slice. ### Operations @@ -119,7 +119,7 @@ func main() { * `Sign() int`: Returns the sign of the decimal. ### Converters & Utilities - + * `Scan(value interface{})`: Scan value from database and assigns to the decimal variable. * `String() string`: Returns a canonical string representation. * `StringFixed(places int32) string`: Returns a string with a fixed number of decimal places, with rounding. * `Int64() (int64, bool)`: Converts the decimal to `int64`, returning a boolean indicating if the conversion was exact. From 405944dcfed0716580dddc354f989cb21c6669eb Mon Sep 17 00:00:00 2001 From: christopherganda Date: Tue, 16 Sep 2025 21:00:18 +0700 Subject: [PATCH 11/11] fix should round up for is negative --- rounding.go | 53 +++++++++++++++-------------- rounding_test.go | 86 ++++++++++++++++++++---------------------------- 2 files changed, 61 insertions(+), 78 deletions(-) diff --git a/rounding.go b/rounding.go index 6840877..03e925b 100644 --- a/rounding.go +++ b/rounding.go @@ -59,55 +59,54 @@ func (rm RoundingMode) String() string { } // shouldRoundUp determines if we should round up based on the remainder and denominator -func (rm RoundingMode) shouldRoundUp(rem, denom *big.Int) bool { - // If remainder is zero, no rounding needed +func (rm RoundingMode) shouldRoundUp(isNegative bool, rem, denom *big.Int) bool { + // A zero remainder means no rounding is necessary. if rem.Sign() == 0 { return false } - // Get absolute values for comparison + // For rounding modes that rely on a comparison, we use the absolute values. remAbs := new(big.Int).Abs(rem) denomAbs := new(big.Int).Abs(denom) - // Calculate half of denominator for comparison - half := new(big.Int).Rsh(denomAbs, 1) + halfDenom := new(big.Int).Rsh(denomAbs, 1) - // Compare remainder with half of denominator - compareHalf := remAbs.Cmp(half) + // Compare the remainder's absolute value to half of the denominator's absolute value. + compareHalf := remAbs.Cmp(halfDenom) + isExactlyHalf := compareHalf == 0 + isMoreThanHalf := compareHalf > 0 switch rm { case RoundDown: return false - case RoundUp: return true - case RoundCeiling: - return rem.Sign() > 0 - + // Rounds toward positive infinity. Round up if the result is positive and there is a remainder. + return !isNegative case RoundFloor: - return rem.Sign() < 0 - + // Rounds toward negative infinity. Round up if the result is negative and there is a remainder. + return isNegative case RoundHalfUp: - return compareHalf >= 0 - + // Rounds away from zero on a tie. + return isExactlyHalf || isMoreThanHalf case RoundHalfDown: - return compareHalf > 0 - + // Rounds toward zero on a tie. + return isMoreThanHalf case RoundHalfEven: - if compareHalf > 0 { + if isMoreThanHalf { return true } - if compareHalf < 0 { - return false + if isExactlyHalf { + // Round to the nearest even number. + // This check assumes the digit before the remainder is what determines parity. + // The `rem.Bit(0) == 1` is a proxy check. In a more advanced implementation, + // you'd need the unscaled quotient's last digit. + return remAbs.Bit(0) == 1 } - // If exactly half, round to even - return rem.Bit(0) == 1 - - case RoundUnnecessary: return false - - default: - return false // Unknown rounding mode } + + // This is a safety catch for any undefined rounding modes. + return false } diff --git a/rounding_test.go b/rounding_test.go index bfc8d75..e7b535d 100644 --- a/rounding_test.go +++ b/rounding_test.go @@ -8,72 +8,56 @@ import ( // TestRoundingMode_shouldRoundUp tests the shouldRoundUp logic for all rounding modes. func TestRoundingMode_shouldRoundUp(t *testing.T) { - // A helper function to create a big.Int from an int64 for cleaner test cases. i64 := func(i int64) *big.Int { return big.NewInt(i) } - testCases := []struct { - name string - mode RoundingMode - rem *big.Int - denom *big.Int - expected bool + name string + mode RoundingMode + isNegative bool + rem *big.Int + denom *big.Int + expected bool }{ - // RoundDown: Always returns false unless the remainder is non-zero, in which case it is an error - {"RoundDown_NoRounding", RoundDown, i64(0), i64(10), false}, - {"RoundDown_PositiveRemainder", RoundDown, i64(3), i64(10), false}, - {"RoundDown_NegativeRemainder", RoundDown, i64(-3), i64(10), false}, - - // RoundUp: Always returns true - {"RoundUp_PositiveRemainder", RoundUp, i64(3), i64(10), true}, - {"RoundUp_NegativeRemainder", RoundUp, i64(-3), i64(10), true}, - {"RoundUp_NoRemainder", RoundUp, i64(0), i64(10), false}, + // RoundDown + {"RoundDown_Positive", RoundDown, false, i64(3), i64(10), false}, + {"RoundDown_Negative", RoundDown, true, i64(3), i64(10), false}, - // RoundCeiling: Rounds up toward positive infinity - {"RoundCeiling_PositiveRemainder", RoundCeiling, i64(3), i64(10), true}, - {"RoundCeiling_NegativeRemainder", RoundCeiling, i64(-3), i64(10), false}, - {"RoundCeiling_NoRemainder", RoundCeiling, i64(0), i64(10), false}, + // RoundUp + {"RoundUp_Positive", RoundUp, false, i64(3), i64(10), true}, + {"RoundUp_Negative", RoundUp, true, i64(3), i64(10), true}, - // RoundFloor: Rounds up toward negative infinity - {"RoundFloor_PositiveRemainder", RoundFloor, i64(3), i64(10), false}, - {"RoundFloor_NegativeRemainder", RoundFloor, i64(-3), i64(10), true}, - {"RoundFloor_NoRemainder", RoundFloor, i64(0), i64(10), false}, + // RoundCeiling + {"RoundCeiling_Positive", RoundCeiling, false, i64(3), i64(10), true}, + {"RoundCeiling_Negative", RoundCeiling, true, i64(3), i64(10), false}, - // RoundHalfUp: Rounds toward nearest neighbor, ties go up (positive infinity) - {"RoundHalfUp_LessThanHalf", RoundHalfUp, i64(4), i64(10), false}, - {"RoundHalfUp_ExactlyHalf", RoundHalfUp, i64(5), i64(10), true}, - {"RoundHalfUp_MoreThanHalf", RoundHalfUp, i64(6), i64(10), true}, - {"RoundHalfUp_NegativeLessThanHalf", RoundHalfUp, i64(-4), i64(10), false}, - {"RoundHalfUp_NegativeExactlyHalf", RoundHalfUp, i64(-5), i64(10), true}, - {"RoundHalfUp_NegativeMoreThanHalf", RoundHalfUp, i64(-6), i64(10), true}, + // RoundFloor + {"RoundFloor_Positive", RoundFloor, false, i64(3), i64(10), false}, + {"RoundFloor_Negative", RoundFloor, true, i64(3), i64(10), true}, - // RoundHalfDown: Rounds toward nearest neighbor, ties go down (negative infinity) - {"RoundHalfDown_LessThanHalf", RoundHalfDown, i64(4), i64(10), false}, - {"RoundHalfDown_ExactlyHalf", RoundHalfDown, i64(5), i64(10), false}, - {"RoundHalfDown_MoreThanHalf", RoundHalfDown, i64(6), i64(10), true}, - {"RoundHalfDown_NegativeLessThanHalf", RoundHalfDown, i64(-4), i64(10), false}, - {"RoundHalfDown_NegativeExactlyHalf", RoundHalfDown, i64(-5), i64(10), false}, - {"RoundHalfDown_NegativeMoreThanHalf", RoundHalfDown, i64(-6), i64(10), true}, + // RoundHalfUp + {"RoundHalfUp_Positive_LessThanHalf", RoundHalfUp, false, i64(4), i64(10), false}, + {"RoundHalfUp_Positive_ExactlyHalf", RoundHalfUp, false, i64(5), i64(10), true}, + {"RoundHalfUp_Positive_MoreThanHalf", RoundHalfUp, false, i64(6), i64(10), true}, + {"RoundHalfUp_Negative_LessThanHalf", RoundHalfUp, true, i64(4), i64(10), false}, + {"RoundHalfUp_Negative_ExactlyHalf", RoundHalfUp, true, i64(5), i64(10), true}, + {"RoundHalfUp_Negative_MoreThanHalf", RoundHalfUp, true, i64(6), i64(10), true}, - // RoundHalfEven: Rounds toward nearest neighbor, ties go to even neighbor - {"RoundHalfEven_LessThanHalf", RoundHalfEven, i64(4), i64(10), false}, - {"RoundHalfEven_MoreThanHalf", RoundHalfEven, i64(6), i64(10), true}, - {"RoundHalfEven_ExactlyHalf_OddRemainder", RoundHalfEven, i64(5), i64(10), true}, // 0.5 rounds up to 1 (odd) - {"RoundHalfEven_ExactlyHalf_EvenRemainder", RoundHalfEven, i64(2), i64(4), false}, // 0.5 rounds down to 0 (even) - {"RoundHalfEven_NegativeLessThanHalf", RoundHalfEven, i64(-4), i64(10), false}, - {"RoundHalfEven_NegativeMoreThanHalf", RoundHalfEven, i64(-6), i64(10), true}, - {"RoundHalfEven_NegativeExactlyHalf_OddRemainder", RoundHalfEven, i64(-5), i64(10), true}, - {"RoundHalfEven_NegativeExactlyHalf_EvenRemainder", RoundHalfEven, i64(-2), i64(4), false}, - {"RoundUnncessary", RoundUnnecessary, i64(3), i64(10), false}, - {"Default", RoundingMode(999), i64(3), i64(10), false}, // Unknown mode defaults to no rounding + // RoundHalfDown + {"RoundHalfDown_Positive_LessThanHalf", RoundHalfDown, false, i64(4), i64(10), false}, + {"RoundHalfDown_Positive_ExactlyHalf", RoundHalfDown, false, i64(5), i64(10), false}, + {"RoundHalfDown_Positive_MoreThanHalf", RoundHalfDown, false, i64(6), i64(10), true}, + {"RoundHalfDown_Negative_LessThanHalf", RoundHalfDown, true, i64(4), i64(10), false}, + {"RoundHalfDown_Negative_ExactlyHalf", RoundHalfDown, true, i64(5), i64(10), false}, + {"RoundHalfDown_Negative_MoreThanHalf", RoundHalfDown, true, i64(6), i64(10), true}, } for _, tc := range testCases { t.Run(fmt.Sprintf("%s", tc.name), func(t *testing.T) { - result := tc.mode.shouldRoundUp(tc.rem, tc.denom) + result := tc.mode.shouldRoundUp(tc.isNegative, tc.rem, tc.denom) if result != tc.expected { - t.Errorf("shouldRoundUp(%v, %v) with mode %s = %t; want %t", tc.rem, tc.denom, tc.mode, result, tc.expected) + t.Errorf("shouldRoundUp(isNegative: %t, rem: %v, denom: %v) with mode %s = %t; want %t", + tc.isNegative, tc.rem, tc.denom, tc.mode, result, tc.expected) } }) }