0

is there a way to inhibit python rounding, e.g.:

>>> getcontext().prec = 11; print (Decimal(1.1237836550999999999)*10**10)
11237836551

I need to show the first 10 decimal places of the decimal number, so actually I need 11237836550 as output and I do not need that rounding on the last digit (in this case that 0 rounded to a 1).

Sometimes I can just increase the precision (not in this case) and then perform the calculation safely, like this:

>>> getcontext().prec = 14; print (Decimal(1.12378365509978)*10**10)
11237836550.998

But suppose that you are not aware of the decimal part of the number after that '0', e.g. it could be 1.1237836550999999999999999999999999... and so on, and then you cannot rely upon this 'workaround'.

So, is there a way to tell python, "for this calculation, I don't want any rounding to be performed, actually I want that you just return me the needed number of decimals (10 in this example), and throw away anything else after it"?

I took a look here Truncating floats in Python, but basically what I need is not a string of a truncated float, but another decimal to use to perform calculations upon. Furthermore, the trunc function of the accepted answer in the post I linked will not work in this case, cause the Decimal will be rounded just before it will be passed to the function's 'f' argument:

>>> def trunc(f, n):
...     '''Truncates/pads a float f to n decimal places without rounding'''
...     return ('%.*f' % (n + 1, f))[:-1]
... 
>>> trunc(Decimal(1.12378365509978), 10)
'1.1237836551' # and not 1.1237836550 as needed

How can I accomplish that?

Community
  • 1
  • 1
tonix
  • 6,671
  • 13
  • 75
  • 136

2 Answers2

7

You problem is not Decimal. It's float. Namely:

>>> 1.1237836550999999999
1.1237836551

But if you do:

>>> int(Decimal("1.1237836550999999999")*10**10)
11237836550

You are good to go. Use strings when initializing decimals with higher precision.

Stefan Gruenwald
  • 2,582
  • 24
  • 30
dmg
  • 7,438
  • 2
  • 24
  • 33
  • Good, thank you, does it works always, even if I have a number with infinite 9s? like 1.1237836550999999999999999999.........? – tonix Jan 23 '15 at 08:50
  • It doesn't seem to work for `>>> getcontext().prec = 11; print (int(Decimal(1.123783655099999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999)*10**10))` it still outputs `1.1237836551` – tonix Jan 23 '15 at 08:52
  • 1
    @user3019105 you are still missing the quotes. Use a string. If you write it as you have, it's converted to the machine-precision floating point number before passing to the Decimal constructor; double precision floats have around ~15 decimal places of precision, so all the 9's you wrote down are just useless. – orion Jan 23 '15 at 08:56
  • Got it, thanks! Now it works, but what is the purpose of the int() here, it seems to work even without it: `print (Decimal('1.12378365509999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999')*10**10)` outputs 11237836550 – tonix Jan 23 '15 at 08:58
  • @user3019105 Note that I'm using the python shell in my examples. If you want to use this in a script which prints to the standard output, you will need `print` – dmg Jan 23 '15 at 09:00
  • It works, what about the int()? What's it's purpose? – tonix Jan 23 '15 at 09:03
  • `print Decimal("1.1237836550999999999")*10**10` outputs, 11237836550.99999999900000000. Using `int` you will strip the non-integer part. – dmg Jan 23 '15 at 09:05
  • All right, thank you for you help, just the last question, how can I make it work while having an expression? I can do e.g.: `>>> getcontext().prec = 41; print (Decimal( str( Decimal(2).sqrt() + Decimal(5) )))` which outputs `6.4142135623730950488016887242096980785697` but I need to output `6.4142135623730950488016887242096980785696`, just to know how can I use this tricks with an expression with variables? – tonix Jan 23 '15 at 09:18
  • @user3019105 `getcontext().prec = 41 + 1; print str(Decimal( str( Decimal(2).sqrt() + Decimal(5) )))[:-1]` seems to do the trick – dmg Jan 23 '15 at 09:46
  • It works with 41 + 1, but not for 40 + 1, i.e. for the first 39 decimal places to be right, `getcontext().prec = 40 + 1; print (str(Decimal( str( Decimal(2).sqrt() + Decimal(5) ))))` outputs `6.4142135623730950488016887242096980785697` while the correct one would be `6.4142135623730950488016887242096980785696`, is there a way in such a case? – tonix Jan 23 '15 at 09:55
