From 06e5e09d704036d6ffac02f8b104a931093b7215 Mon Sep 17 00:00:00 2001 From: Jamie Gaskins Date: Mon, 2 Dec 2024 23:28:37 -0600 Subject: [PATCH 1/4] Add PG::Numeric#to_big_d --- spec/pg/numeric_spec.cr | 47 +++++++++++++++++++++++++++++++++------ src/pg_ext/big_decimal.cr | 27 ++++++++++++++++++++++ 2 files changed, 67 insertions(+), 7 deletions(-) create mode 100644 src/pg_ext/big_decimal.cr diff --git a/spec/pg/numeric_spec.cr b/spec/pg/numeric_spec.cr index f355416c..ec2c7afa 100644 --- a/spec/pg/numeric_spec.cr +++ b/spec/pg/numeric_spec.cr @@ -1,5 +1,6 @@ require "../spec_helper" require "../../src/pg_ext/big_rational" +require "../../src/pg_ext/big_decimal" private def n(nd, w, s, ds, d) PG::Numeric.new(nd.to_i16, w.to_i16, s.to_i16, ds.to_i16, d.map(&.to_i16)) @@ -9,6 +10,14 @@ private def br(n, d) BigRational.new(n, d) end +private def bd(n, scale) + BigDecimal.new(n, scale) +end + +private def bd(value : String) + BigDecimal.new(value) +end + private def ex(which) case which when "nan" @@ -79,8 +88,8 @@ describe PG::Numeric do {"-0.0000009", -0.0000009_f64}, {"-0.00000009", -0.00000009_f64}, {"0.0...9", 0.0000000000000000000000000000000000009999999_f64}, - ].each do |x| - ex(x[0]).to_f.should be_close(x[1], 1e-50) + ].each do |string, float| + ex(string).to_f.should be_close(float, 1e-50) end end @@ -99,8 +108,32 @@ describe PG::Numeric do {"-0.0000009", br(-9, 10000000)}, {"-0.00000009", br(-9, 100000000)}, {"0.0...9", br(BigInt.new(9999999), BigInt.new(10)**43)}, - ].each do |x| - ex(x[0]).to_big_r.should eq(x[1]) + ].each do |string, big_rational| + ex(string).to_big_r.should eq(big_rational) + end + end + + describe "#to_big_d" do + [ + {"nan", bd(0, 1)}, + {"0", bd(0, 1)}, + {"0.0", bd(0, 1)}, + {"1", bd(1, 1)}, + {"-1", bd(-1, 1)}, + {"1.3", bd(13, 1)}, + {"1.30", bd(13, 1)}, + {"12345.6789123", bd(123456789123, 7)}, + {"2566.1918905000002000", bd("2566.1918905000002000")}, + {"-0.00009", bd(-9, 5)}, + {"-0.000009", bd(-9, 6)}, + {"-0.0000009", bd(-9, 7)}, + {"-0.00000009", bd(-9, 8)}, + {"0.0...9", bd(BigInt.new(9999999), 43)}, + ].each do |string, big_decimal| + numeric = ex(string) + it "converts #{numeric} to #{big_decimal}" do + numeric.to_big_d.should eq(big_decimal) + end end end @@ -129,9 +162,9 @@ describe PG::Numeric do {"500000093", "500000093"}, {"0.0000006000000", "0.0000006000000"}, {"50093.60754417", "50093.60754417"}, - ].each do |x| - ex(x[0]).to_s.should eq(x[1]) - ex(x[0]).inspect.should eq(x[1]) + ].each do |source_string, result_string| + ex(source_string).to_s.should eq(result_string) + ex(source_string).inspect.should eq(result_string) end end diff --git a/src/pg_ext/big_decimal.cr b/src/pg_ext/big_decimal.cr new file mode 100644 index 00000000..26668685 --- /dev/null +++ b/src/pg_ext/big_decimal.cr @@ -0,0 +1,27 @@ +require "big" + +module PG + struct Numeric + # Returns a BigDecimal representation of the numeric. This retains all + # precision, but requires LibGMP installed. + def to_big_d + return BigDecimal.new(0, 0) if nan? || ndigits == 0 + + ten_k = BigInt.new(10_000) + num = digits.reduce(BigInt.new(0)) { |a, i| a*ten_k + BigInt.new(i) } + scale = 4 * (ndigits - 1 - weight) + quot = BigDecimal.new(num, scale) + neg? ? -quot : quot + end + end + + class ResultSet + def read(t : BigRational.class) + read(PG::Numeric).to_big_d + end + + def read(t : BigRational?.class) + read(PG::Numeric?).try &.to_big_d + end + end +end From a3caf46895fbdc17006199a2f025b8806f6c73b5 Mon Sep 17 00:00:00 2001 From: Jamie Gaskins Date: Tue, 3 Dec 2024 00:13:24 -0600 Subject: [PATCH 2/4] lol I converted the numbers wrong for the specs --- spec/pg/numeric_spec.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/pg/numeric_spec.cr b/spec/pg/numeric_spec.cr index ec2c7afa..4164c628 100644 --- a/spec/pg/numeric_spec.cr +++ b/spec/pg/numeric_spec.cr @@ -118,8 +118,8 @@ describe PG::Numeric do {"nan", bd(0, 1)}, {"0", bd(0, 1)}, {"0.0", bd(0, 1)}, - {"1", bd(1, 1)}, - {"-1", bd(-1, 1)}, + {"1", bd(1, 0)}, + {"-1", bd(-1, 0)}, {"1.3", bd(13, 1)}, {"1.30", bd(13, 1)}, {"12345.6789123", bd(123456789123, 7)}, From 1920a2dca613a0b6ff58470fb98bbd621b38e865 Mon Sep 17 00:00:00 2001 From: Jamie Gaskins Date: Tue, 3 Dec 2024 00:20:13 -0600 Subject: [PATCH 3/4] Use the right type for ResultSet#read --- src/pg_ext/big_decimal.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pg_ext/big_decimal.cr b/src/pg_ext/big_decimal.cr index 26668685..6244355e 100644 --- a/src/pg_ext/big_decimal.cr +++ b/src/pg_ext/big_decimal.cr @@ -16,11 +16,11 @@ module PG end class ResultSet - def read(t : BigRational.class) + def read(t : BigDecimal.class) read(PG::Numeric).to_big_d end - def read(t : BigRational?.class) + def read(t : BigDecimal?.class) read(PG::Numeric?).try &.to_big_d end end From f91da72c1bdfb3bbd479626a03319248695641af Mon Sep 17 00:00:00 2001 From: Jamie Gaskins Date: Tue, 3 Dec 2024 09:02:39 -0600 Subject: [PATCH 4/4] Update changelog --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 168f0c4b..6e6f6a09 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ v? upcoming ===================== +* Add support for `BigDecimal` via `PG::Numeric#to_big_d` (thanks @jgaskins) v0.29.0 2024-11-05 =====================