Engineering Notation

Anyone reading this will likely already have a background that covers
engineering notation, so I won't go into it deeply. Computer scientists
might also call engineering notation a 'human-readable' numbers. Instead
of 4096B you might see 4kB or 4.096kB, depending on your precision.

Motivation

Explanation of Decimal Class

The Decimal class is used when you want to do math the way that you
learned to do it in school. Numbers are not represented in the processor
as binary equivalents, they are represented as the exact value. Slower,
but more precise.

The python Decimal class actually has a built-in method to_eng_string()
appears to be written for this purpose, but actually doesn't work strictly
for all numbers, meaning that:

>>> Decimal('1000000').to_eng_string()
1000000

This is not engineering notation! But it is in line with the standard
that is being implemented by Decimal. You have to actually give the
string value in scientific notation OR call the normalize method:

Desired Features

As explained above, the most fundamental features is the ability to resolve
any number into engineering notation the same way that you might on paper.
Some additional features that would add some usefulness:

Practical Rounding Strategy

Source

Actually, using the Decimal class as a starting point works very
well since it implements 90% of the desired operations. All we have to
do is parse input strings for proper suffixes (m, k, etc.) and
replace __repl__ and __str__ with proper parsing to turn a Decimal
into a human-readable format.

_suffix_lookup={'p':'e-12','n':'e-9','u':'e-6','m':'e-3','':'e0','k':'e3','M':'e6','G':'e9','T':'e12'}_exponent_lookup_scaled={'-36':'p','-33':'n','-30':'u','-27':'m','-24':'','-21':'k','-18':'M','-15':'G','-12':'T'}classEngNumber:""" Used for easy manipulation of numbers which use engineering notation """def__init__(self,value:(str,int,float),precision:int=2):""" Initialize the class :param value: string, integer, or float representing the numeric value of the number :param precision: the precision past the decimal - default to 2 """self.precision=precisionifisinstance(value,str):suffix_keys=[keyforkeyin_suffix_lookup.keys()ifkey!='']forsuffixinsuffix_keys:ifsuffixinvalue:value=value[:-1]+_suffix_lookup[suffix]breakself.number=Decimal(value)elifisinstance(value,int)orisinstance(value,float):self.number=Decimal(str(value))def__repr__(self):""" Returns the string representation :return: a string representing the engineering number """# since Decimal class only really converts number that are very small# into engineering notation, then we will simply make all number a# small number and take advantage of Decimal classnum_str=self.number*Decimal('10e-25')num_str=num_str.to_eng_string().lower()base,exponent=num_str.split('e')base=str(round(Decimal(base),self.precision))if'.00'inbase:base=base[:-3]returnbase+_exponent_lookup_scaled[exponent]......

One trick that I used in the __repr__ is to take advantage of the
Decimal class. Since it works well for very small numbers, then
simply multiplying the number by a very small number and looking up
the proper suffix based on the scaled exponent works quite well.

Adding Units

I also created an EngUnit class which builds on the EngNumber class.
It simply allows one to specify units that get parsed correctly. It also
does basic unit checking for addition, subtraction, and comparison: