commit d87bf453309066aeefe88cec87e7ecd3e65d0195
parent e4dd3a89023c391d55c50235aaeae057cff33392
Author: Antoine A <>
Date: Mon, 30 Oct 2023 20:28:38 +0000
Handle the tiniest tiny amount in multiplication
Diffstat:
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;