summaryrefslogtreecommitdiff
path: root/src/lib/exchange_api_stefan.c
blob: 226bca82fe448b3321d73027d77c6e6051d1ee3d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
/*
  This file is part of TALER
  Copyright (C) 2023 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
  TALER; see the file COPYING.  If not, see
  <http://www.gnu.org/licenses/>
*/
/**
 * @file lib/exchange_api_stefan.c
 * @brief calculations on the STEFAN curve
 * @author Christian Grothoff
 */
#include "platform.h"
#include "taler_json_lib.h"
#include <gnunet/gnunet_curl_lib.h>
#include "exchange_api_handle.h"
#include <math.h>


/**
 * Determine smallest denomination in @a keys.
 *
 * @param keys exchange response to evaluate
 * @return NULL on error (no denominations)
 */
static const struct TALER_Amount *
get_unit (const struct TALER_EXCHANGE_Keys *keys)
{
  const struct TALER_Amount *min = NULL;

  for (unsigned int i = 0; i<keys->num_denom_keys; i++)
  {
    const struct TALER_EXCHANGE_DenomPublicKey *dk
      = &keys->denom_keys[i];

    if ( (NULL == min) ||
         (1 == TALER_amount_cmp (min,
                                 /* > */
                                 &dk->value)) )
      min = &dk->value;
  }
  GNUNET_break (NULL != min);
  return min;
}


/**
 * Convert amount to double for STEFAN curve evaluation.
 *
 * @param a input amount
 * @return (rounded) amount as a double
 */
static double
amount_to_double (const struct TALER_Amount *a)
{
  double d = (double) a->value;

  d += a->fraction / ((double) TALER_AMOUNT_FRAC_BASE);
  return d;
}


/**
 * Convert double to amount for STEFAN curve evaluation.
 *
 * @param dv input amount
 * @param currency deisred currency
 * @param[out] rval (rounded) amount as a double
 */
static void
double_to_amount (double dv,
                  const char *currency,
                  struct TALER_Amount *rval)
{
  double rem;

  GNUNET_assert (GNUNET_OK ==
                 TALER_amount_set_zero (currency,
                                        rval));
  rval->value = floorl (dv);
  rem = dv - ((double) rval->value);
  if (rem < 0.0)
    rem = 0.0;
  rem *= TALER_AMOUNT_FRAC_BASE;
  rval->fraction = floorl (rem);
  if (rval->fraction >= TALER_AMOUNT_FRAC_BASE)
  {
    /* Strange, multiplication overflowed our range,
       round up value instead */
    rval->fraction = 0;
    rval->value += 1;
  }
}


enum GNUNET_GenericReturnValue
TALER_EXCHANGE_keys_stefan_b2n (
  const struct TALER_EXCHANGE_Keys *keys,
  const struct TALER_Amount *brut,
  struct TALER_Amount *net)
{
  const struct TALER_Amount *min;
  double log_d = amount_to_double (&keys->stefan_log);
  double lin_d = keys->stefan_lin;
  double abs_d = amount_to_double (&keys->stefan_abs);
  double bru_d = amount_to_double (brut);
  double min_d;
  double fee_d;
  double net_d;

  if (TALER_amount_is_zero (brut))
  {
    *net = *brut;
    return GNUNET_NO;
  }
  min = get_unit (keys);
  if (NULL == min)
    return GNUNET_SYSERR;
  if (1.0f <= keys->stefan_lin)
  {
    /* This cannot work, linear STEFAN fee estimate always
       exceed any gross amount. */
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }
  min_d = amount_to_double (min);
  fee_d = abs_d
          + log_d * log2 (bru_d / min_d)
          + lin_d * bru_d;
  if (fee_d > bru_d)
  {
    GNUNET_assert (GNUNET_OK ==
                   TALER_amount_set_zero (brut->currency,
                                          net));
    return GNUNET_NO;
  }
  net_d = bru_d - fee_d;
  double_to_amount (net_d,
                    brut->currency,
                    net);
  return GNUNET_OK;
}


/**
 * Our function
 * f(x) := ne + ab + lo * log2(x/mi) + li * x - x
 * for #newton().
 */
static double
eval_f (double mi,
        double ab,
        double lo,
        double li,
        double ne,
        double x)
{
  return ne + ab + lo * log2 (x / mi) + li * x - x;
}


