gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-exchange] branch master updated: expose 2^52 amount value limit i


From: gnunet
Subject: [taler-exchange] branch master updated: expose 2^52 amount value limit in header, check that limit in test cases, add TALER_amount_multiply and TALER_amount_divide2 operations
Date: Sat, 10 Jul 2021 14:55:39 +0200

This is an automated email from the git hooks/post-receive script.

grothoff pushed a commit to branch master
in repository exchange.

The following commit(s) were added to refs/heads/master by this push:
     new 249ba03c expose 2^52 amount value limit in header, check that limit in 
test cases, add TALER_amount_multiply and TALER_amount_divide2 operations
249ba03c is described below

commit 249ba03c36d6bc61b78bdd5a7f1ca55701f1c287
Author: Christian Grothoff <christian@grothoff.org>
AuthorDate: Sat Jul 10 14:52:59 2021 +0200

    expose 2^52 amount value limit in header, check that limit in test cases, 
add TALER_amount_multiply and TALER_amount_divide2 operations
---
 debian/changelog               |   6 +
 src/include/taler_amount_lib.h |  31 +++++
 src/util/amount.c              | 275 +++++++++++++++++------------------------
 src/util/test_amount.c         |  67 ++++++++--
 4 files changed, 213 insertions(+), 166 deletions(-)

diff --git a/debian/changelog b/debian/changelog
index 74922b01..b827c937 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+taler-exchange (0.9.0-12) unstable; urgency=low
+
+  * Fix typo in taler-auditor shell script: clean before building.
+
+ -- Christian Grothoff <grothoff@gnu.org>  Mon, 28 Jun 2021 14:02:10 +0100
+
 taler-exchange (0.9.0-11) unstable; urgency=low
 
   * Fix typo in taler-auditor-sync.
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;
 }
 

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]