Skip to content

Commit

Permalink
Allow currency symbols to be given after the number, as is common pra…
Browse files Browse the repository at this point in the history
…ctice in some regions
  • Loading branch information
Ken Kundert authored and Ken Kundert committed Jan 5, 2022
1 parent b464ce0 commit 6827705
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 21 deletions.
60 changes: 60 additions & 0 deletions doc/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -915,3 +915,63 @@ If you do, the output of the script looks like this::
A more sophisticated version of `cryptocurrency
<https://github.com/KenKundert/cryptocurrency/blob/master/cryptocurrency>`_
this example can be found on GitHub.
.. bitcoin example {{{1
Dynamic Unit Conversions
------------------------
Normally unit conversions are static, meaning that once the conversion values
are set they do not change during the life of the process. However, that need
not be true if functions are used to perform the conversion. In the following
example, the current price of Bitcoin is queried from a price service and used
in the conversion. The price service is queried each time a conversion is
performed, so it is always up-to-date, no longer how long the program runs.
.. code-block:: python
#!/usr/bin/env python3
# Bitcoin
# This example demonstrates how to use UnitConversion to convert between
# bitcoin and dollars at the current price.
from quantiphy import Quantity, UnitConversion
import requests
# get the current bitcoin price from coingecko.com
url = 'https://api.coingecko.com/api/v3/simple/price'
params = dict(ids='bitcoin', vs_currencies='usd')
def get_btc_price():
try:
resp = requests.get(url=url, params=params)
prices = resp.json()
return prices['bitcoin']['usd']
except Exception as e:
print('error: cannot connect to coingecko.com.')
# use UnitConversion from QuantiPhy to perform the conversion
bitcoin_units = ['BTC', 'btc', 'Ƀ', '']
satoshi_units = ['sat', 'sats', 'ș']
dollar_units = ['USD', 'usd', '$']
UnitConversion(
dollar_units, bitcoin_units,
lambda b: b*get_btc_price(), lambda d: d/get_btc_price()
)
UnitConversion(satoshi_units, bitcoin_units, 1e8)
UnitConversion(
dollar_units, satoshi_units,
lambda s: s*get_btc_price()/1e8, lambda d: d/(get_btc_price()/1e8),
)
unit_btc = Quantity('1 BTC')
unit_dollar = Quantity('$1')
print(f'{unit_btc:>8,.2p} = {unit_btc:,.2p$}')
print(f'{unit_dollar:>8,.2p} = {unit_dollar:,.0psat}')
When run, the script prints something like this:
1 BTC = $46,007
$1 = 2,174 sat
3 changes: 3 additions & 0 deletions doc/releases.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ Latest development release
| Version: 2.16.1
| Released: 2021-12-23
- Refine the list of currency symbols.
- Allows currency symbols to be given before or after the underlying number.
- Allow :class:`Quantity` subclasses to be used in scaling if they have units.


2.16 (2021-12-14)
-----------------
- Add support for — as comment character and make it the default.
Expand Down
2 changes: 1 addition & 1 deletion doc/user.rst
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,7 @@ QuantiPhy* provides a collection of pre-defined converters for common units:

====== ================================================================
K: K, F °F, R °R
C, °C: K, C °C, F °F, R °R
C °C: K, C °C, F °F, R °R
m: km, m, cm, mm, um μm micron, nm, Å angstrom, mi mile miles,
in inch inches
g: oz, lb lbs
Expand Down
14 changes: 7 additions & 7 deletions quantiphy.py
Original file line number Diff line number Diff line change
Expand Up @@ -428,12 +428,12 @@ def add_constant(value, alias=None, unit_systems=None):
# These must be given in order, one for every three decades.

# Supported currency symbols (these go on left side of number)
CURRENCY_SYMBOLS = '$€¥£₩₺₽₹ɃΞȄ'
CURRENCY_SYMBOLS = '$€¥£₩₺₽₹Ƀ₿Ξ'

# Unit symbols that are not simple letters.
# Do not include % as it will be picked up when converting text to numbers,
# which is generally not desired (you would end up converting 0.001% to 1m%).
UNIT_SYMBOLS = '°ÅΩƱΩ℧'
UNIT_SYMBOLS = '°ÅΩƱΩ℧¢$€¥£₩₺₽₹Ƀ₿șΞ'

# Regular expression for recognizing and decomposing string .format method codes
FORMAT_SPEC = re.compile(r'''\A
Expand All @@ -446,7 +446,7 @@ def add_constant(value, alias=None, unit_systems=None):
([qpPQrRbBusSeEfFgGdn]) # format
([a-zA-Z%{us}{cs}][-^/()\w]*)? # units
)?
\Z'''.format(cs=CURRENCY_SYMBOLS, us=UNIT_SYMBOLS), re.VERBOSE)
\Z'''.format(cs=re.escape(CURRENCY_SYMBOLS), us=re.escape(UNIT_SYMBOLS)), re.VERBOSE)

# Defaults {{{1
DEFAULTS = dict(
Expand Down Expand Up @@ -1244,8 +1244,8 @@ def fix_sign(num):
currency = _named_regex('currency', '[%s]' % CURRENCY_SYMBOLS)
units = _named_regex(
r'units', r'(?:[a-zA-Z%√{us}{cur}][-^/()\w·⁻⁰¹²³⁴⁵⁶⁷⁸⁹√{us}{cur}]*)?'.format(
us = UNIT_SYMBOLS,
cur = CURRENCY_SYMBOLS,
us = re.escape(UNIT_SYMBOLS),
cur = re.escape(CURRENCY_SYMBOLS),
)
# examples: Ohms, V/A, J-s, m/s^2, H/(m-s), Ω, %, m·s⁻², V/√Hz
# leading char must be letter to avoid 1.0E-9s -> (1e18, '-9s')
Expand Down Expand Up @@ -1391,10 +1391,10 @@ def fix_sign(num):
]

# numbers embedded in text {{{3
smpl_units = '[a-zA-Z_{us}]*'.format(us=UNIT_SYMBOLS)
smpl_units = '[a-zA-Z_{us}]*'.format(us=re.escape(UNIT_SYMBOLS))
# may only contain alphabetic characters, ex: V, A, _Ohms, etc.
# or obvious unicode units, ex: °ÅΩƱ
sf_or_units = '[a-zA-Z_µ{us}]+'.format(us=UNIT_SYMBOLS)
sf_or_units = '[a-zA-Z_µ{us}]+'.format(us=re.escape(UNIT_SYMBOLS))
# must match units or scale factors: add µ, make non-optional
space = '[   ]?' # optional non-breaking space (do not use a normal space)
left_delimit = r'(?:\A|(?<=[^a-zA-Z0-9_.]))'
Expand Down
86 changes: 73 additions & 13 deletions tests/test_quantity.nt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
test_number_recognition:

# some simple cases
# some simple cases {{{1
grange:
given: q = Quantity(0)
tests:
Expand Down Expand Up @@ -111,7 +111,7 @@ test_number_recognition:
evaluate: str(q)
expected: '2 s'

# test all the scale factors
# test all the scale factors {{{1
quill:
given: q = Quantity('1ys')
tests:
Expand Down Expand Up @@ -323,7 +323,7 @@ test_number_recognition:
expected: '1e24 s'


# test zero
# test zero {{{1
nickel:
given: q = Quantity('0ns')
tests:
Expand Down Expand Up @@ -549,7 +549,7 @@ test_number_recognition:
expected: '0 s'


# test various forms of the mantissa when using scale factors
# test various forms of the mantissa when using scale factors {{{1
delicacy:
given: q = Quantity('1ns')
tests:
Expand Down Expand Up @@ -831,7 +831,7 @@ test_number_recognition:
expected: '100 ns'


# test various forms of the mantissa when using exponents
# test various forms of the mantissa when using exponents {{{1
hairpiece:
given: q = Quantity('1e-9s')
tests:
Expand Down Expand Up @@ -1103,7 +1103,7 @@ test_number_recognition:
expected: '-100 ps'


# test various forms of the mantissa alone
# test various forms of the mantissa alone {{{1
educate:
given: q = Quantity('100000.0s')
tests:
Expand Down Expand Up @@ -1325,7 +1325,7 @@ test_number_recognition:
expected: '10 us'


# test various forms of units
# test various forms of units {{{1
impute:
given: q = Quantity('1nΩ')
tests:
Expand Down Expand Up @@ -1575,7 +1575,7 @@ test_number_recognition:
expected: '1 nm·s⁻²'


# test currency
# test currency {{{1
bishop:
given: q = Quantity('$10K')
tests:
Expand Down Expand Up @@ -1817,8 +1817,68 @@ test_number_recognition:
evaluate: q.fixed(prec=2, strip_zeros=True, strip_radix=True)
expected: '$100'

retina:
given: q = Quantity('Ƀ1.4')
tests:
tendon:
evaluate: q.as_tuple()
expected: (1.4, 'Ƀ')
recover:
evaluate: str(q)
expected: 'Ƀ1.4'

curly:
given: q = Quantity('₿1.4')
tests:
tendon:
evaluate: q.as_tuple()
expected: (1.4, '₿')
recover:
evaluate: str(q)
expected: '₿1.4'

blaze:
given: q = Quantity('1.4 ș')
tests:
tendon:
evaluate: q.as_tuple()
expected: (1.4, 'ș')
recover:
evaluate: str(q)
expected: '1.4 ș'

splendor:
given: q = Quantity('1.4 ¢')
tests:
tendon:
evaluate: q.as_tuple()
expected: (1.4, '¢')
recover:
evaluate: str(q)
expected: '1.4 ¢'

brassy:
given: q = Quantity('1.4Ƀ')
tests:
tendon:
evaluate: q.as_tuple()
expected: (1.4, 'Ƀ')
recover:
evaluate: str(q)
expected: 'Ƀ1.4'

backlog:
given: q = Quantity('1.4 ₿')
tests:
tendon:
evaluate: q.as_tuple()
expected: (1.4, '₿')
recover:
evaluate: str(q)
expected: '₿1.4'


# test unusual numbers
# test unusual numbers {{{1
sheathe:
given: q = Quantity('inf')
tests:
Expand Down Expand Up @@ -2244,7 +2304,7 @@ test_number_recognition:
expected: '$100M'


# test full precision
# test full precision {{{1
fiery:
given: q = Quantity('3.14159265ns')
tests:
Expand Down Expand Up @@ -2526,7 +2586,7 @@ test_number_recognition:
expected: '31.415926500 fs'


# test preferences
# test preferences {{{1
chestnut:
given: q = Quantity('1 ns')
tests:
Expand Down Expand Up @@ -2961,7 +3021,7 @@ test_number_recognition:
expected: '−4.7 kΩ'


# test constants
# test constants {{{1
neuron:
given: q = Quantity('h')
tests:
Expand Down Expand Up @@ -3343,7 +3403,7 @@ test_number_recognition:
evaluate: f'{q:S}'
expected: "Z₀ = 376.73 Ohms"

# test scaling
# test scaling {{{1
segregate:
given: q = Quantity(2, scale=(1000, 'g'))
tests:
Expand Down

0 comments on commit 6827705

Please sign in to comment.