diff --git a/si_prefix/__init__.py b/si_prefix/__init__.py index 75ea834..3ffc5e1 100644 --- a/si_prefix/__init__.py +++ b/si_prefix/__init__.py @@ -1,9 +1,11 @@ # coding: utf-8 from __future__ import division + import math import re from ._version import get_versions + __version__ = get_versions()['version'] del get_versions @@ -30,6 +32,7 @@ #: .. _`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. #: @@ -38,14 +41,13 @@ #: `Issue #4`_. #: #: .. _`Issue #4`: https://github.com/cfobel/si-prefix/issues/4 -CRE_SI_NUMBER = re.compile(r'\s*(?P[\+\-])?' - r'(?P\d+)' - r'(?P.\d+)?\s*' - u'(?P[%s])?\s*' % SI_PREFIX_UNITS) +CRE_SI_NUMBER = re.compile(r'^\s*(?P(?P[+\-]?\d+)?' + r'(?P.\d+)?)\s*' + r'(?P[' + SI_PREFIX_UNITS + r'])?\s*$') 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. @@ -74,7 +76,7 @@ def split(value, precision=1): si_prefix.split(4781.123) -> (4.8, 3) See :func:`si_format` for more examples. - ''' + """ negative = False digits = precision + 1 @@ -107,7 +109,7 @@ def split(value, precision=1): def prefix(expof10): - ''' + """ Args: expof10 : Exponent of a power of 10 associated with a SI unit @@ -116,7 +118,7 @@ def prefix(expof10): Returns: str : One of the characters in "yzafpnum kMGTPEZY". - ''' + """ prefix_levels = (len(SI_PREFIX_UNITS) - 1) // 2 si_level = expof10 // 3 @@ -126,8 +128,8 @@ def prefix(expof10): def si_format(value, precision=1, format_str=u'{value} {prefix}', - exp_format_str=u'{value}e{expof10}'): - ''' + exp_format_str=u'{value}e{expof10}', trailing_zeroes=False): + """ Format value to string with SI prefix, using the specified precision. Parameters @@ -136,15 +138,17 @@ def si_format(value, precision=1, format_str=u'{value} {prefix}', Input value. precision : int Number of digits after decimal place to include. - format_str : str or unicode + exp_format_str : str or unicode Format string where ``{prefix}`` and ``{value}`` represent the SI prefix and the value (scaled according to the prefix), respectively. The default format matches the `SI prefix style`_ format. - exp_str : str or unicode + format_str : str or unicode Format string where ``{expof10}`` and ``{value}`` represent the exponent of 10 and the value (scaled according to the exponent of 10), respectively. This format is used if the absolute exponent of 10 value is greater than 24. + trailing_zeroes : bool + Include trailing zeroes if number of decimals in value is less than the precision Returns ------- @@ -205,24 +209,27 @@ def si_format(value, precision=1, format_str=u'{value} {prefix}', .. _`Issue #4`: https://github.com/cfobel/si-prefix/issues/4 .. _SI prefix style: - http://physics.nist.gov/cuu/Units/checklist.html - ''' + https://physics.nist.gov/cuu/Units/checklist.html + """ svalue, expof10 = split(value, precision) - value_format = u'%%.%df' % precision - value_str = value_format % svalue + if trailing_zeroes: + value_str = f"{svalue:.{precision}f}" + else: + # rounds to precision without trailing zeroes which drives me insane + value_str = f"{round(svalue, precision):g}" try: return format_str.format(value=value_str, - prefix=prefix(expof10).strip()) + prefix=prefix(expof10).strip()).strip() except ValueError: sign = '' if expof10 > 0: sign = "+" return exp_format_str.format(value=value_str, - expof10=''.join([sign, str(expof10)])) + expof10=''.join([sign, str(expof10)])).strip() def si_parse(value): - ''' + """ Parse a value expressed using SI prefix units to a floating point number. Parameters @@ -240,22 +247,20 @@ def si_parse(value): `Issue #4`_. .. _`Issue #4`: https://github.com/cfobel/si-prefix/issues/4 - ''' - CRE_10E_NUMBER = re.compile(r'^\s*(?P[\+\-]?\d+)?' + """ + CRE_10E_NUMBER = re.compile(r'^\s*(?P[+\-]?\d+)?' r'(?P.\d+)?\s*([eE]\s*' - r'(?P[\+\-]?\d+))?$') - CRE_SI_NUMBER = re.compile(r'^\s*(?P(?P[\+\-]?\d+)?' - r'(?P.\d+)?)\s*' - u'(?P[%s])?\s*$' % SI_PREFIX_UNITS) + r'(?P[+\-]?\d+))?$') + 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) + 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) + 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 @@ -264,7 +269,7 @@ def si_parse(value): def si_prefix_scale(si_unit): - ''' + """ Parameters ---------- si_unit : str @@ -274,12 +279,12 @@ def si_prefix_scale(si_unit): ------- int Multiple associated with `si_unit`, e.g., 1000 for `si_unit=k`. - ''' + """ return 10 ** si_prefix_expof10(si_unit) def si_prefix_expof10(si_unit): - ''' + """ Parameters ---------- si_unit : str @@ -290,6 +295,6 @@ def si_prefix_expof10(si_unit): 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)) + return 3 * (SI_PREFIX_UNITS.index(si_unit) - prefix_levels)