2

Is this what you are finding:

from decimal import *

getcontext().prec = 103

def precision(context, prec=100):

    return Decimal(context).quantize(Decimal('.'+'0'*(prec-1)+'1'), rounding=ROUND_DOWN)

print precision(Decimal(10).log10() / Decimal(2).log10())

Output:

3.3219280948873623478703194294893901758648313930245806120547563958159347766086252158501397433593701550
dragon2fly
  • 2,309
  • 19
  • 23
  • This seems a more reliable solution, doesn't it? – tonix Jan 23 '15 at 08:56
  • How can I use quantize with a variable result, i.e. the result of an expression, `Decimal(math.sqrt(2) + 5).quantize('.00000000001')` gives a `TypeError: conversion from str to Decimal is not supported` – tonix Jan 23 '15 at 09:09
  • 1
    @user3019105 Inside `quantize` is `Decimal`, don't forget it! `In[5]: Decimal(math.sqrt(2) + 5).quantize(Decimal('.00000000001'), rounding=ROUND_DOWN)` will give your output without Error `Out[5]: Decimal('6.41421356237')` – dragon2fly Jan 23 '15 at 09:24
  • How can I use `.quantize()` with an expression like: `Decimal(Decimal(2).sqrt() + Decimal(5)).quantize(Decimal('.0000000000000000000000000000000000000001'))` which outputs `Decimal('6.4142135623730950488016887242096980785697')` but the expected output would be `Decimal('6.4142135623730950488016887242096980785696')` i.e. the last decimal place has a 6 and not 7? – tonix Jan 23 '15 at 09:58
  • 1
    You need to specify the rounding method, as shown in the answer: `(Decimal(2).sqrt() + Decimal(5)).quantize(Decimal('.0000000000000000000000000000000000000001'), rounding=ROUND_DOWN)` – mata Jan 23 '15 at 10:10
  • @user3019105 In my computer, I cannot get so many number after `.` like your. It only give me 27 digit, so I cannot test your case. But as @mata said, don't forget `rounding=ROUND_DOWN`, it should do the job. I edited my answer so that you don't have to trouble your eyes with dozens of `0` in `quantize` – dragon2fly Jan 23 '15 at 10:36
  • @mat, @dragonfly, with ROUND_DOWN quantize works fine for the sqrt of 2 + 5, but I have also tested it with this expression: `getcontext().prec = 101; (Decimal(Decimal(10).log10() / Decimal(2).log10())).quantize(Decimal('.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001'), rounding=ROUND_DOWN)`, which is the logarithm base 2 of 10 with its first `100` decimal places. Even with ROUND_DOWN it outputs...(continue in the comment below) – tonix Jan 23 '15 at 11:02
  • it outputs `Decimal('3.3219280948873623478703194294893901758648313930245806120547563958159347766086252158501397433593701551')` while the exact output is... `Decimal('3.3219280948873623478703194294893901758648313930245806120547563958159347766086252158501397433593701550')`, as the 100th decimal of log(10)/log(2) is `0`, what should be done in such a case? – tonix Jan 23 '15 at 11:03
  • @user3019105 You should give `getcontext().prec` a number that a little bit higher than the precision that you want, (eg. `103`) then it should work. – dragon2fly Jan 23 '15 at 11:54
  • @dragon2fly Yeah, increasing the precision works, but is it reliable even if e.g. the number had an infinite series of *9s* after that '0'? I know it looks maniacal, but I need as much as possible a universal solution, can I rely upon setting the precision just a little bit higher and be sure that the number will be that one and not a rounded one even if the decimal part after it has a series of infinite 9s? Does ROUND_DOWN help here? – tonix Jan 23 '15 at 12:02
  • 1
    @user3019105 set `getcontext().prec = 200`, try to put `prec=100` and then `prec=110` with your expression above. With `prec=110` it gives you `...55099...` and with `prec=100` it gives `...550` . So I think it would be reliable regardless of how many and what digits after the `pre`th digit are – dragon2fly Jan 23 '15 at 12:22