LCOV - code coverage report
Current view: top level - lib - exchange_api_stefan.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 88.2 % 110 97
Test Date: 2026-04-14 15:39:31 Functions: 100.0 % 9 9

            Line data    Source code
       1              : /*
       2              :   This file is part of TALER
       3              :   Copyright (C) 2023 Taler Systems SA
       4              : 
       5              :   TALER is free software; you can redistribute it and/or modify it under the
       6              :   terms of the GNU General Public License as published by the Free Software
       7              :   Foundation; either version 3, or (at your option) any later version.
       8              : 
       9              :   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
      10              :   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
      11              :   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
      12              : 
      13              :   You should have received a copy of the GNU General Public License along with
      14              :   TALER; see the file COPYING.  If not, see
      15              :   <http://www.gnu.org/licenses/>
      16              : */
      17              : /**
      18              :  * @file lib/exchange_api_stefan.c
      19              :  * @brief calculations on the STEFAN curve
      20              :  * @author Christian Grothoff
      21              :  */
      22              : #include "taler/taler_json_lib.h"
      23              : #include <gnunet/gnunet_curl_lib.h>
      24              : #include "exchange_api_handle.h"
      25              : #include <math.h>
      26              : 
      27              : 
      28              : /**
      29              :  * Determine smallest denomination in @a keys.
      30              :  *
      31              :  * @param keys exchange response to evaluate
      32              :  * @return NULL on error (no denominations)
      33              :  */
      34              : static const struct TALER_Amount *
      35        54460 : get_unit (const struct TALER_EXCHANGE_Keys *keys)
      36              : {
      37        54460 :   const struct TALER_Amount *min = NULL;
      38              : 
      39       108920 :   for (unsigned int i = 0; i<keys->num_denom_keys; i++)
      40              :   {
      41        54460 :     const struct TALER_EXCHANGE_DenomPublicKey *dk
      42        54460 :       = &keys->denom_keys[i];
      43              : 
      44        54460 :     if ( (NULL == min) ||
      45            0 :          (1 == TALER_amount_cmp (min,
      46              :                                  /* > */
      47              :                                  &dk->value)) )
      48        54460 :       min = &dk->value;
      49              :   }
      50        54460 :   GNUNET_break (NULL != min);
      51        54460 :   return min;
      52              : }
      53              : 
      54              : 
      55              : /**
      56              :  * Convert amount to double for STEFAN curve evaluation.
      57              :  *
      58              :  * @param a input amount
      59              :  * @return (rounded) amount as a double
      60              :  */
      61              : static double
      62       180492 : amount_to_double (const struct TALER_Amount *a)
      63              : {
      64       180492 :   double d = (double) a->value;
      65              : 
      66       180492 :   d += a->fraction / ((double) TALER_AMOUNT_FRAC_BASE);
      67       180492 :   return d;
      68              : }
      69              : 
      70              : 
      71              : /**
      72              :  * Convert double to amount for STEFAN curve evaluation.
      73              :  *
      74              :  * @param dv input amount
      75              :  * @param currency deisred currency
      76              :  * @param[out] rval (rounded) amount as a double
      77              :  */
      78              : static void
      79        33072 : double_to_amount (double dv,
      80              :                   const char *currency,
      81              :                   struct TALER_Amount *rval)
      82              : {
      83              :   double rem;
      84              : 
      85        33072 :   GNUNET_assert (GNUNET_OK ==
      86              :                  TALER_amount_set_zero (currency,
      87              :                                         rval));
      88        33072 :   rval->value = floorl (dv);
      89        33072 :   rem = dv - ((double) rval->value);
      90        33072 :   if (rem < 0.0)
      91            0 :     rem = 0.0;
      92        33072 :   rem *= TALER_AMOUNT_FRAC_BASE;
      93        33072 :   rval->fraction = floorl (rem);
      94        33072 :   if (rval->fraction >= TALER_AMOUNT_FRAC_BASE)
      95              :   {
      96              :     /* Strange, multiplication overflowed our range,
      97              :        round up value instead */
      98            0 :     rval->fraction = 0;
      99            0 :     rval->value += 1;
     100              :   }
     101        33072 : }
     102              : 
     103              : 
     104              : enum GNUNET_GenericReturnValue
     105        23762 : TALER_EXCHANGE_keys_stefan_b2n (
     106              :   const struct TALER_EXCHANGE_Keys *keys,
     107              :   const struct TALER_Amount *brut,
     108              :   struct TALER_Amount *net)
     109              : {
     110              :   const struct TALER_Amount *min;
     111        23762 :   double log_d = amount_to_double (&keys->stefan_log);
     112        23762 :   double lin_d = keys->stefan_lin;
     113        23762 :   double abs_d = amount_to_double (&keys->stefan_abs);
     114        23762 :   double bru_d = amount_to_double (brut);
     115              :   double min_d;
     116              :   double fee_d;
     117              :   double net_d;
     118              : 
     119        23762 :   if (TALER_amount_is_zero (brut))
     120              :   {
     121         2376 :     *net = *brut;
     122         2376 :     return GNUNET_NO;
     123              :   }
     124        21386 :   min = get_unit (keys);
     125        21386 :   if (NULL == min)
     126            0 :     return GNUNET_SYSERR;
     127        21386 :   if (1.0 <= keys->stefan_lin)
     128              :   {
     129              :     /* This cannot work, linear STEFAN fee estimate always
     130              :        exceed any gross amount. */
     131            2 :     GNUNET_break_op (0);
     132            2 :     return GNUNET_SYSERR;
     133              :   }
     134        21384 :   min_d = amount_to_double (min);
     135        21384 :   fee_d = abs_d
     136        21384 :           + log_d * log2 (bru_d / min_d)
     137        21384 :           + lin_d * bru_d;
     138        21384 :   if (fee_d > bru_d)
     139              :   {
     140         4848 :     GNUNET_assert (GNUNET_OK ==
     141              :                    TALER_amount_set_zero (brut->currency,
     142              :                                           net));
     143         4848 :     return GNUNET_NO;
     144              :   }
     145        16536 :   net_d = bru_d - fee_d;
     146        16536 :   double_to_amount (net_d,
     147        16536 :                     brut->currency,
     148              :                     net);
     149        16536 :   return GNUNET_OK;
     150              : }
     151              : 
     152              : 
     153              : /**
     154              :  * Our function
     155              :  * f(x) := ne + ab + lo * log2(x/mi) + li * x - x
     156              :  * for #newton().
     157              :  */
     158              : static double
     159        50076 : eval_f (double mi,
     160              :         double ab,
     161              :         double lo,
     162              :         double li,
     163              :         double ne,
     164              :         double x)
     165              : {
     166        50076 :   return ne + ab + lo * log2 (x / mi) + li * x - x;
     167              : }
     168              : 
     169              : 
     170              : /**
     171              :  * Our function
     172              :  * f'(x) := lo / log(2) / x + li - 1
     173              :  * for #newton().
     174              :  */
     175              : static double
     176        50076 : eval_fp (double mi,
     177              :          double lo,
     178              :          double li,
     179              :          double ne,
     180              :          double x)
     181              : {
     182        50076 :   return lo / log (2) / x + li - 1;
     183              : }
     184              : 
     185              : 
     186              : /**
     187              :  * Use Newton's method to find x where f(x)=0.
     188              :  *
     189              :  * @return x where "eval_f(x)==0".
     190              :  */
     191              : static double
     192        16536 : newton (double mi,
     193              :         double ab,
     194              :         double lo,
     195              :         double li,
     196              :         double ne)
     197              : {
     198        16536 :   const double eps = 0.00000001; /* max error allowed */
     199        16536 :   double min_ab = ne + ab; /* result cannot be smaller than this! */
     200              :   /* compute lower bounds by various heuristics */
     201        16536 :   double min_ab_li = min_ab + min_ab * li;
     202        16536 :   double min_ab_li_lo = min_ab_li + log2 (min_ab_li / mi) * lo;
     203        16536 :   double min_ab_lo = min_ab + log2 (min_ab / mi) * lo;
     204        16536 :   double min_ab_lo_li = min_ab_lo + min_ab_lo * li;
     205              :   /* take global lower bound */
     206        16536 :   double x_min = GNUNET_MAX (min_ab_lo_li,
     207              :                              min_ab_li_lo);
     208        16536 :   double x = x_min; /* use lower bound as starting point */
     209              : 
     210              :   /* Objective: invert
     211              :      ne := br - ab - lo * log2 (br/mi) - li * br
     212              :      to find 'br'.
     213              :      Method: use Newton's method to find root of:
     214              :      f(x) := ne + ab + lo * log2 (x/mi) + li * x - x
     215              :      using also
     216              :      f'(x) := lo / log(2) / x  + li - 1
     217              :   */
     218              :   /* Loop to abort in case of divergence;
     219              :      100 is already very high, 2-4 is normal! */
     220        50076 :   for (unsigned int i = 0; i<100; i++)
     221              :   {
     222        50076 :     double fx = eval_f (mi, ab, lo, li, ne, x);
     223        50076 :     double fxp = eval_fp (mi, lo, li, ne, x);
     224        50076 :     double x_new = x - fx / fxp;
     225              : 
     226        50076 :     if (fabs (x - x_new) <= eps)
     227              :     {
     228        16536 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     229              :                   "Needed %u rounds from %f to result BRUT %f => NET: %f\n",
     230              :                   i,
     231              :                   x_min,
     232              :                   x_new,
     233              :                   x_new - ab - li * x_new - lo * log2 (x / mi));
     234        16536 :       return x_new;
     235              :     }
     236        33540 :     if (x_new < x_min)
     237              :     {
     238            0 :       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     239              :                   "Divergence, obtained very bad estimate %f after %u rounds!\n",
     240              :                   x_new,
     241              :                   i);
     242            0 :       return x_min;
     243              :     }
     244        33540 :     x = x_new;
     245              :   }
     246            0 :   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     247              :               "Slow convergence, returning bad estimate %f!\n",
     248              :               x);
     249            0 :   return x;
     250              : }
     251              : 
     252              : 
     253              : enum GNUNET_GenericReturnValue
     254        23762 : TALER_EXCHANGE_keys_stefan_n2b (
     255              :   const struct TALER_EXCHANGE_Keys *keys,
     256              :   const struct TALER_Amount *net,
     257              :   struct TALER_Amount *brut)
     258              : {
     259              :   const struct TALER_Amount *min;
     260        23762 :   double lin_d = keys->stefan_lin;
     261        23762 :   double log_d = amount_to_double (&keys->stefan_log);
     262        23762 :   double abs_d = amount_to_double (&keys->stefan_abs);
     263        23762 :   double net_d = amount_to_double (net);
     264              :   double min_d;
     265              :   double brut_d;
     266              : 
     267        23762 :   if (TALER_amount_is_zero (net))
     268              :   {
     269         7224 :     *brut = *net;
     270         7224 :     return GNUNET_NO;
     271              :   }
     272        16538 :   min = get_unit (keys);
     273        16538 :   if (NULL == min)
     274            0 :     return GNUNET_SYSERR;
     275        16538 :   if (1.0 <= keys->stefan_lin)
     276              :   {
     277              :     /* This cannot work, linear STEFAN fee estimate always
     278              :        exceed any gross amount. */
     279            2 :     GNUNET_break_op (0);
     280            2 :     return GNUNET_SYSERR;
     281              :   }
     282        16536 :   min_d = amount_to_double (min);
     283        16536 :   brut_d = newton (min_d,
     284              :                    abs_d,
     285              :                    log_d,
     286              :                    lin_d,
     287              :                    net_d);
     288        16536 :   double_to_amount (brut_d,
     289        16536 :                     net->currency,
     290              :                     brut);
     291        16536 :   return GNUNET_OK;
     292              : }
     293              : 
     294              : 
     295              : void
     296        17724 : TALER_EXCHANGE_keys_stefan_round (
     297              :   const struct TALER_EXCHANGE_Keys *keys,
     298              :   struct TALER_Amount *val)
     299              : {
     300              :   const struct TALER_Amount *min;
     301              :   uint32_t mod;
     302              :   uint32_t frac;
     303              :   uint32_t lim;
     304              : 
     305        17724 :   if (0 == val->fraction)
     306              :   {
     307              :     /* rounding of non-fractions not supported */
     308         1188 :     return;
     309              :   }
     310        16536 :   min = get_unit (keys);
     311        16536 :   if (NULL == min)
     312            0 :     return;
     313        16536 :   if (0 == min->fraction)
     314              :   {
     315            0 :     frac = TALER_AMOUNT_FRAC_BASE;
     316              :   }
     317              :   else
     318              :   {
     319        16536 :     frac = min->fraction;
     320              :   }
     321        16536 :   lim = frac / 2;
     322        16536 :   mod = val->fraction % frac;
     323        16536 :   if (mod < lim)
     324            0 :     val->fraction -= mod; /* round down */
     325              :   else
     326        16536 :     val->fraction += frac - mod; /* round up */
     327              : }
        

Generated by: LCOV version 2.0-1