summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2021-07-10 14:52:59 +0200
committerChristian Grothoff <christian@grothoff.org>2021-07-10 14:55:35 +0200
commit249ba03c36d6bc61b78bdd5a7f1ca55701f1c287 (patch)
tree4bb49f6defd74b77b9eed0622c74f41749301717 /src
parent883b1fc70bc84bb053d09b5eab921f24134194b9 (diff)
downloadexchange-249ba03c36d6bc61b78bdd5a7f1ca55701f1c287.tar.gz
exchange-249ba03c36d6bc61b78bdd5a7f1ca55701f1c287.tar.bz2
exchange-249ba03c36d6bc61b78bdd5a7f1ca55701f1c287.zip
expose 2^52 amount value limit in header, check that limit in test cases, add TALER_amount_multiply and TALER_amount_divide2 operations
Diffstat (limited to 'src')
-rw-r--r--src/include/taler_amount_lib.h31
-rw-r--r--src/util/amount.c275
-rw-r--r--src/util/test_amount.c67
3 files changed, 207 insertions, 166 deletions
diff --git a/src/include/taler_amount_lib.h b/src/include/taler_amount_lib.h
index 7a0b299c..5defc37e 100644
--- a/src/include/taler_amount_lib.h
+++ b/src/include/taler_amount_lib.h
@@ -64,6 +64,11 @@ extern "C"
*/
#define TALER_AMOUNT_FRAC_LEN 8
+/**
+ * Maximum legal 'value' for an amount, based on IEEE double (for JavaScript compatibility).
+ */
+#define TALER_AMOUNT_MAX_VALUE (1LLU << 52)
+
GNUNET_NETWORK_STRUCT_BEGIN
@@ -332,6 +337,32 @@ TALER_amount_divide (struct TALER_Amount *result,
const struct TALER_Amount *dividend,
uint32_t divisor);
+/**
+ * Divide one amount by another. Note that this function
+ * may introduce a rounding error. It rounds down.
+ *
+ * @param dividend amount to divide
+ * @param divisor by what to divide, must be positive
+ * @return @a dividend / @a divisor, rounded down. -1 on currency missmatch,
+ * INT_MAX for division by zero
+ */
+int
+TALER_amount_divide2 (const struct TALER_Amount *dividend,
+ const struct TALER_Amount *divisor);
+
+
+/**
+ * Multiply an @a amount by a @ factor.
+ *
+ * @param[out] result where to store @a amount * @a factor
+ * @param amount amount to multiply
+ * @param factor factor by which to multiply
+ */
+enum TALER_AmountArithmeticResult
+TALER_amount_multiply (struct TALER_Amount *result,
+ const struct TALER_Amount *amount,
+ uint32_t factor);
+
/**
* Normalize the given amount.
diff --git a/src/util/amount.c b/src/util/amount.c
index 3aec5461..9012788f 100644
--- a/src/util/amount.c
+++ b/src/util/amount.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014 Taler Systems SA
+ Copyright (C) 2014-2021 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -24,11 +24,6 @@
#include "platform.h"
#include "taler_util.h"
-/**
- * Maximum legal 'value' for an amount, based on IEEE double (for JavaScript compatibility).
- */
-#define MAX_AMOUNT_VALUE (1LLU << 52)
-
/**
* Set @a a to "invalid".
@@ -44,14 +39,6 @@ invalidate (struct TALER_Amount *a)
}
-/**
- * Parse monetary amount, in the format "T:V.F".
- *
- * @param str amount string
- * @param[out] amount amount to write the result to
- * @return #GNUNET_OK if the string is a valid monetary amount specification,
- * #GNUNET_SYSERR if it is invalid.
- */
enum GNUNET_GenericReturnValue
TALER_string_to_amount (const char *str,
struct TALER_Amount *amount)
@@ -128,8 +115,8 @@ TALER_string_to_amount (const char *str,
n = *value - '0';
if ( (amount->value * 10 < amount->value) ||
(amount->value * 10 + n < amount->value) ||
- (amount->value > MAX_AMOUNT_VALUE) ||
- (amount->value * 10 + n > MAX_AMOUNT_VALUE) )
+ (amount->value > TALER_AMOUNT_MAX_VALUE) ||
+ (amount->value * 10 + n > TALER_AMOUNT_MAX_VALUE) )
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Value specified in amount `%s' is too large\n",
@@ -182,15 +169,6 @@ TALER_string_to_amount (const char *str,
}
-/**
- * Parse monetary amount, in the format "T:V.F".
- * The result is stored in network byte order (NBO).
- *
- * @param str amount string
- * @param[out] amount_nbo amount to write the result to
- * @return #GNUNET_OK if the string is a valid amount specification,
- * #GNUNET_SYSERR if it is invalid.
- */
enum GNUNET_GenericReturnValue
TALER_string_to_amount_nbo (const char *str,
struct TALER_AmountNBO *amount_nbo)
@@ -207,12 +185,6 @@ TALER_string_to_amount_nbo (const char *str,
}
-/**
- * Convert amount from host to network representation.
- *
- * @param res where to store amount in network representation
- * @param[out] d amount in host representation
- */
void
TALER_amount_hton (struct TALER_AmountNBO *res,
const struct TALER_Amount *d)
@@ -227,12 +199,6 @@ TALER_amount_hton (struct TALER_AmountNBO *res,
}
-/**
- * Convert amount from network to host representation.
- *
- * @param[out] res where to store amount in host representation
- * @param dn amount in network representation
- */
void
TALER_amount_ntoh (struct TALER_Amount *res,
const struct TALER_AmountNBO *dn)
@@ -247,14 +213,6 @@ TALER_amount_ntoh (struct TALER_Amount *res,
}
-/**
- * Get the value of "zero" in a particular currency.
- *
- * @param cur currency description
- * @param[out] amount amount to write the result to
- * @return #GNUNET_OK if @a cur is a valid currency specification,
- * #GNUNET_SYSERR if it is invalid.
- */
enum GNUNET_GenericReturnValue
TALER_amount_get_zero (const char *cur,
struct TALER_Amount *amount)
@@ -274,15 +232,11 @@ TALER_amount_get_zero (const char *cur,
}
-/**
- * Test if the given amount is valid.
- *
- * @param amount amount to check
- * @return #GNUNET_OK if @a amount is valid
- */
enum GNUNET_GenericReturnValue
TALER_amount_is_valid (const struct TALER_Amount *amount)
{
+ if (amount->value > TALER_AMOUNT_MAX_VALUE)
+ return GNUNET_SYSERR;
return ('\0' != amount->currency[0]) ? GNUNET_OK : GNUNET_NO;
}
@@ -301,15 +255,6 @@ test_valid_nbo (const struct TALER_AmountNBO *a)
}
-/**
- * Test if @a a1 and @a a2 are the same currency.
- *
- * @param a1 amount to test
- * @param a2 amount to test
- * @return #GNUNET_YES if @a a1 and @a a2 are the same currency
- * #GNUNET_NO if the currencies are different,
- * #GNUNET_SYSERR if either amount is invalid
- */
enum GNUNET_GenericReturnValue
TALER_amount_cmp_currency (const struct TALER_Amount *a1,
const struct TALER_Amount *a2)
@@ -324,15 +269,6 @@ TALER_amount_cmp_currency (const struct TALER_Amount *a1,
}
-/**
- * Test if @a a1 and @a a2 are the same currency, NBO variant.
- *
- * @param a1 amount to test
- * @param a2 amount to test
- * @return #GNUNET_YES if @a a1 and @a a2 are the same currency
- * #GNUNET_NO if the currencies are different,
- * #GNUNET_SYSERR if either amount is invalid
- */
enum GNUNET_GenericReturnValue
TALER_amount_cmp_currency_nbo (const struct TALER_AmountNBO *a1,
const struct TALER_AmountNBO *a2)
@@ -347,19 +283,6 @@ TALER_amount_cmp_currency_nbo (const struct TALER_AmountNBO *a1,
}
-/**
- * Compare the value/fraction of two amounts. Does not compare the currency.
- * Comparing amounts of different currencies will cause the program to abort().
- * If unsure, check with #TALER_amount_cmp_currency() first to be sure that
- * the currencies of the two amounts are identical.
- *
- * @param a1 first amount
- * @param a2 second amount
- * @return result of the comparison,
- * -1 if `a1 < a2`
- * 1 if `a1 > a2`
- * 0 if `a1 == a2`.
- */
int
TALER_amount_cmp (const struct TALER_Amount *a1,
const struct TALER_Amount *a2)
@@ -390,19 +313,6 @@ TALER_amount_cmp (const struct TALER_Amount *a1,
}
-/**
- * Compare the value/fraction of two amounts. Does not compare the currency.
- * Comparing amounts of different currencies will cause the program to abort().
- * If unsure, check with #TALER_amount_cmp_currency() first to be sure that
- * the currencies of the two amounts are identical. NBO variant.
- *
- * @param a1 first amount
- * @param a2 second amount
- * @return result of the comparison
- * -1 if `a1 < a2`
- * 1 if `a1 > a2`
- * 0 if `a1 == a2`.
- */
int
TALER_amount_cmp_nbo (const struct TALER_AmountNBO *a1,
const struct TALER_AmountNBO *a2)
@@ -419,14 +329,6 @@ TALER_amount_cmp_nbo (const struct TALER_AmountNBO *a1,
}
-/**
- * Perform saturating subtraction of amounts.
- *
- * @param[out] diff where to store (@a a1 - @a a2), or invalid if @a a2 > @a a1
- * @param a1 amount to subtract from
- * @param a2 amount to subtract
- * @return operation status, negative on failures
- */
enum TALER_AmountArithmeticResult
TALER_amount_subtract (struct TALER_Amount *diff,
const struct TALER_Amount *a1,
@@ -482,14 +384,6 @@ TALER_amount_subtract (struct TALER_Amount *diff,
}
-/**
- * Perform addition of amounts.
- *
- * @param[out] sum where to store @a a1 + @a a2, set to "invalid" on overflow
- * @param a1 first amount to add
- * @param a2 second amount to add
- * @return operation status, negative on failures
- */
enum TALER_AmountArithmeticResult
TALER_amount_add (struct TALER_Amount *sum,
const struct TALER_Amount *a1,
@@ -500,7 +394,8 @@ TALER_amount_add (struct TALER_Amount *sum,
struct TALER_Amount res;
if (GNUNET_YES !=
- TALER_amount_cmp_currency (a1, a2))
+ TALER_amount_cmp_currency (a1,
+ a2))
{
invalidate (sum);
return TALER_AAR_INVALID_CURRENCIES_INCOMPATIBLE;
@@ -509,8 +404,10 @@ TALER_amount_add (struct TALER_Amount *sum,
diff and a1/a2 */
n1 = *a1;
n2 = *a2;
- if ( (GNUNET_SYSERR == TALER_amount_normalize (&n1)) ||
- (GNUNET_SYSERR == TALER_amount_normalize (&n2)) )
+ if ( (GNUNET_SYSERR ==
+ TALER_amount_normalize (&n1)) ||
+ (GNUNET_SYSERR ==
+ TALER_amount_normalize (&n2)) )
{
invalidate (sum);
return TALER_AAR_INVALID_NORMALIZATION_FAILED;
@@ -526,7 +423,7 @@ TALER_amount_add (struct TALER_Amount *sum,
invalidate (sum);
return TALER_AAR_INVALID_RESULT_OVERFLOW;
}
- if (res.value > MAX_AMOUNT_VALUE)
+ if (res.value > TALER_AMOUNT_MAX_VALUE)
{
/* too large to be legal */
invalidate (sum);
@@ -548,14 +445,6 @@ TALER_amount_add (struct TALER_Amount *sum,
}
-/**
- * Normalize the given amount.
- *
- * @param[in,out] amount amount to normalize
- * @return #GNUNET_OK if normalization worked
- * #GNUNET_NO if value was already normalized
- * #GNUNET_SYSERR if value was invalid or could not be normalized
- */
enum GNUNET_GenericReturnValue
TALER_amount_normalize (struct TALER_Amount *amount)
{
@@ -569,7 +458,7 @@ TALER_amount_normalize (struct TALER_Amount *amount)
amount->fraction %= TALER_AMOUNT_FRAC_BASE;
amount->value += overflow;
if ( (amount->value < overflow) ||
- (amount->value > MAX_AMOUNT_VALUE) )
+ (amount->value > TALER_AMOUNT_MAX_VALUE) )
{
invalidate (amount);
return GNUNET_SYSERR;
@@ -600,12 +489,6 @@ amount_to_tail (const struct TALER_Amount *amount,
}
-/**
- * Convert amount to string.
- *
- * @param amount amount to convert to string
- * @return freshly allocated string representation
- */
char *
TALER_amount_to_string (const struct TALER_Amount *amount)
{
@@ -640,13 +523,6 @@ TALER_amount_to_string (const struct TALER_Amount *amount)
}
-/**
- * Convert amount to string.
- *
- * @param amount amount to convert to string
- * @return statically allocated buffer with string representation,
- * NULL if the @a amount was invalid
- */
const char *
TALER_amount2s (const struct TALER_Amount *amount)
{
@@ -685,14 +561,6 @@ TALER_amount2s (const struct TALER_Amount *amount)
}
-/**
- * Divide an amount by a @a divisor. Note that this function
- * may introduce a rounding error!
- *
- * @param[out] result where to store @a dividend / @a divisor
- * @param dividend amount to divide
- * @param divisor by what to divide, must be positive
- */
void
TALER_amount_divide (struct TALER_Amount *result,
const struct TALER_Amount *dividend,
@@ -718,20 +586,109 @@ TALER_amount_divide (struct TALER_Amount *result,
}
-/**
- * Round the amount to something that can be transferred on the wire.
- * The rounding mode is specified via the smallest transferable unit,
- * which must only have a fractional part *or* only a value (either
- * of the two must be zero!).
- *
- * If the @a round_unit given is zero, we do nothing and return #GNUNET_NO.
- *
- * @param[in,out] amount amount to round down
- * @param[in] round_unit unit that should be rounded down to, and
- * either value part or the faction must be zero
- * @return #GNUNET_OK on success, #GNUNET_NO if rounding was unnecessary,
- * #GNUNET_SYSERR if the amount or currency or @a round_unit was invalid
- */
+int
+TALER_amount_divide2 (const struct TALER_Amount *dividend,
+ const struct TALER_Amount *divisor)
+{
+ double approx;
+ double d;
+ double r;
+ int ret;
+ struct TALER_Amount tmp;
+ struct TALER_Amount nxt;
+
+ if (GNUNET_YES !=
+ TALER_amount_cmp_currency (dividend,
+ divisor))
+ {
+ GNUNET_break (0);
+ return -1;
+ }
+ if ( (0 == divisor->fraction) &&
+ (0 == divisor->value) )
+ return INT_MAX;
+ /* first, get rounded approximation */
+ d = ((double) dividend->value) * ((double) TALER_AMOUNT_FRAC_BASE)
+ + ( (double) dividend->fraction);
+ r = ((double) divisor->value) * ((double) TALER_AMOUNT_FRAC_BASE)
+ + ( (double) divisor->fraction);
+ approx = d / r;
+ if (approx > ((double) INT_MAX))
+ return INT_MAX; /* 'infinity' */
+ /* round down */
+ if (approx < 2)
+ ret = 0;
+ else
+ ret = (int) approx - 2;
+ /* Now do *exact* calculation, using well rounded-down factor as starting
+ point to avoid having to do too many steps. */
+ GNUNET_assert (0 <=
+ TALER_amount_multiply (&tmp,
+ divisor,
+ ret));
+ /* in practice, this loop will only run for one or two iterations */
+ while (1)
+ {
+ GNUNET_assert (0 <=
+ TALER_amount_add (&nxt,
+ &tmp,
+ divisor));
+ if (1 ==
+ TALER_amount_cmp (&nxt,
+ dividend))
+ break; /* nxt > dividend */
+ ret++;
+ tmp = nxt;
+ }
+ return ret;
+}
+
+
+enum TALER_AmountArithmeticResult
+TALER_amount_multiply (struct TALER_Amount *result,
+ const struct TALER_Amount *amount,
+ uint32_t factor)
+{
+ struct TALER_Amount in = *amount;
+
+ if (GNUNET_SYSERR ==
+ TALER_amount_normalize (&in))
+ return TALER_AAR_INVALID_NORMALIZATION_FAILED;
+ memcpy (result->currency,
+ amount->currency,
+ TALER_CURRENCY_LEN);
+ if ( (0 == factor) ||
+ ( (0 == in.value) &&
+ (0 == in.fraction) ) )
+ {
+ result->value = 0;
+ result->fraction = 0;
+ return TALER_AAR_RESULT_ZERO;
+ }
+ result->value = in.value * ((uint64_t) factor);
+ if (in.value != result->value / factor)
+ return TALER_AAR_INVALID_RESULT_OVERFLOW;
+ {
+ /* This multiplication cannot overflow since both inputs are 32-bit values */
+ uint64_t tmp = ((uint64_t) factor) * ((uint64_t) in.fraction);
+ uint64_t res;
+
+ res = tmp / TALER_AMOUNT_FRAC_BASE;
+ /* check for overflow */
+ if (result->value + res < result->value)
+ return TALER_AAR_INVALID_RESULT_OVERFLOW;
+ result->value += res;
+ result->fraction = tmp % TALER_AMOUNT_FRAC_BASE;
+ }
+ if (result->value > TALER_AMOUNT_MAX_VALUE)
+ return TALER_AAR_INVALID_RESULT_OVERFLOW;
+ /* This check should be redundant... */
+ GNUNET_assert (GNUNET_SYSERR !=
+ TALER_amount_normalize (result));
+ return TALER_AAR_RESULT_POSITIVE;
+}
+
+
enum GNUNET_GenericReturnValue
TALER_amount_round_down (struct TALER_Amount *amount,
const struct TALER_Amount *round_unit)
diff --git a/src/util/test_amount.c b/src/util/test_amount.c
index 8a83e4cf..1af383dc 100644
--- a/src/util/test_amount.c
+++ b/src/util/test_amount.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2015 Taler Systems SA
+ (C) 2015, 2021 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -186,21 +186,27 @@ main (int argc,
/* test addition with overflow */
a1.fraction = TALER_AMOUNT_FRAC_BASE - 1;
- a1.value = UINT64_MAX - 5;
+ a1.value = TALER_AMOUNT_MAX_VALUE - 5;
a2.fraction = 2;
a2.value = 5;
GNUNET_assert (TALER_AAR_INVALID_RESULT_OVERFLOW ==
- TALER_amount_add (&a3, &a1, &a2));
+ TALER_amount_add (&a3,
+ &a1,
+ &a2));
/* test addition with underflow on fraction */
a1.fraction = 1;
- a1.value = UINT64_MAX;
+ a1.value = TALER_AMOUNT_MAX_VALUE;
a2.fraction = 2;
a2.value = 0;
GNUNET_assert (TALER_AAR_RESULT_POSITIVE ==
- TALER_amount_subtract (&a3, &a1, &a2));
- GNUNET_assert (UINT64_MAX - 1 == a3.value);
- GNUNET_assert (TALER_AMOUNT_FRAC_BASE - 1 == a3.fraction);
+ TALER_amount_subtract (&a3,
+ &a1,
+ &a2));
+ GNUNET_assert (TALER_AMOUNT_MAX_VALUE - 1 ==
+ a3.value);
+ GNUNET_assert (TALER_AMOUNT_FRAC_BASE - 1 ==
+ a3.fraction);
/* test division */
GNUNET_assert (GNUNET_OK ==
@@ -288,6 +294,53 @@ main (int argc,
&r));
GNUNET_assert (0 == TALER_amount_cmp (&a1,
&a2));
+
+ /* test multiplication */
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("BTC:0",
+ &a1));
+ GNUNET_assert (TALER_AAR_RESULT_ZERO ==
+ TALER_amount_multiply (&a2,
+ &a1,
+ 42));
+ GNUNET_assert (0 == TALER_amount_cmp (&a1,
+ &a2));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("BTC:5.001",
+ &a1));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("BTC:5001",
+ &r));
+ GNUNET_assert (TALER_AAR_RESULT_POSITIVE ==
+ TALER_amount_multiply (&a2,
+ &a1,
+ 1000));
+ GNUNET_assert (0 == TALER_amount_cmp (&r,
+ &a2));
+ GNUNET_assert (1000 ==
+ TALER_amount_divide2 (&a2,
+ &a1));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("BTC:5006.00099999",
+ &r));
+ GNUNET_assert (1000 ==
+ TALER_amount_divide2 (&r,
+ &a1));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("BTC:5000.99999999",
+ &r));
+ GNUNET_assert (999 ==
+ TALER_amount_divide2 (&r,
+ &a1));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("BTC:0",
+ &a1));
+ GNUNET_assert (INT_MAX ==
+ TALER_amount_divide2 (&a2,
+ &a1));
+ GNUNET_assert (0 ==
+ TALER_amount_divide2 (&a1,
+ &a2));
return 0;
}