summaryrefslogtreecommitdiff
path: root/design-documents/051-fractional-digits.rst
blob: 31e2959d310b4a91319b5f0c292fa9d817c15089 (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
DD 51: Fractional Digits
#########################

Summary
=======

This design document specifies how an amount's fractional digits should be rendered.
Note that UIs that cannot render amounts as specified (e.g. because the display does
not support super script digits) may ignore the rendering advice provided by the
protocol under this DD.


Motivation
==========

Since different currencies have different ways to show/render fractionals, the
end-user apps should follow these guidelines.

Requirements
============

There is already a specification for ScopedCurrencyInfo - this needs to change

We need three core characteristics for fractional digits for each currency:

e) the number of fractional digits e in [0..8] the user may 'e'nter in a TextInputField

n) the number of fractional digits n in [0..8] to be rendered as 'n'ormal characters (same font and size as the integer digits).
   All additional fractional digits will be rendered as SuperScriptDigits as known from gas filling stations.
   The UI should never round or truncate any amount, but always render all existing digits (except trailing zeroes, see c).

z) the number of fractional digits z in [0..8] to be rendered as trailing 'z'eroes (including SuperScript digits).
   E.g. if z = 2 (and n = 2), then render $5 as “$ 5.00”.
   If z = 3 (and n = 2), then render $5 as “$ 5.00⁰” with two normal trailing zeroes and one superscript trailing zero.

The values e, n, and z are independent from each other. Each could be any value
from 0 to 8. However, when a user enters an amount, s/he should be able to input
all normal fractionals. Thus e should never be smaller than n.

Usually, all these three numbers have the same value (e = n = z), which means
that in case of e.g. “2” (used for €,$,£) the user can enter cent/penny values
(but not a fraction of those), these cents/pennies are always shown (even if
they are 0) as two normal digits after the decimal separator, and fractions of
a cent/penny are rendered as SuperScriptDigits, but appear only if they are not
trailing zeroes.
For japanese ¥, all three values could be 0, which means that the user cannot
enter fractionals at all. Fractions would never be rendered as normal digits
but always as SuperScript, and appear only if they are not trailing zeroes.

Additionally, some cryptocurrencies have such huge units, that they are
commonly rendered in milli-units, such as mBTC (milliBTC, 1/1000 of a BTC),
Gwei (Giga-WEI), Mwei (Million-WEI), Kwei (Kilo-WEI), or
Mether/Kether/Gether/Tether and more "logical" units such as Szabo and
Finney. See ``https://coinguides.org/ethereum-unit-converter-gwei-ether/`` if
you want a good laugh. Regardless of the self-inflicted insanity here, this
could also make sense for inflated currencies in some cases. So we probably
should also have the ability to ship such a conversion map.



iOS has a built-in currency formatter, which knows how to deal with
thousands-separators and where to apply them (e.g. India uses a mixture of
hundreds and thousands instead of putting the separator after each 3 digits
like western currencies).  However, this formatter will round after two (or
three) fractional digits and thus cannot be used for the whole amount. But it
could be used to convert the integer part plus the fractional part to be
rendered as normal digits (n) into a string, and then remaining fractional
digits (if not zero, or if trailing SuperScript zeroes should be shown)
could be added as SuperScript digits to this formatted string.

(please add information about Android and WebEx here)


Proposed Solution
=================

Protocol considerations
-----------------------

The exchange, bank and merchant backends would need to be configured (via
their configuration files) to return the following ScopedCurrencyInfo in their
``/config`` and/or ``/keys`` endpoints.  The bank returns this so that the
bank SPA can render amounts correctly, the exchange informs the wallets about
the desired way to render the currency, and the merchant backend informs the
merchant SPA --- independently of any particular exchange being used --- how
the merchant SPA should render amounts. Hence, the information will need to be
provisioned by all three services.

  .. code-block:: swift

      public struct ScopedCurrencyInfo: Codable, Sendable {
          // e.g. “Japanese Yen” or "Bitcoin (Mainnet)"
          let name: String
          // e.g. “.” for $ and ¥; “,” for €
          let decimal_separator: String
          // how many digits the user may enter after the decimalSeparator
          let num_fractional_input_digits: Integer
          // €,$,£: 2; some arabic currencies: 3, ¥: 0
          let num_fractional_normal_digits: Int
          // usually same as numFractionalNormalDigits, but e.g. might be 2 for ¥
          let num_fractional_trailing_zero_digits: Int
          // true for “$ 3.50”; false for “3,50 €”
          let is_currency_name_leading: Bool
          // map of powers of 10 to alternative currency names / symbols, must
          // always have an entry under "0" that defines the base name,
          // e.g.  "0 => €" or "3 => k€". For BTC, would be "0 => BTC, -3 => mBTC".
          // This way, we can also communicate the currency symbol to be used.
          let alt_unit_names: Map<Int, String>
      }

