exchange/src/util/amount.c

705 lines
17 KiB
C
Raw Normal View History

/*
This file is part of TALER
2020-01-19 19:21:58 +01:00
Copyright (C) 2014 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
Foundation; either version 3, or (at your option) any later version.
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
2016-07-07 17:55:25 +02:00
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
* @file util/amount.c
* @brief Common utility functions to deal with units of currency
* @author Sree Harsha Totakura <sreeharsha@totakura.in>
* @author Florian Dold
* @author Benedikt Mueller
2015-04-13 17:20:46 +02:00
* @author Christian Grothoff
*/
#include "platform.h"
2015-07-15 11:40:51 +02:00
#if HAVE_GNUNET_GNUNET_UTIL_LIB_H
#include "taler_util.h"
2015-07-20 10:34:32 +02:00
#elif HAVE_GNUNET_GNUNET_UTIL_TALER_WALLET_LIB_H
2015-07-15 11:40:51 +02:00
#include "taler_util_wallet.h"
#endif
#include <gcrypt.h>
2017-11-02 17:40:14 +01:00
/**
* Maximum legal 'value' for an amount, based on IEEE double (for JavaScript compatibility).
*/
2017-11-02 18:42:12 +01:00
#define MAX_AMOUNT_VALUE (1LLU << 52)
2017-11-02 17:40:14 +01:00
/**
* Set @a a to "invalid".
*
* @param a amount to set to invalid
*/
static void
invalidate (struct TALER_Amount *a)
{
memset (a,
0,
sizeof (struct TALER_Amount));
}
/**
* Parse money amount description, in the format "A:B.C".
*
* @param str amount description
* @param denom amount to write the result to
* @return #GNUNET_OK if the string is a valid amount specification,
* #GNUNET_SYSERR if it is invalid.
*/
int
TALER_string_to_amount (const char *str,
struct TALER_Amount *denom)
{
size_t i;
int n;
uint32_t b;
const char *colon;
const char *value;
2017-11-02 17:40:14 +01:00
invalidate (denom);
/* skip leading whitespace */
2019-08-25 16:18:24 +02:00
while (isspace ( (unsigned char) str[0]))
str++;
if ('\0' == str[0])
{
2015-03-17 11:37:21 +01:00
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Null before currency\n");
return GNUNET_SYSERR;
}
/* parse currency */
colon = strchr (str, (int) ':');
if ( (NULL == colon) ||
((colon - str) >= TALER_CURRENCY_LEN) )
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
2019-02-11 19:55:00 +01:00
"Invalid currency specified before colon: `%s'\n",
str);
goto fail;
}
memcpy (denom->currency,
str,
colon - str);
/* skip colon */
value = colon + 1;
if ('\0' == value[0])
{
2015-03-17 11:37:21 +01:00
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Null before value\n");
goto fail;
}
/* parse value */
i = 0;
while ('.' != value[i])
{
if ('\0' == value[i])
{
return GNUNET_OK;
}
2015-04-13 17:20:46 +02:00
if ( (value[i] < '0') || (value[i] > '9') )
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Invalid character `%c'\n",
2015-04-13 17:20:46 +02:00
value[i]);
goto fail;
}
2015-04-13 17:20:46 +02:00
n = value[i] - '0';
if (denom->value * 10 + n < denom->value)
{
2015-03-17 11:37:21 +01:00
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Value too large\n");
goto fail;
}
denom->value = (denom->value * 10) + n;
i++;
}
/* skip the dot */
i++;
/* parse fraction */
2015-04-13 17:20:46 +02:00
if ('\0' == value[i])
{
2015-03-17 11:37:21 +01:00
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
2015-04-13 17:20:46 +02:00
"Null after dot\n");
goto fail;
}
b = TALER_AMOUNT_FRAC_BASE / 10;
2015-04-13 17:20:46 +02:00
while ('\0' != value[i])
{
if (0 == b)
{
2015-03-17 11:37:21 +01:00
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
2015-04-13 17:20:46 +02:00
"Fractional value too small (only %u digits supported)\n",
(unsigned int) TALER_AMOUNT_FRAC_LEN);
goto fail;
}
2015-04-13 17:20:46 +02:00
if ( (value[i] < '0') || (value[i] > '9') )
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
2015-04-13 17:20:46 +02:00
"Error after dot\n");
goto fail;
}
2015-04-13 17:20:46 +02:00
n = value[i] - '0';
denom->fraction += n * b;
b /= 10;
i++;
}
2017-11-02 17:40:14 +01:00
if (denom->value > MAX_AMOUNT_VALUE)
{
/* too large to be legal */
invalidate (denom);
return GNUNET_SYSERR;
}
return GNUNET_OK;
2019-10-05 15:42:08 +02:00
fail:
/* set currency to 'invalid' to prevent accidental use */
memset (denom->currency,
0,
TALER_CURRENCY_LEN);
return GNUNET_SYSERR;
}
/**
* Parse denomination description, in the format "T:V.F".
*
* @param str denomination description
* @param denom denomination to write the result to, in NBO
* @return #GNUNET_OK if the string is a valid denomination specification,
* #GNUNET_SYSERR if it is invalid.
*/
int
TALER_string_to_amount_nbo (const char *str,
struct TALER_AmountNBO *denom)
{
struct TALER_Amount amount;
if (GNUNET_OK !=
TALER_string_to_amount (str,
&amount))
return GNUNET_SYSERR;
TALER_amount_hton (denom,
&amount);
return GNUNET_OK;
}
/**
* Convert amount from host to network representation.
*
* @param res where to store amount in network representation
* @param d amount in host representation
*/
void
TALER_amount_hton (struct TALER_AmountNBO *res,
const struct TALER_Amount *d)
{
GNUNET_assert (GNUNET_YES ==
2019-08-25 16:18:24 +02:00
TALER_amount_is_valid (d));
res->value = GNUNET_htonll (d->value);
res->fraction = htonl (d->fraction);
memcpy (res->currency,
d->currency,
TALER_CURRENCY_LEN);
}
/**
* Convert amount from network to host representation.
*
* @param res where to store amount in host representation
2015-03-28 15:42:07 +01:00
* @param dn amount in network representation
*/
void
TALER_amount_ntoh (struct TALER_Amount *res,
const struct TALER_AmountNBO *dn)
{
res->value = GNUNET_ntohll (dn->value);
res->fraction = ntohl (dn->fraction);
memcpy (res->currency,
dn->currency,
TALER_CURRENCY_LEN);
GNUNET_assert (GNUNET_YES ==
2019-08-25 16:18:24 +02:00
TALER_amount_is_valid (res));
}
/**
* Get the value of "zero" in a particular currency.
*
* @param cur currency description
* @param denom denomination to write the result to
* @return #GNUNET_OK if @a cur is a valid currency specification,
* #GNUNET_SYSERR if it is invalid.
*/
int
TALER_amount_get_zero (const char *cur,
struct TALER_Amount *denom)
{
size_t slen;
slen = strlen (cur);
if (slen >= TALER_CURRENCY_LEN)
return GNUNET_SYSERR;
memset (denom,
0,
sizeof (struct TALER_Amount));
memcpy (denom->currency,
cur,
slen);
return GNUNET_OK;
}
/**
* Test if the given amount is valid.
*
* @param amount amount to check
* @return #GNUNET_OK if @a amount is valid
*/
int
TALER_amount_is_valid (const struct TALER_Amount *amount)
{
return ('\0' != amount->currency[0]);
}
/**
* Test if @a a is valid, NBO variant.
*
* @param a amount to test
* @return #GNUNET_YES if valid,
* #GNUNET_NO if invalid
*/
static int
test_valid_nbo (const struct TALER_AmountNBO *a)
{
return ('\0' != a->currency[0]);
}
/**
* 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
*/
int
TALER_amount_cmp_currency (const struct TALER_Amount *a1,
const struct TALER_Amount *a2)
{
if ( (GNUNET_NO == TALER_amount_is_valid (a1)) ||
(GNUNET_NO == TALER_amount_is_valid (a2)) )
return GNUNET_SYSERR;
2015-04-13 17:20:46 +02:00
if (0 == strcasecmp (a1->currency,
2019-08-25 16:18:24 +02:00
a2->currency))
return GNUNET_YES;
return GNUNET_NO;
}
/**
* 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
*/
int
TALER_amount_cmp_currency_nbo (const struct TALER_AmountNBO *a1,
const struct TALER_AmountNBO *a2)
{
if ( (GNUNET_NO == test_valid_nbo (a1)) ||
(GNUNET_NO == test_valid_nbo (a2)) )
return GNUNET_SYSERR;
if (0 == strcasecmp (a1->currency,
2019-08-25 16:18:24 +02:00
a2->currency))
return GNUNET_YES;
return GNUNET_NO;
}
/**
* 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
2015-07-05 13:18:49 +02:00
* @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)
{
struct TALER_Amount n1;
struct TALER_Amount n2;
GNUNET_assert (GNUNET_YES ==
TALER_amount_cmp_currency (a1, a2));
n1 = *a1;
n2 = *a2;
2015-04-13 17:20:46 +02:00
GNUNET_assert (GNUNET_SYSERR !=
2019-08-25 16:18:24 +02:00
TALER_amount_normalize (&n1));
2015-04-13 17:20:46 +02:00
GNUNET_assert (GNUNET_SYSERR !=
2019-08-25 16:18:24 +02:00
TALER_amount_normalize (&n2));
if (n1.value == n2.value)
{
if (n1.fraction < n2.fraction)
return -1;
if (n1.fraction > n2.fraction)
return 1;
return 0;
}
if (n1.value < n2.value)
return -1;
return 1;
}
/**
* Perform saturating subtraction of amounts.
*
* @param 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 #GNUNET_OK if the subtraction worked,
* #GNUNET_NO if @a a1 = @a a2
* #GNUNET_SYSERR if @a a2 > @a a1 or currencies are incompatible;
* @a diff is set to invalid
*/
int
TALER_amount_subtract (struct TALER_Amount *diff,
const struct TALER_Amount *a1,
const struct TALER_Amount *a2)
{
struct TALER_Amount n1;
struct TALER_Amount n2;
if (GNUNET_YES !=
TALER_amount_cmp_currency (a1, a2))
{
invalidate (diff);
return GNUNET_SYSERR;
}
n1 = *a1;
n2 = *a2;
2015-04-13 17:20:46 +02:00
if ( (GNUNET_SYSERR == TALER_amount_normalize (&n1)) ||
(GNUNET_SYSERR == TALER_amount_normalize (&n2)) )
{
invalidate (diff);
return GNUNET_SYSERR;
}
if (n1.fraction < n2.fraction)
{
if (0 == n1.value)
{
invalidate (diff);
return GNUNET_SYSERR;
}
n1.fraction += TALER_AMOUNT_FRAC_BASE;
n1.value--;
}
if (n1.value < n2.value)
{
invalidate (diff);
return GNUNET_SYSERR;
}
GNUNET_assert (GNUNET_OK ==
TALER_amount_get_zero (n1.currency,
diff));
GNUNET_assert (n1.fraction >= n2.fraction);
diff->fraction = n1.fraction - n2.fraction;
GNUNET_assert (n1.value >= n2.value);
diff->value = n1.value - n2.value;
if ( (0 == diff->fraction) &&
(0 == diff->value) )
return GNUNET_NO;
return GNUNET_OK;
}
/**
* Perform addition of amounts.
*
* @param 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 #GNUNET_OK if the addition worked,
* #GNUNET_SYSERR on overflow
*/
int
TALER_amount_add (struct TALER_Amount *sum,
const struct TALER_Amount *a1,
const struct TALER_Amount *a2)
{
struct TALER_Amount n1;
struct TALER_Amount n2;
2015-04-13 17:20:46 +02:00
struct TALER_Amount res;
if (GNUNET_YES !=
TALER_amount_cmp_currency (a1, a2))
{
invalidate (sum);
return GNUNET_SYSERR;
}
n1 = *a1;
n2 = *a2;
2015-04-13 17:20:46 +02:00
if ( (GNUNET_SYSERR == TALER_amount_normalize (&n1)) ||
(GNUNET_SYSERR == TALER_amount_normalize (&n2)) )
{
invalidate (sum);
return GNUNET_SYSERR;
}
GNUNET_assert (GNUNET_OK ==
TALER_amount_get_zero (a1->currency,
2015-04-13 17:20:46 +02:00
&res));
res.value = n1.value + n2.value;
if (res.value < n1.value)
{
/* integer overflow */
invalidate (sum);
return GNUNET_SYSERR;
}
2017-11-02 17:40:14 +01:00
if (res.value > MAX_AMOUNT_VALUE)
{
/* too large to be legal */
invalidate (sum);
return GNUNET_SYSERR;
}
2015-04-13 17:20:46 +02:00
res.fraction = n1.fraction + n2.fraction;
if (GNUNET_SYSERR ==
2015-04-13 17:20:46 +02:00
TALER_amount_normalize (&res))
{
/* integer overflow via carry from fraction */
invalidate (sum);
return GNUNET_SYSERR;
}
2015-04-13 17:20:46 +02:00
*sum = res;
return GNUNET_OK;
}
/**
* Normalize the given amount.
*
* @param 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
*/
int
TALER_amount_normalize (struct TALER_Amount *amount)
{
int ret;
if (GNUNET_YES != TALER_amount_is_valid (amount))
return GNUNET_SYSERR;
ret = GNUNET_NO;
while ( (amount->value != UINT64_MAX) &&
(amount->fraction >= TALER_AMOUNT_FRAC_BASE) )
{
amount->fraction -= TALER_AMOUNT_FRAC_BASE;
amount->value++;
ret = GNUNET_OK;
}
if (amount->fraction >= TALER_AMOUNT_FRAC_BASE)
{
/* failed to normalize, adding up fractions caused
main value to overflow! */
2015-04-13 17:20:46 +02:00
invalidate (amount);
return GNUNET_SYSERR;
}
return ret;
}
2019-06-18 13:20:43 +02:00
/**
2020-01-18 13:23:10 +01:00
* Convert the fraction of @a amount to a string in decimals.
2019-06-18 13:20:43 +02:00
*
* @param amount value to convert
2020-01-18 13:23:10 +01:00
* @param[out] tail where to write the reesult
2019-06-18 13:20:43 +02:00
*/
static void
amount_to_tail (const struct TALER_Amount *amount,
char tail[TALER_AMOUNT_FRAC_LEN + 1])
{
uint32_t n = amount->fraction;
2020-01-18 14:01:56 +01:00
unsigned int i;
2019-06-18 13:20:43 +02:00
2020-01-18 14:01:56 +01:00
for (i = 0; (i < TALER_AMOUNT_FRAC_LEN) && (0 != n); i++)
2019-06-18 13:20:43 +02:00
{
tail[i] = '0' + (n / (TALER_AMOUNT_FRAC_BASE / 10));
n = (n * 10) % (TALER_AMOUNT_FRAC_BASE);
}
tail[i] = '\0';
}
/**
* 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)
{
char *result;
struct TALER_Amount norm;
if (GNUNET_YES != TALER_amount_is_valid (amount))
return NULL;
norm = *amount;
GNUNET_break (GNUNET_SYSERR !=
TALER_amount_normalize (&norm));
2019-06-18 13:20:43 +02:00
if (0 != norm.fraction)
{
2019-06-18 13:20:43 +02:00
char tail[TALER_AMOUNT_FRAC_LEN + 1];
amount_to_tail (&norm,
tail);
GNUNET_asprintf (&result,
"%s:%llu.%s",
norm.currency,
(unsigned long long) norm.value,
tail);
}
else
{
GNUNET_asprintf (&result,
"%s:%llu",
norm.currency,
(unsigned long long) norm.value);
}
return result;
}
2017-03-20 02:29:33 +01:00
/**
* 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)
{
static char result[TALER_AMOUNT_FRAC_LEN + TALER_CURRENCY_LEN + 3 + 12];
struct TALER_Amount norm;
if (GNUNET_YES != TALER_amount_is_valid (amount))
return NULL;
norm = *amount;
GNUNET_break (GNUNET_SYSERR !=
TALER_amount_normalize (&norm));
2019-06-18 13:20:43 +02:00
if (0 != norm.fraction)
2017-03-20 02:29:33 +01:00
{
2019-06-18 13:20:43 +02:00
char tail[TALER_AMOUNT_FRAC_LEN + 1];
amount_to_tail (&norm,
tail);
2017-03-20 02:29:33 +01:00
GNUNET_snprintf (result,
sizeof (result),
"%s:%llu.%s",
norm.currency,
(unsigned long long) norm.value,
tail);
}
else
{
GNUNET_snprintf (result,
sizeof (result),
"%s:%llu",
norm.currency,
(unsigned long long) norm.value);
}
return result;
}
2017-03-06 16:35:30 +01:00
/**
* Divide an amount by a float. Note that this function
* may introduce a rounding error!
*
* @param 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,
uint32_t divisor)
{
uint64_t modr;
GNUNET_assert (0 != divisor);
*result = *dividend;
if (1 == divisor)
return;
modr = result->value % divisor;
result->value /= divisor;
/* modr is a 32-bit value, so we can safely multiply by (<32-bit) base and add fraction! */
modr = (modr * TALER_AMOUNT_FRAC_BASE) + result->fraction;
GNUNET_assert (modr < TALER_AMOUNT_FRAC_BASE * divisor);
result->fraction = (uint32_t) (modr / divisor);
}
2020-01-13 18:16:01 +01:00
/**
* Round the amount to something that can be transferred on the wire.
2020-01-17 18:59:15 +01:00
* The rounding mode is specified via the smallest transferable unit,
* which must only have a fractional part.
2020-01-13 18:16:01 +01:00
*
* @param[in,out] amount amount to round down
2020-01-17 18:59:15 +01:00
* @param[in] round_unit unit that should be rounded down to,
* the value part of this amount must be zero
2020-01-13 18:16:01 +01:00
* @return #GNUNET_OK on success, #GNUNET_NO if rounding was unnecessary,
* #GNUNET_SYSERR if the amount or currency was invalid
*/
int
TALER_amount_round_down (struct TALER_Amount *amount,
2020-01-17 18:59:15 +01:00
const struct TALER_Amount *round_unit)
2020-01-13 18:16:01 +01:00
{
uint32_t delta;
2020-01-17 18:59:15 +01:00
GNUNET_break (0 == round_unit->value);
2020-01-17 18:59:15 +01:00
if (0 == round_unit->fraction)
return GNUNET_OK;
delta = amount->fraction % round_unit->fraction;
2020-01-13 18:16:01 +01:00
if (0 == delta)
return GNUNET_NO;
amount->fraction -= delta;
return GNUNET_OK;
}
/* end of amount.c */