Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python decimal.Decimal producing result in scientific notation

I'm dividing a very long into much smaller number. Both are of type decimal.Decimal().

The result is coming out in scientific notation. How do I stop this? I need to print the number in full.

>>> decimal.getcontext().prec
50
>>> val
Decimal('1000000000000000000000000')
>>> units
Decimal('1500000000')
>>> units / val
Decimal('1.5E-15')
like image 601
markling Avatar asked Oct 24 '25 11:10

markling


2 Answers

Render the decimal into a formatted string with a float type-indicator {:,f}, and it will display just the right number of digits to express the whole number, regardless of whether it is a very large integer or a very large decimal.

>>> val
Decimal('1000000000000000000000000')

>>> units
Decimal('1500000000')

>>> "{:,f}".format(units / val)
'0.0000000000000015'

# very large decimal integer, formatted as float-type string, appears without any decimal places at all when it has none! Nice!

>>> "{:,f}".format(units * val)
 '1,500,000,000,000,000,000,000,000,000,000,000'

You don't need to specify the decimal places. It will display only as many as required to express the number, omitting that trail of useless zeros that appear after the final decimal digit when the decimal is shorter than a fixed format width. And you don't get any decimal places if the number has no fraction part.

Very large numbers are therefore accommodated without having to second guess how large they will be. And you don't have to second guess whether they will be have decimal places either.

Any specified thousands separator {:,f} will likewise only have effect if it turns out that the number is a large integer instead of a long decimal.

Proviso

Decimal(), however, has this idea of significant places, by which it will add trailing zeros if it thinks you want them.

The idea is that it intelligently handles situations where you might be dealing with currency digits such as £ 10.15. To use the example from the documentation:

>>> decimal.Decimal('1.30') +  decimal.Decimal('1.20')
Decimal('2.50')

It makes no difference if you format the Decimal() - you still get the trailing zero if the Decimal() deems it to be significant:

>>> "{:,f}".format( decimal.Decimal('1.30') +  decimal.Decimal('1.20'))
'2.50'

The same thing happens (perhaps for some good reason?) when you treat thousands and fractions together:

>>> decimal.Decimal(2500) * decimal.Decimal('0.001')
Decimal('2.500')

Remove significant trailing zeros with the Decimal().normalize() method:

>>> (2500 * decimal.Decimal('0.001')).normalize()
Decimal('2.5')
like image 196
markling Avatar answered Oct 27 '25 02:10

markling


The precision is kept internally - you just have to explicitly call for the number of decimal places you want at the point you are exporting your decimal value to a string.

So, if you are going a print, or inserting the value in an HTML template, the first step is to use the string format method (or f-strings), to ensure the number is encompassed:

In [29]: print(f"{units/val:.50f}")                                                                                                                    
0.00000000000000150000000000000000000000000000000000

Unfortunatelly, the string-format minilanguage has no way to eliminate by itself the redundant zeroes on the right hand side. (the left side can be padded with "0", " ", custom characters, whatever one want, but all the precision after the decimal separator is converted to trailing 0s).

Since finding the least significant non-zero digit is complicated - otherwiser we could use a parameter extracted from the number instead of the "50" for precision in the format expression, the simpler thing is to remove those zeros after formatting take place, with the string .rstrip method:


In [30]: print(f"{units/val:.50f}".rstrip("0"))                                                                                                        
0.0000000000000015

In short: this seems to be the only way to go: in all interface points, where the number is leaving the core to an output where it is representd as a string, you format it with an excess of precision with the fixed point notation, and strip out the tailing zeros with f-string:

return template.render(number=f"{number:.50f}".rstrip("0"), ...)
like image 32
jsbueno Avatar answered Oct 27 '25 00:10

jsbueno



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!