For very large (2400000) or very tiny amounts (0.000056) the software would
then first represent the number compactly without any fraction (so for our
examples above, 24 * 10^6 and 56 * 10^-6) and then search for the nearest fit
in the alt_unit_names table. The result might then be 24000 KGELD or 0.056
mGELD, assuming the map had entries for 3 and -3 respectively. Depending on
the table, the result could also be 24 MGELD (6 => MGELD), or 5.6 nGELD
(assuming -6 => nGeld).  Fractional rendering rules would still be applied
to the alternative unit name, alas the "num_fractional_input_digits" would
always apply to the unit currency and may need to be adjusted if amounts
are input using an alternative unit name.

Configuration syntax
--------------------

Each currency should be specified in its own subsystem-independent
currency, with the section name prefixed with "currency-". In that
section. The map could be given directly in JSON. For example:

  .. code-block:: ini

    [currency-euro]
    ENABLED = YES
    name = "Euro"
    code = "EUR"
    decimal_separator = ","
    fractional_input_digits = 2
    fractional_normal_digits = 2
    fractional_trailing_zero_digits = 2
    is_currency_name_leading = NO
    alt_unit_names = {"0":"€"}

    [currency-japanese-yen]
    ENABLED = YES
    name = "Japanese Yen"
    code = "JPY"
    decimal_separator = "."
    fractional_input_digits = 2
    fractional_normal_digits = 0
    fractional_trailing_zero_digits = 2
    is_currency_name_leading = YES
    alt_unit_names = {"0":"¥"}

    [currency-bitcoin-mainnet]
    ENABLED = NO
    name = "Bitcoin (Mainnet)"
    code = "BITCOINBTC"
    decimal_separator = "."
    fractional_input_digits = 8
    fractional_normal_digits = 3
    fractional_trailing_zero_digits = 0
    is_currency_name_leading = NO
    alt_unit_names = {"0":"BTC","-3":"mBTC"}

    [currency-ethereum]
    ENABLED = NO
    name = "WAI-ETHER (Ethereum)"
    code = "EthereumWAI"
    decimal_separator = "."
    fractional_input_digits = 0
    fractional_normal_digits = 0
    fractional_trailing_zero_digits = 0
    is_currency_name_leading = NO
    alt_unit_names = {"0":"WAI","3":"KWAI","6":"MWAI","9":"GWAI","12":"Szabo","15":"Finney","18":"Ether","21":"KEther","24":"MEther"}


Implementation considerations
-----------------------------

For iOS, we plan to format the integer part of the amount with the built-in
currency formatter, then add the fractional part according to this document.

(please add information about Android and WebEx here)



Definition of Done
==================

(Only applicable to design documents that describe a new feature.  While the
DoD is not satisfied yet, a user-facing feature **must** be behind a feature
flag or dev-mode flag.)

  * Configuration (INI) format finalized and documented in taler.conf man page [DONE]
  * Endpoints of libeufin-bank, fakebank, exchange and merchant return the information
  * SPAs use the information to render amounts
  * Wallet-core passes rendering information to wallet UIs
  * Cashier, Android PoS, WebExtension, Android and iOS Wallet render amounts accordingly


Alternatives
============

None, we cannot confuse users by rendering amounts in ways that break cultural
standards, and we cannot round and have numbers in balances not add up.


Drawbacks
=========

Discussion / Q&A
================

We probably should NOT have the decimalSeparator in this definition. Instead that
should be taken from the locale of the user, so they see currency amounts formatted
like they're used to.
If we really keep this, then we would also need the thousandsSeparator to ensure it is
not identical to the decimalSeparator.
Better to leave this can of worms to the operating system our app runs on, and render
according to the user's preferences (locale)...

However, instead of decimalSeparator we could specify the locale this currency belongs to.



(This should be filled in with results from discussions on mailing lists / personal communication.)