# coding: utf-8
from __future__ import division
import math
import re
# Print a floating-point number in engineering notation.
# Ported from [C version][1] written by
# Jukka “Yucca” Korpela <jkorpela@cs.tut.fi>.
#
# [1]: http://www.cs.tut.fi/~jkorpela/c/eng.html
#: .. versionchanged:: 1.0
#: Define as unicode string and use µ (i.e., ``\N{MICRO SIGN}``, ``\x0b5``)
#: to denote micro (not u).
#:
#: .. seealso::
#:
#: `Issue #4`_.
#:
#: `Forum post`_ discussing unicode using µ as an example.
#:
#: `The International System of Units (SI) report`_ from the Bureau
#: International des Poids et Mesures
#:
#: .. _`Issue #4`: https://github.com/cfobel/si-prefix/issues/4
#: .. _`Forum post`: https://mail.python.org/pipermail/python-list/2009-February/525913.html
#: .. _`The International System of Units (SI) report`: https://www.bipm.org/utils/common/pdf/si_brochure_8_en.pdf
SI_PREFIX_UNITS = u"yzafpnµm kMGTPEZY"
#: .. versionchanged:: 1.0
#: Use unicode string for SI unit to support micro (i.e., µ) character.
#:
#: .. seealso::
#:
#: `Issue #4`_.
#:
#: .. _`Issue #4`: https://github.com/cfobel/si-prefix/issues/4
CRE_SI_NUMBER = re.compile(r'\s*(?P<sign>[\+\-])?'
r'(?P<integer>\d+)'
r'(?P<fraction>.\d+)?\s*'
u'(?P<si_unit>[%s])?\s*' % SI_PREFIX_UNITS)
[docs]def split(value, precision=1):
'''
Split `value` into value and "exponent-of-10", where "exponent-of-10" is a
multiple of 3. This corresponds to SI prefixes.
Returns tuple, where the second value is the "exponent-of-10" and the first
value is `value` divided by the "exponent-of-10".
Args
----
value : int, float
Input value.
precision : int
Number of digits after decimal place to include.
Returns
-------
tuple
The second value is the "exponent-of-10" and the first value is `value`
divided by the "exponent-of-10".
Examples
--------
.. code-block:: python
si_prefix.split(0.04781) -> (47.8, -3)
si_prefix.split(4781.123) -> (4.8, 3)
See :func:`si_format` for more examples.
'''
negative = False
digits = precision + 1
if value < 0.:
value = -value
negative = True
elif value == 0.:
return 0., 0
expof10 = int(math.log10(value))
if expof10 > 0:
expof10 = (expof10 // 3) * 3
else:
expof10 = (-expof10 + 3) // 3 * (-3)
value *= 10 ** (-expof10)
if value >= 1000.:
value /= 1000.0
expof10 += 3
elif value >= 100.0:
digits -= 2
elif value >= 10.0:
digits -= 1
if negative:
value *= -1
return value, int(expof10)
[docs]def prefix(expof10):
'''
Args:
expof10 : Exponent of a power of 10 associated with a SI unit
character.
Returns:
str : One of the characters in "yzafpnum kMGTPEZY".
'''
prefix_levels = (len(SI_PREFIX_UNITS) - 1) // 2
si_level = expof10 // 3
if abs(si_level) > prefix_levels:
raise ValueError("Exponent out range of available prefixes.")
return SI_PREFIX_UNITS[si_level + prefix_levels]
[docs]def si_parse(value):
'''
Parse a value expressed using SI prefix units to a floating point number.
Parameters
----------
value : str or unicode
Value expressed using SI prefix units (as returned by :func:`si_format`
function).
.. versionchanged:: 1.0
Use unicode string for SI unit to support micro (i.e., µ) character.
.. seealso::
`Issue #4`_.
.. _`Issue #4`: https://github.com/cfobel/si-prefix/issues/4
'''
CRE_10E_NUMBER = re.compile(r'^\s*(?P<integer>[\+\-]?\d+)?'
r'(?P<fraction>.\d+)?\s*([eE]\s*'
r'(?P<expof10>[\+\-]?\d+))?$')
CRE_SI_NUMBER = re.compile(r'^\s*(?P<number>(?P<integer>[\+\-]?\d+)?'
r'(?P<fraction>.\d+)?)\s*'
u'(?P<si_unit>[%s])?\s*$' % SI_PREFIX_UNITS)
match = CRE_10E_NUMBER.match(value)
if match:
# Can be parse using `float`.
assert(match.group('integer') is not None or
match.group('fraction') is not None)
return float(value)
match = CRE_SI_NUMBER.match(value)
assert(match.group('integer') is not None or
match.group('fraction') is not None)
d = match.groupdict()
si_unit = d['si_unit'] if d['si_unit'] else ' '
prefix_levels = (len(SI_PREFIX_UNITS) - 1) // 2
scale = 10 ** (3 * (SI_PREFIX_UNITS.index(si_unit) - prefix_levels))
return float(d['number']) * scale
[docs]def si_prefix_scale(si_unit):
'''
Parameters
----------
si_unit : str
SI unit character, i.e., one of "yzafpnµm kMGTPEZY".
Returns
-------
int
Multiple associated with `si_unit`, e.g., 1000 for `si_unit=k`.
'''
return 10 ** si_prefix_expof10(si_unit)
[docs]def si_prefix_expof10(si_unit):
'''
Parameters
----------
si_unit : str
SI unit character, i.e., one of "yzafpnµm kMGTPEZY".
Returns
-------
int
Exponent of the power of ten associated with `si_unit`, e.g., 3 for
`si_unit=k` and -6 for `si_unit=µ`.
'''
prefix_levels = (len(SI_PREFIX_UNITS) - 1) // 2
return (3 * (SI_PREFIX_UNITS.index(si_unit) - prefix_levels))