diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..4d48419 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "reference/decimal"] + path = reference/decimal + url = https://github.com/cppalliance/decimal.git diff --git a/d64/math_test.go b/d64/math_test.go index 70912ab..8123c5a 100644 --- a/d64/math_test.go +++ b/d64/math_test.go @@ -99,6 +99,13 @@ func TestDecimalAddInf(t *testing.T) { equal(t, QNaN, NegInf.Add(Inf)) } +func TestAddAdhoc(t *testing.T) { + t.Parallel() + + // add := testBinop(Decimal.Add) + // t.Run("1", add("0.4164333216995441", "0.1628118190198403", "0.2536215026797038")) +} + func TestDecimalCmp(t *testing.T) { t.Parallel() diff --git a/d64/util_test.go b/d64/util_test.go index e708fc4..f194b1e 100644 --- a/d64/util_test.go +++ b/d64/util_test.go @@ -58,6 +58,20 @@ func equalD64(t *testing.T, expected, actual Decimal) pass { return equal(t, expected.String(), actual.String()) } +func testBinop(op func(a, b Decimal) Decimal) func(expected, a, b string) func(*testing.T) { + return func(expected, a, b string) func(*testing.T) { + return func(t *testing.T) { + t.Helper() + + e := MustParse(expected) + x := MustParse(a) + y := MustParse(b) + z := op(x, y) + equalD64(t, e, z) + } + } +} + func isnil(t *testing.T, a any) pass { t.Helper() if a != nil { diff --git a/reference/decimal b/reference/decimal new file mode 160000 index 0000000..f34b02b --- /dev/null +++ b/reference/decimal @@ -0,0 +1 @@ +Subproject commit f34b02b80dc098baa1f065200b40b3b2e3f05775 diff --git a/reference/decref/decref.cpp b/reference/decref/decref.cpp new file mode 100644 index 0000000..c281476 --- /dev/null +++ b/reference/decref/decref.cpp @@ -0,0 +1,44 @@ +#include "decref.h" + +#include +#include + +#include +#include +#include + +using namespace boost::decimal; + +// Inline function to convert decimal64 to Dec64 +inline Dec64 f64(decimal64 d) { + return *(Dec64*)&d; +} + +// Inline function to convert Dec64 to decimal64 +inline decimal64 t64(Dec64 d) { + return *(decimal64*)(&d); +} + +Dec64 parse64(const char* str) { + decimal64 d; + std::istringstream(str) >> d; + return f64(d); +} + +char* string64(Dec64 wrapper) { + std::ostringstream oss; + oss << std::scientific + << std::setprecision(std::numeric_limits::max_digits10) + << t64(wrapper) << std::flush; + return strdup(oss.str().c_str()); +} + +Dec64 frombid64(uint64_t b) { return f64(from_bid(b)); } +uint64_t tobid64(Dec64 a) { return to_bid(t64(a)); } + +int isNaN64(Dec64 a) { return isnan(t64(a)); } + +Dec64 add64(Dec64 a, Dec64 b) { return f64(t64(a) + t64(b)); } +Dec64 sub64(Dec64 a, Dec64 b) { return f64(t64(a) - t64(b)); } +Dec64 mul64(Dec64 a, Dec64 b) { return f64(t64(a) * t64(b)); } +Dec64 quo64(Dec64 a, Dec64 b) { return f64(t64(a) / t64(b)); } diff --git a/reference/decref/decref.go b/reference/decref/decref.go new file mode 100644 index 0000000..9cf4537 --- /dev/null +++ b/reference/decref/decref.go @@ -0,0 +1,34 @@ +package decref + +/* +#cgo CXXFLAGS: -std=c++17 -I../decimal/include +#include "decref.h" +#include +*/ +import "C" + +import "unsafe" + +type Dec64 C.Dec64 + +func Parse64(s string) Dec64 { + cstr := C.CString(s) + defer C.free(unsafe.Pointer(cstr)) + return Dec64(C.parse64(cstr)) +} + +func (d Dec64) String() string { + cstr := C.string64(C.Dec64(d)) + defer C.free(unsafe.Pointer(cstr)) + return C.GoString(cstr) +} + +func FromBid64(b uint64) Dec64 { return Dec64(C.frombid64(C.uint64_t(b))) } +func (d Dec64) ToBid() uint64 { return uint64(C.tobid64(C.Dec64(d))) } + +func (d Dec64) IsNaN() bool { return C.isNaN64(C.Dec64(d)) != 0 } + +func (d Dec64) Add(e Dec64) Dec64 { return Dec64(C.add64(C.Dec64(d), C.Dec64(e))) } +func (d Dec64) Sub(e Dec64) Dec64 { return Dec64(C.sub64(C.Dec64(d), C.Dec64(e))) } +func (d Dec64) Mul(e Dec64) Dec64 { return Dec64(C.mul64(C.Dec64(d), C.Dec64(e))) } +func (d Dec64) Quo(e Dec64) Dec64 { return Dec64(C.quo64(C.Dec64(d), C.Dec64(e))) } diff --git a/reference/decref/decref.h b/reference/decref/decref.h new file mode 100644 index 0000000..a603266 --- /dev/null +++ b/reference/decref/decref.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef uint64_t Dec64; + +Dec64 parse64(const char* str); +char* string64(Dec64 wrapper); + +Dec64 frombid64(uint64_t b); +uint64_t tobid64(Dec64 a); + +int isNaN64(Dec64 a); + +Dec64 add64(Dec64 a, Dec64 b); +Dec64 sub64(Dec64 a, Dec64 b); +Dec64 mul64(Dec64 a, Dec64 b); +Dec64 quo64(Dec64 a, Dec64 b); + +#ifdef __cplusplus +} +#endif diff --git a/reference/decref/decref_test.go b/reference/decref/decref_test.go new file mode 100644 index 0000000..b72abc8 --- /dev/null +++ b/reference/decref/decref_test.go @@ -0,0 +1,82 @@ +package decref_test + +import ( + "fmt" + "math/rand/v2" + "strings" + "testing" + + "github.com/anz-bank/decimal/d64" + "github.com/anz-bank/decimal/reference/decref" +) + +func TestParseString(t *testing.T) { + t.Parallel() + + d := decref.Parse64("3.142") + if d.String() != "3.142" { + t.Errorf("expected 3.142, got %s", d.String()) + } +} + +func TestAdd(t *testing.T) { + t.Parallel() + + rng := rand.New(rand.NewPCG(0, 0)) + for i := range 1000000 { + decrefA := randDec(i, rng) + decrefB := randDec(i, rng) + + aStr := formatDec(decrefA) + bStr := formatDec(decrefB) + + d64A := d64.MustParse(aStr) + d64B := d64.MustParse(bStr) + + // Perform addition + decrefResult := decrefA.Add(decrefB) + d64Result := d64A.Add(d64B) + + d64Str := d64Result.Text('e', -1) + decrefStr := formatDec(decrefResult) + + if d64Str != decrefStr { + t.Errorf("Mismatch: d64Result=%s, decrefResult=%s for a=%s, b=%s, %s", d64Str, decrefStr, aStr, bStr, decrefResult.String()) + t.FailNow() + } + } +} + +func formatDec(d decref.Dec64) string { + decrefStr := d.String() + // Trim mantissa trailing zeros. + if a, b, cut := strings.Cut(decrefStr, "0e"); cut { + decrefStr = strings.TrimRight(a, "0") + "e" + b + } + // Trim exponent leading zeros and all-zeros. + if a, b, cut := strings.Cut(decrefStr, "+0"); cut { + decrefStr = a[:len(a)-1] + if exp := strings.TrimLeft(b, "0"); exp != "" { + decrefStr += "e+" + exp + } + } + // Trim negative-exponent leading zeros. + if a, b, cut := strings.Cut(decrefStr, "-0"); cut { + decrefStr = a + "-" + strings.TrimLeft(b, "0") + } + return decrefStr +} + +func randDec(i int, rng *rand.Rand) decref.Dec64 { + sign := [2]string{"", "-"}[rng.IntN(2)] + const explimit = 385 + exp := rng.IntN(2*explimit+1) - (explimit - 1) + hidigit := rng.IntN(10) + lodigits := rng.Int64N(1_000_000_000_000_000) + s := fmt.Sprintf("%s%d.%dE%d", sign, hidigit, lodigits, exp) + d := decref.Parse64(s) + if d.IsNaN() { + panic(fmt.Errorf("[%d] unparsable: %s (d = %s)", i, s, d)) + } + return d +} diff --git a/reference/decref/go.mod b/reference/decref/go.mod new file mode 100644 index 0000000..f7005f6 --- /dev/null +++ b/reference/decref/go.mod @@ -0,0 +1,5 @@ +module github.com/anz-bank/decimal/reference/decref + +go 1.24.0 + +require github.com/anz-bank/decimal v1.15.0 diff --git a/reference/decref/go.sum b/reference/decref/go.sum new file mode 100644 index 0000000..f1bf4a8 --- /dev/null +++ b/reference/decref/go.sum @@ -0,0 +1,2 @@ +github.com/anz-bank/decimal v1.15.0 h1:wJ9X6XCVpBUqEwapFamKd0eTbCkJPQe9JJSOoShKykU= +github.com/anz-bank/decimal v1.15.0/go.mod h1:lSAX9MYcSaTIxYaII8tXEJbPom/njRKSAX2VMFVh434=