8

I have a float number such as x=23392342.1

I would like to convert it to a string with engineering notation (with metric prefix)

http://en.wikipedia.org/wiki/Engineering_notation http://en.wikipedia.org/wiki/Metric_prefix

So in my example 23392342.1 = 23.3923421E6 = 23.3923421 M (mega)

I would like to display 23.3923421 M

Cœur
  • 37,241
  • 25
  • 195
  • 267
working4coins
  • 1,997
  • 3
  • 22
  • 30
  • Please tell us how you would approach your problem and why you think that your idea is not optimal or which part of your idea you can't implement. – thejh Mar 31 '13 at 19:53
  • I think I will need a dict to store every metric prefix. I will probably calculate log10 to find the "nearest" exponent which is multiple of 3 (but except for da, h,) ... but this not so trivial... (because of this exceptions)... I also wondering if there is no STANDARD way with Python to achieve such a task because I want to manage every possible metric prefix – working4coins Mar 31 '13 at 19:57
  • Also see, [`pint`](https://pint.readthedocs.io/en/stable/) – phoenix Jul 26 '20 at 13:49

5 Answers5

12

Here is a function inspired from Formatting a number with a metric prefix?

metric.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import math


def to_si(d, sep=' '):
    """
    Convert number to string with SI prefix

    :Example:

    >>> to_si(2500.0)
    '2.5 k'

    >>> to_si(2.3E6)
    '2.3 M'

    >>> to_si(2.3E-6)
    '2.3 µ'

    >>> to_si(-2500.0)
    '-2.5 k'

    >>> to_si(0)
    '0'

    """

    inc_prefixes = ['k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']
    dec_prefixes = ['m', 'µ', 'n', 'p', 'f', 'a', 'z', 'y']

    if d == 0:
        return str(0)

    degree = int(math.floor(math.log10(math.fabs(d)) / 3))

    prefix = ''

    if degree != 0:
        ds = degree / math.fabs(degree)
        if ds == 1:
            if degree - 1 < len(inc_prefixes):
                prefix = inc_prefixes[degree - 1]
            else:
                prefix = inc_prefixes[-1]
                degree = len(inc_prefixes)

        elif ds == -1:
            if -degree - 1 < len(dec_prefixes):
                prefix = dec_prefixes[-degree - 1]
            else:
                prefix = dec_prefixes[-1]
                degree = -len(dec_prefixes)

        scaled = float(d * math.pow(1000, -degree))

        s = "{scaled}{sep}{prefix}".format(scaled=scaled,
                                           sep=sep,
                                           prefix=prefix)

    else:
        s = "{d}".format(d=d)

    return s


if __name__ == "__main__":
    import doctest
    doctest.testmod()

and its usage:

from metric import to_si
d = 23392342.1
print(to_si(d))

It will display

23.3923421 M
phoenix
  • 7,988
  • 6
  • 39
  • 45
scls
  • 16,591
  • 10
  • 44
  • 55
  • 1
    Parameter needs testing for zero as [log10](https://en.wikipedia.org/wiki/Logarithm) is undefined, resulting in `ValueError: math domain error`. E.g. : ```` if d == 0.0: degree = 0 else: degree = int(... ```` – handle May 20 '14 at 13:56
  • 1
    I dumped this into an extended `int` class's `__str__` method so it can be used in string formatting output. https://gist.github.com/thorsummoner/aed154cbba0cf9f61182 Example: `import metric; print('>> %s' % metric.MetricInt(2**16));` – ThorSummoner Feb 13 '15 at 09:21
  • really love your function! – user8162 Oct 22 '18 at 18:09
  • for the missing NaN support see https://stackoverflow.com/a/944733/7809404 – user8162 Oct 22 '18 at 18:48
8

Use the QuantiPhy package. It is a stable well documented and well supported package that is designed to do just what you want.

>>> from quantiphy import Quantity

>>> v = Quantity(23.3923421E6)                                               
>>> str(v)                                                                   
'23.392M'                                                                    

>>> v.render(prec='full')                                                    
'23.3923421M'

Generally people use SI unit prefixes with units, and Quantity is designed to combine the units with the number.

>>> v = Quantity(23.3923421E6, 'V')
>>> print(v)
23.392 MV

>>> f = Quantity('23.3923421 MHz')
>>> print('{}'.format(f))
23.392 MHz

Quantity subclasses float, so you can use quantities in expressions as you would a float:

>>> t = 1/f                                                                  
>>> print(t)                                                                 
4.274903281275114e-08

>>> t = Quantity(t, 's')                                                     
>>> print(t)
42.749 ns
August West
  • 351
  • 3
  • 7
5

A direct solution is to use the Decimal.to_eng_string method and then do a dictionary lookup to convert the exponent to appropriate metric prefix.

Raymond Hettinger
  • 216,523
  • 63
  • 388
  • 485
  • How do you get exponent value ? you will have to parse string !! – working4coins Mar 31 '13 at 20:33
  • There are two related questions that could be considered duplicates and should be linked (automatically?). The first uses your solution ( http://stackoverflow.com/questions/12311148/print-number-in-engineering-format ). A duplicate also shows an approach: http://stackoverflow.com/questions/12985438/print-numbers-in-terms-of-engineering-units-in-python – handle May 20 '14 at 12:45
1

You can use Prefixed which has a float type with additional formatting options.

>>> from prefixed import Float

>>> x = Float(23392342.1)
>>> print(f'{x:!h}')
23.392342 M

Or if you want to keep 7 decimal places

>>> print(f'{x:!.7h}')
23.3923421 M

aviso
  • 2,371
  • 1
  • 14
  • 15
0

This is a simple method with no dependencies:

def to_units(x_):
    units = {-12: "T",-9: "G",-6: "M",-3: "K",0: "",3: "m",6: "µ",9: "n",12: "p",15: "f"}
    k = -12
    while x_ * 10.0**k < 1: 
        k += 3
    return f"{x_*10.0**k}{units[k]}"

Example:

for i in range(-15,15):
    print(f"{to_units(1*10.0**i)} \t {1*10.0**i:1,.15f}")

1.0f    0.000000000000001
10.0f   0.000000000000010
100.0f  0.000000000000100
1.0p    0.000000000001000
10.0p   0.000000000010000
100.0p  0.000000000100000
1.0n    0.000000001000000
10.0n   0.000000010000000
100.0n  0.000000100000000
1.0µ    0.000001000000000
10.0µ   0.000010000000000
100.0µ  0.000100000000000
1.0m    0.001000000000000
10.0m   0.010000000000000
100.0m  0.100000000000000
1.0     1.000000000000000
10.0    10.000000000000000
100.0   100.000000000000000
1.0K    1,000.000000000000000
10.0K   10,000.000000000000000
100.0K  100,000.000000000000000
1.0M    1,000,000.000000000000000
10.0M   10,000,000.000000000000000
100.0M  100,000,000.000000000000000
1.0G    1,000,000,000.000000000000000
10.0G   10,000,000,000.000000000000000
100.0G  100,000,000,000.000000000000000
1.0T    1,000,000,000,000.000000000000000
10.0T   10,000,000,000,000.000000000000000
100.0T  100,000,000,000,000.000000000000000
D.Deriso
  • 4,271
  • 2
  • 21
  • 14