/**
 * Our function
 * f'(x) := lo / log(2) / x + li - 1
 * for #newton().
 */
static double
eval_fp (double mi,
         double lo,
         double li,
         double ne,
         double x)
{
  return lo / log (2) / x + li - 1;
}


/**
 * Use Newton's method to find x where f(x)=0.
 *
 * @return x where "eval_f(x)==0".
 */
static double
newton (double mi,
        double ab,
        double lo,
        double li,
        double ne)
{
  const double eps = 0.00000001; /* max error allowed */
  double min_ab = ne + ab; /* result cannot be smaller than this! */
  /* compute lower bounds by various heuristics */
  double min_ab_li = min_ab + min_ab * li;
  double min_ab_li_lo = min_ab_li + log2 (min_ab_li / mi) * lo;
  double min_ab_lo = min_ab + log2 (min_ab / mi) * lo;
  double min_ab_lo_li = min_ab_lo + min_ab_lo * li;
  /* take global lower bound */
  double x_min = GNUNET_MAX (min_ab_lo_li,
                             min_ab_li_lo);
  double x = x_min; /* use lower bound as starting point */

  /* Objective: invert
     ne := br - ab - lo * log2 (br/mi) - li * br
     to find 'br'.
     Method: use Newton's method to find root of:
     f(x) := ne + ab + lo * log2 (x/mi) + li * x - x
     using also
     f'(x) := lo / log(2) / x  + li - 1
  */
  /* Loop to abort in case of divergence;
     100 is already very high, 2-4 is normal! */
  for (unsigned int i = 0; i<100; i++)
  {
    double fx = eval_f (mi, ab, lo, li, ne, x);
    double fxp = eval_fp (mi, lo, li, ne, x);
    double x_new = x - fx / fxp;

    if (fabs (x - x_new) <= eps)
    {
      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  "Needed %u rounds from %f to result BRUT %f => NET: %f\n",
                  i,
                  x_min,
                  x_new,
                  x_new - ab - li * x_new - lo * log2 (x / mi));
      return x_new;
    }
    if (x_new < x_min)
    {
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  "Divergence, obtained very bad estimate %f after %u rounds!\n",
                  x_new,
                  i);
      return x_min;
    }
    x = x_new;
  }
  GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
              "Slow convergence, returning bad estimate %f!\n",
              x);
  return x;
}


enum GNUNET_GenericReturnValue
TALER_EXCHANGE_keys_stefan_n2b (
  const struct TALER_EXCHANGE_Keys *keys,
  const struct TALER_Amount *net,
  struct TALER_Amount *brut)
{
  const struct TALER_Amount *min;
  double lin_d = keys->stefan_lin;
  double log_d = amount_to_double (&keys->stefan_log);
  double abs_d = amount_to_double (&keys->stefan_abs);
  double net_d = amount_to_double (net);
  double min_d;
  double brut_d;

  if (TALER_amount_is_zero (net))
  {
    *brut = *net;
    return GNUNET_NO;
  }
  min = get_unit (keys);
  if (NULL == min)
    return GNUNET_SYSERR;
  if (1.0f <= keys->stefan_lin)
  {
    /* This cannot work, linear STEFAN fee estimate always
       exceed any gross amount. */
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }
  min_d = amount_to_double (min);
  brut_d = newton (min_d,
                   abs_d,
                   log_d,
                   lin_d,
                   net_d);
  double_to_amount (brut_d,
                    net->currency,
                    brut);
  return GNUNET_OK;
}


void
TALER_EXCHANGE_keys_stefan_round (
  const struct TALER_EXCHANGE_Keys *keys,
  struct TALER_Amount *val)
{
  const struct TALER_Amount *min;
  uint32_t mod;
  uint32_t frac;
  uint32_t lim;

  if (0 == val->fraction)
  {
    /* rounding of non-fractions not supported */
    return;
  }
  min = get_unit (keys);
  if (NULL == min)
    return;
  if (0 == min->fraction)
  {
    frac = TALER_AMOUNT_FRAC_BASE;
  }
  else
  {
    frac = min->fraction;
  }
  lim = frac / 2;
  mod = val->fraction % frac;
  if (mod < lim)
    val->fraction -= mod; /* round down */
  else
    val->fraction += frac - mod; /* round up */
}