250 lines
6.8 KiB
Python
250 lines
6.8 KiB
Python
# 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
|
|
|
|
SI_PREFIX_UNITS = "yzafpnum kMGTPEZY"
|
|
CRE_SI_NUMBER = re.compile(r'\s*(?P<sign>[\+\-])?'
|
|
r'(?P<integer>\d+)'
|
|
r'(?P<fraction>.\d+)?\s*'
|
|
r'(?P<si_unit>[%s])?\s*' % SI_PREFIX_UNITS)
|
|
|
|
|
|
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 `si_prefix.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)
|
|
|
|
|
|
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]
|
|
|
|
|
|
def si_format(value, precision=1, format_str='{value} {prefix}',
|
|
exp_format_str='{value}e{expof10}'):
|
|
'''
|
|
Format value to string with SI prefix, using the specified precision.
|
|
|
|
Args
|
|
----
|
|
|
|
value : int, float
|
|
Input value.
|
|
precision : int
|
|
Number of digits after decimal place to include.
|
|
format_str : str
|
|
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
|
|
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.
|
|
|
|
Returns
|
|
-------
|
|
str
|
|
`value` formatted according to the `SI prefix style`_.
|
|
|
|
Examples
|
|
--------
|
|
|
|
For example, with `precision=2`:
|
|
|
|
.. code-block:: python
|
|
|
|
1e-27 --> 1.00e-27
|
|
1.764e-24 --> 1.76 y
|
|
7.4088e-23 --> 74.09 y
|
|
3.1117e-21 --> 3.11 z
|
|
1.30691e-19 --> 130.69 z
|
|
5.48903e-18 --> 5.49 a
|
|
2.30539e-16 --> 230.54 a
|
|
9.68265e-15 --> 9.68 f
|
|
4.06671e-13 --> 406.67 f
|
|
1.70802e-11 --> 17.08 p
|
|
7.17368e-10 --> 717.37 p
|
|
3.01295e-08 --> 30.13 n
|
|
1.26544e-06 --> 1.27 u
|
|
5.31484e-05 --> 53.15 u
|
|
0.00223223 --> 2.23 m
|
|
0.0937537 --> 93.75 m
|
|
3.93766 --> 3.94
|
|
165.382 --> 165.38
|
|
6946.03 --> 6.95 k
|
|
291733 --> 291.73 k
|
|
1.22528e+07 --> 12.25 M
|
|
5.14617e+08 --> 514.62 M
|
|
2.16139e+10 --> 21.61 G
|
|
9.07785e+11 --> 907.78 G
|
|
3.8127e+13 --> 38.13 T
|
|
1.60133e+15 --> 1.60 P
|
|
6.7256e+16 --> 67.26 P
|
|
2.82475e+18 --> 2.82 E
|
|
1.1864e+20 --> 118.64 E
|
|
4.98286e+21 --> 4.98 Z
|
|
2.0928e+23 --> 209.28 Z
|
|
8.78977e+24 --> 8.79 Y
|
|
3.6917e+26 --> 369.17 Y
|
|
1.55051e+28 --> 15.51e+27
|
|
6.51216e+29 --> 651.22e+27
|
|
|
|
|
|
.. _SI prefix style:
|
|
http://physics.nist.gov/cuu/Units/checklist.html
|
|
'''
|
|
svalue, expof10 = split(value, precision)
|
|
value_format = '%%.%df' % precision
|
|
value_str = value_format % svalue
|
|
try:
|
|
return format_str.format(value=value_str,
|
|
prefix=prefix(expof10).strip())
|
|
except ValueError:
|
|
sign = ''
|
|
if expof10 > 0:
|
|
sign = "+"
|
|
return exp_format_str.format(value=value_str,
|
|
expof10=''.join([sign, str(expof10)]))
|
|
|
|
|
|
def si_parse(value):
|
|
'''
|
|
Parse a value expressed using SI prefix units to a floating point number.
|
|
|
|
Args:
|
|
|
|
value (str) : Value expressed using SI prefix units (as returned by
|
|
`si_format` function).
|
|
'''
|
|
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*'
|
|
r'(?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
|
|
|
|
|
|
def si_prefix_scale(si_unit):
|
|
'''
|
|
Parameters
|
|
----------
|
|
si_unit : str
|
|
SI unit character, i.e., one of "yzafpnum kMGTPEZY".
|
|
|
|
Returns
|
|
-------
|
|
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
|
|
SI unit character, i.e., one of "yzafpnum 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=u`.
|
|
'''
|
|
prefix_levels = (len(SI_PREFIX_UNITS) - 1) // 2
|
|
return (3 * (SI_PREFIX_UNITS.index(si_unit) - prefix_levels))
|
|
|