taler-docs

Documentation for GNU Taler components, APIs and protocols
Log | Files | Refs | README | LICENSE

051-fractional-digits.rst (10625B)


      1 DD 51: Fractional Digits
      2 #########################
      3 
      4 Summary
      5 =======
      6 
      7 This design document specifies how an amount's fractional digits should be rendered.
      8 Note that UIs that cannot render amounts as specified (e.g. because the display does
      9 not support super script digits) may ignore the rendering advice provided by the
     10 protocol under this DD.
     11 
     12 
     13 Motivation
     14 ==========
     15 
     16 Since different currencies have different ways to show/render fractionals, the
     17 end-user apps should follow these guidelines.
     18 
     19 Requirements
     20 ============
     21 
     22 There was already a specification for ScopedCurrencyInfo - which got renamed to CurrencySpecification.
     23 
     24 We need three core characteristics for fractional digits for each currency:
     25 
     26   e) the number of fractional digits e in [0..8] the user may 'e'nter in a TextInputField
     27 
     28   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).
     29 
     30   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.
     31 
     32 The values e, n, and z are independent from each other. Each could be any value
     33 from 0 to 8. However, when a user enters an amount, s/he should be able to input
     34 all normal fractionals. Thus e should never be smaller than n.
     35 
     36 Usually, all these three numbers have the same value (e = n = z), which means
     37 that in case of e.g. '2' (used for €,$,£) the user can enter cent/penny values
     38 (but not a fraction of those), these cents/pennies are always shown (even if
     39 they are 0) as two normal digits after the decimal separator, and fractions of
     40 a cent/penny are rendered as SuperScriptDigits, but appear only if they are not
     41 trailing zeroes.
     42 For japanese ¥, all three values could be 0, which means that the user cannot
     43 enter fractions at all. If there are fractions they would never be rendered as
     44 normal digits but always as SuperScript, and appear only if they are not
     45 trailing zeroes.
     46 
     47 Additionally, some cryptocurrencies have such huge units, that they are
     48 commonly rendered in milli-units, such as mBTC (milliBTC, 1/1000 of a BTC),
     49 Gwei (Giga-WEI), Mwei (Million-WEI), Kwei (Kilo-WEI), or
     50 Mether/Kether/Gether/Tether and more "logical" units such as Szabo and
     51 Finney. See ``https://coinguides.org/ethereum-unit-converter-gwei-ether/`` if
     52 you want a good laugh. Regardless of the self-inflicted insanity here, this
     53 could also make sense for inflated currencies in some cases. So we probably
     54 should also have the ability to ship such a conversion map.
     55 
     56 For the "withdraw" dialog action buttons with the pre-filled amounts in
     57 some wallet GUIs, we also need to provide wallets with an idea of what
     58 good amounts would be.
     59 
     60 
     61 Proposed Solution
     62 =================
     63 
     64 Protocol considerations
     65 -----------------------
     66 
     67 The exchange, bank and merchant backends would need to be configured (via
     68 their configuration files) to return the following CurrencySpecification in their
     69 ``/config`` and/or ``/keys`` endpoints.  The bank returns this so that the
     70 bank SPA can render amounts correctly, the exchange informs the wallets about
     71 the desired way to render the currency, and the merchant backend informs the
     72 merchant SPA --- independently of any particular exchange being used --- how
     73 the merchant SPA should render amounts. Hence, the information will need to be
     74 provisioned by all three services.
     75 
     76   .. code-block:: swift
     77 
     78       public struct CurrencySpecification: Codable, Sendable {
     79           // e.g. “Japanese Yen” or "Bitcoin (Mainnet)"
     80           let name: String
     81           // how many digits the user may enter after the decimal separator
     82           let fractional_input_digits: Int
     83           // €,$,£: 2; some arabic currencies: 3, ¥: 0
     84           let fractional_normal_digits: Int
     85           // usually same as fractionalNormalDigits, but e.g. might be 2 for ¥
     86           let fractional_trailing_zero_digits: Int
     87           // specifies whether the keys in `alt_unit_names' are symbols
     88           // (e.g. €, k€) or names (e.g. BTC, mBTC), so that apps can decide
     89           // how to render it (e.g. EUR 10 vs €10)
     90           let alt_unit_names_are_symbols: Bool
     91           // map of powers of 10 to alternative currency names / symbols,
     92           // must always have an entry under "0" that defines the base name,
     93           // e.g.  "0 : €" or "3 : k€". For BTC, would be "0 : BTC, -3 : mBTC".
     94           // This way, we can also communicate the currency symbol to be used.
     95           let alt_unit_names: [Int : String]
     96 
     97           // An array of common amounts that should be turned into
     98           // display buttons in dialogs where the user might like
     99           // a short-cut.  The array should have four entries, but
    100           // may have fewer or more entries. Wallets may omit
    101           // later entries in the array.
    102           let common_amounts: [Amount]
    103       }
    104 
    105 (Note: decimal_separator, group_separator and is_currency_name_leading were
    106 removed from this struct since they should always be taken from the user's
    107 locale.)
    108 
    109 For very large (2400000) or very tiny amounts (0.000056) the software would
    110 then first represent the number compactly without any fraction (so for our
    111 examples above, 24 * 10^6 and 56 * 10^-6) and then search for the nearest fit
    112 in the alt_unit_names table. The result might then be 24000 KGELD or 0.056
    113 mGELD, assuming the map had entries for 3 and -3 respectively. Depending on
    114 the table, the result could also be 24 MGELD (6 : MGELD), or 5.6 nGELD
    115 (assuming -6 : nGeld).  Fractional rendering rules would still be applied
    116 to the alternative unit name, alas the "fractional_input_digits" would
    117 always apply to the unit currency and may need to be adjusted if amounts
    118 are input using an alternative unit name.
    119 
    120 Configuration syntax
    121 --------------------
    122 
    123 Each currency should be specified in its own subsystem-independent
    124 currency, with the section name prefixed with "currency-". In that
    125 section. The map could be given directly in JSON. For example:
    126 
    127   .. code-block:: ini
    128 
    129     [currency-euro]
    130     ENABLED = YES
    131     name = "Euro"
    132     code = "EUR"
    133     fractional_input_digits = 2
    134     fractional_normal_digits = 2
    135     fractional_trailing_zero_digits = 2
    136     alt_unit_names_are_symbols = YES
    137     alt_unit_names = {"0":"€"}
    138     common_amounts = EUR:10 EUR:25 EUR:50 EUR:100
    139 
    140     [currency-japanese-yen]
    141     ENABLED = YES
    142     name = "Japanese Yen"
    143     code = "JPY"
    144     fractional_input_digits = 2
    145     fractional_normal_digits = 0
    146     fractional_trailing_zero_digits = 2
    147     alt_unit_names_are_symbols = YES
    148     alt_unit_names = {"0":"¥"}
    149     common_amounts = JPY:500 JPY:1000 JPY:5000 JPY:10000
    150 
    151     [currency-bitcoin-mainnet]
    152     ENABLED = NO
    153     name = "Bitcoin (Mainnet)"
    154     code = "BITCOINBTC"
    155     fractional_input_digits = 8
    156     fractional_normal_digits = 3
    157     fractional_trailing_zero_digits = 0
    158     alt_unit_names_are_symbols = NO
    159     alt_unit_names = {"0":"BTC","-3":"mBTC"}
    160     common_amounts = BITCOINBTC:0.001 BITCOINBTC:0.01 BITCOINBTC:0.02 BITCOINBTC:0.025
    161 
    162     [currency-ethereum]
    163     ENABLED = NO
    164     name = "WAI-ETHER (Ethereum)"
    165     code = "EthereumWAI"
    166     fractional_input_digits = 0
    167     fractional_normal_digits = 0
    168     fractional_trailing_zero_digits = 0
    169     alt_unit_names_are_symbols = NO
    170     alt_unit_names = {"0":"WAI","3":"KWAI","6":"MWAI","9":"GWAI","12":"Szabo","15":"Finney","18":"Ether","21":"KEther","24":"MEther"}
    171     common_amounts = EthereumWAI:0.001 EthereumWAI:0.01
    172 
    173 
    174 Implementation considerations
    175 -----------------------------
    176 
    177 iOS has a built-in currency formatter, which can be configured from a locale.
    178 It knows how to deal with group-separators and where to apply them (e.g. India
    179 uses a mixture of thousands and hundreds instead of putting the separator
    180 after each 3 digits like western currencies). Set the formatter's parameter
    181 ``maximumFractionDigits`` to 8, then it will not round the value and thus can
    182 be used for the whole amount. Set its parameter ``minimumFractionDigits`` to
    183 'z' (``fractionalTrailingZeroDigits``) to let it automatically add trailing
    184 zeroes. Then convert all fractional digits after 'n'
    185 (``fractionalNormalDigits``) to SuperScript digits.
    186 
    187 The field ``alt_unit_names_are_symbols`` was introduced in order to help UIs
    188 better decide how to render amounts with unit names (e.g. BTC, mBTC) instead
    189 of unit symbols (e.g. €, k€). Typically, currency symbols (in Android and iOS)
    190 are rendered, depending on the locale, before or after the amount without a
    191 space in between (e.g. €10), however, if the given currency has names instead
    192 of symbols for its units, rendering amounts without a space in between
    193 (e.g. BTC10) is not ideal and results in ugliness and user
    194 dissatisfaction. When this field is set to ``true``, it is expected that the
    195 wallet apps will render the amount with a space in between.
    196 
    197 Definition of Done
    198 ==================
    199 
    200 (Only applicable to design documents that describe a new feature.  While the
    201 DoD is not satisfied yet, a user-facing feature **must** be behind a feature
    202 flag or dev-mode flag.)
    203 
    204   * Configuration (INI) format finalized and documented in taler.conf man page [DONE]
    205   * Endpoints of libeufin-bank, fakebank, exchange and merchant return the information
    206   * SPAs use the information to render amounts
    207   * Wallet-core passes rendering information to wallet UIs
    208   * Cashier, Android PoS, WebExtension, Android and iOS Wallet render amounts accordingly
    209 
    210 
    211 Alternatives
    212 ============
    213 
    214 None, we cannot confuse users by rendering amounts in ways that break cultural
    215 standards, and we cannot round and have numbers in balances not add up.
    216 
    217 
    218 Drawbacks
    219 =========
    220 
    221 Discussion / Q&A
    222 ================
    223 
    224 We probably should NOT have the decimalSeparator in this definition. Instead that
    225 should be taken from the locale of the user, so they see currency amounts formatted
    226 like they're used to.
    227 If we really keep this, then we would also need the groupSeparator to ensure it is
    228 not identical to the decimalSeparator.
    229 Better to leave this can of worms to the operating system our app runs on, and render
    230 according to the user's preferences (locale)...
    231 
    232 However, instead of decimalSeparator we could specify the locale this currency belongs to.
    233 
    234 
    235 
    236 (This should be filled in with results from discussions on mailing lists / personal communication.)