Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Print number in engineering format

I am trying to print a number into engineering format with python, but I cannot seem to get it to work. The syntax SEEMS simple enough, but it just doesn't work.

>>> import decimal 
>>> x = decimal.Decimal(1000000)
>>> print x
1000000
>>>> print x.to_eng_string() 
1000000

I cannot figure out why this is. The two values are not equal (one is a string, the other is an int). Setting various contexts in decimal doesn't seem to help either. Any clues or ideas?

like image 300
jmurrayufo Avatar asked Sep 07 '12 03:09

jmurrayufo


People also ask

What is engineering number format?

Engineering format is an output display option. This option is very similar to the Exponential format in which a value is displayed as a number followed by E and the integer exponent value, e.g., 1.234E06. In Engineering format, the exponent value will always be an integer multiple of 3, i.e., E0, E3, E6, etc.

What is %s in print?

%s acts a placeholder for a string while %d acts as a placeholder for a number. Their associated values are passed in via a tuple using the % operator. name = 'marcog' number = 42 print '%s %d' % (name, number) will print marcog 42 . Note that name is a string (%s) and number is an integer (%d for decimal).


6 Answers

To get this to work, you have to normalize the decimal first:

>>> x = decimal.Decimal ('10000000')

>>> x.normalize()
Decimal('1E+7')

>>> x.normalize().to_eng_string()
'10E+6'

The reason for this can be discovered by delving in to the source code.

If you examine to_eng_string() in the Python 2.7.3 source tree (Lib/decimal.py from the gzipped source tar ball here), it simply calls __str__ with eng set to true.

You can then see that it decides on how many digits go to the left of the decimal initially with:

leftdigits = self._exp + len(self._int)

The following table shows what the values are for those two things:

                         ._exp       ._int         len   leftdigits
                         -----       ---------     ---   ----------
Decimal (1000000)            0       '1000000'       7            7
Decimal ('1E+6')             6       '1'             1            7

The code that continues after that is:

if self._exp <= 0 and leftdigits > -6:
    # no exponent required
    dotplace = leftdigits
elif not eng:
    # usual scientific notation: 1 digit on left of the point
    dotplace = 1
elif self._int == '0':
    # engineering notation, zero
    dotplace = (leftdigits + 1) % 3 - 1
else:
    # engineering notation, nonzero
    dotplace = (leftdigits - 1) % 3 + 1

and you can see that, unless it already has an exponent in a certain range (self._exp > 0 or leftdigits <= -6), none will be given to it in the string representation.


Further investigation shows the reason for this behaviour. Looking at the code itself, you'll see it's based on the General Decimal Arithmetic Specification (PDF here).

If you search that document for to-scientific-string (on which to-engineering-string is heavily based), it states in part (paraphrased, and with my bold bits):

The "to-scientific-string" operation converts a number to a string, using scientific notation if an exponent is needed. The operation is not affected by the context.

If the number is a finite number then:

The coefficient is first converted to a string in base ten using the characters 0 through 9 with no leading zeros (except if its value is zero, in which case a single 0 character is used).

Next, the adjusted exponent is calculated; this is the exponent, plus the number of characters in the converted coefficient, less one. That is, exponent+(clength-1), where clength is the length of the coefficient in decimal digits.

If the exponent is less than or equal to zero and the adjusted exponent is greater than or equal to -6, the number will be converted to a character form without using exponential notation. In this case, if the exponent is zero then no decimal point is added. Otherwise (the exponent will be negative), a decimal point will be inserted with the absolute value of the exponent specifying the number of characters to the right of the decimal point. “0” characters are added to the left of the converted coefficient as necessary. If no character precedes the decimal point after this insertion then a conventional “0” character is prefixed.

In other words, it's doing what it's doing because that's what the standard tells it to do.

like image 103
paxdiablo Avatar answered Oct 03 '22 20:10

paxdiablo


I realize that this is an old thread, but it does come near the top of a search for python engineering notation.

I am an engineer who likes the "engineering 101" engineering units. I don't even like designations such as 0.1uF, I want that to read 100nF. I played with the Decimal class and didn't really like its behavior over the range of possible values, so I rolled a package called engineering_notation that is pip-installable.

pip install engineering_notation

From within Python:

>>> from engineering_notation import EngNumber
>>> EngNumber('1000000')
1M
>>> EngNumber(1000000)
1M
>>> EngNumber(1000000.0)
1M
>>> EngNumber('0.1u')
100n
>>> EngNumber('1000m')
1

This package also supports comparisons and other simple numerical operations.

https://github.com/slightlynybbled/engineering_notation

like image 21
slightlynybbled Avatar answered Oct 03 '22 22:10

slightlynybbled


It seems to me that you're going to have to roll your own:

from math import log10
def eng_str(x):
    y = abs(x)
    exponent = int(log10(y))
    engr_exponent = exponent - exponent%3
    z = y/10**engr_exponent
    sign = '-' if x < 0 else ''
    return sign+str(z)+'e'+str(engr_exponent)

Although you may want to take a little more care in the formatting of the z portion...

not well tested. Feel free to edit if you find bugs

like image 29
mgilson Avatar answered Oct 03 '22 21:10

mgilson


There is a solution using the built-in printf-style String Formatting

I will assume that (forward in time) you now use the built-in python 3, as all things formatting are greatly simplifed and normalized (I recommend to bookmark this cheat sheet.

In [1]: myfloat = 0.000000001
In [2]: print(f'{myfloat:g}')
1e-09

the older syntax (which would work with python 2) is:

In [17]: '%g' % myfloat
Out[17]: '1e-09'

Another nice format is given by:

In [3]: print(f'{myfloat:E}')
1.000000E-09

for which you ccan have some control over the output:

In [4]: print(f'{myfloat:.3E}')
1.000E-09
like image 42
meduz Avatar answered Oct 03 '22 22:10

meduz


QuantiPhy is another package that can help you with this. QuantiPhy provides the Quantity class that is used to combine a value and units into one object. It can format the quantity in a variety of ways. Generally people use SI scale factors, but engineering form is also supported. Install with:

pip3 install quantiphy

You can create a Quantity object starting from a number or a string:

>>> from quantiphy import Quantity
>>> q = Quantity(10000000)
>>> q.render(form='eng')
'10e6'

By default QuantiPhy uses SI notation, but you can set the default form to 'eng':

>>> print(q)
10M
>>> Quantity.set_prefs(form='eng')
>>> print(q)
10e6

Normally you would specify units to a quantity:

>>> f = Quantity(10_000_000, 'Hz')
>>> print(f)
10e6 Hz

Quantity subclasses float, so you can use a quantity anywhere you would use a float.

QuantiPhy is a powerful package that is well supported and well documented. I encourage you to give it a try.

like image 29
August West Avatar answered Oct 03 '22 22:10

August West


matplotlib can be used also:

>>> from matplotlib.ticker import EngFormatter
>>> engFormat = EngFormatter()
>>> print(engFormat(1000))
1 k

I let you take a look for further details and options at https://matplotlib.org/stable/api/ticker_api.html Another more complex example:

>>> from matplotlib.ticker import EngFormatter
>>> engFormat = EngFormatter(unit='Hz',places=2,sep='')
>>> print(engFormat(1000))
1.00kHz
like image 1
EricF Avatar answered Oct 03 '22 22:10

EricF