libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

commit d87bf453309066aeefe88cec87e7ecd3e65d0195
parent e4dd3a89023c391d55c50235aaeae057cff33392
Author: Antoine A <>
Date:   Mon, 30 Oct 2023 20:28:38 +0000

Handle the tiniest tiny amount in multiplication

Diffstat:
Mbank/src/test/kotlin/AmountTest.kt | 8+++++---
Mdatabase-versioning/libeufin-bank-procedures.sql | 6+++---
2 files changed, 8 insertions(+), 6 deletions(-)

diff --git a/bank/src/test/kotlin/AmountTest.kt b/bank/src/test/kotlin/AmountTest.kt @@ -227,15 +227,14 @@ class AmountTest { assertEquals(TalerAmount("EUR:30.0629"), mul(TalerAmount("EUR:6.41"), DecimalNumber("4.69"))) assertEquals(TalerAmount("EUR:6.41000641"), mul(TalerAmount("EUR:6.41"), DecimalNumber("1.000001"))) - assertEquals(TalerAmount("EUR:2.49999998"), mul(TalerAmount("EUR:0.99999999"), DecimalNumber("2.5"))) - assertEquals(TalerAmount("EUR:2.49999998"), mul(TalerAmount("EUR:0.99999999"), DecimalNumber("2.5"))) + assertEquals(TalerAmount("EUR:2.49999997"), mul(TalerAmount("EUR:0.99999999"), DecimalNumber("2.5"))) assertEquals(TalerAmount("EUR:${TalerAmount.MAX_VALUE}.99999999"), mul(TalerAmount("EUR:${TalerAmount.MAX_VALUE}.99999999"), DecimalNumber("1"))) assertEquals(TalerAmount("EUR:${TalerAmount.MAX_VALUE}"), mul(TalerAmount("EUR:${TalerAmount.MAX_VALUE/4}"), DecimalNumber("4"))) assertException("ERROR: amount value overflowed") { mul(TalerAmount(TalerAmount.MAX_VALUE/3, 0, "EUR"), DecimalNumber("3.00000001")) } assertException("ERROR: amount value overflowed") { mul(TalerAmount((TalerAmount.MAX_VALUE+2)/2, 0, "EUR"), DecimalNumber("2")) } assertException("ERROR: numeric field overflow") { mul(TalerAmount(Long.MAX_VALUE, 0, "EUR"), DecimalNumber("1")) } - // Check euro rounding mode + // Check rounding mode for ((mode, rounding) in listOf( Pair("round-to-zero", listOf(Pair(1, listOf(10, 11, 12, 12, 14, 15, 16, 17, 18, 19)))), Pair("round-up", listOf(Pair(1, listOf(10)), Pair(2, listOf(11, 12, 12, 14, 15, 16, 17, 18, 19)))), @@ -243,7 +242,10 @@ class AmountTest { )) { for ((rounded, amounts) in rounding) { for (amount in amounts) { + // Check euro assertEquals(TalerAmount("EUR:0.0$rounded"), mul(TalerAmount("EUR:$amount"), DecimalNumber("0.001001"), DecimalNumber("0.01"), mode)) + // Check kudos + assertEquals(TalerAmount("KUDOS:0.0000000$rounded"), mul(TalerAmount("KUDOS:0.$amount"), DecimalNumber("0.0000001"), roundingMode = mode)) } } } diff --git a/database-versioning/libeufin-bank-procedures.sql b/database-versioning/libeufin-bank-procedures.sql @@ -40,16 +40,16 @@ CREATE OR REPLACE FUNCTION amount_mul( ) LANGUAGE plpgsql AS $$ DECLARE - product_numeric NUMERIC(32, 8); -- 16 digit for val and 8 for frac + product_numeric NUMERIC(33, 9); -- 16 digit for val, 8 for frac and 1 for rounding error tiny_numeric NUMERIC; rounding_error int2; BEGIN -- Perform multiplication using big numbers - product_numeric = (a.val::numeric(24, 8) + a.frac::numeric(24, 8) / 100000000) * (b.val::numeric(24, 8) + b.frac::numeric(24, 8) / 100000000); + product_numeric = (a.val::numeric(25, 9) + a.frac::numeric(25, 9) / 100000000) * (b.val::numeric(25, 9) + b.frac::numeric(25, 8) / 100000000); -- Round to tiny amounts product_numeric = product_numeric * 100000000; - tiny_numeric = (tiny.val::numeric(32, 8) * 100000000 + tiny.frac::numeric(32, 8)); + tiny_numeric = (tiny.val::numeric(33, 9) * 100000000 + tiny.frac::numeric(33, 9)); product_numeric = product_numeric / tiny_numeric; rounding_error = trunc(product_numeric * 10 % 10)::int2; product_numeric = trunc(product_numeric)*tiny_numeric;