0

I cannot get mpmath to work as expected. Here is my code:


    import mpmath as mp
    from mpmath import *
    
    mp.dps=31
    
    pi_30=mpf('3.141592653589793238462643383279')
    
    for i in range(20):
        print(round(pi_30,i))

It generates:

• 3.0 • 3.1 • 3.14 • 3.142 • 3.1416 • 3.14159 • 3.141593 • 3.1415927 • 3.14159265 • 3.141592654 • 3.1415926536 • 3.14159265359 • 3.14159265359 • 3.1415926535898 • 3.14159265358979 • 3.141592653589793 • 3.141592653589793 • 3.141592653589793 • 3.141592653589793 • 3.141592653589793

Why does the code generate not more then 15 digits of Pi? Seems like I am in float territory despite using mpmath...what am I doing wrong?

Intregal
  • 1
  • 2
  • `round` will create an ordinary Python `float`. It is **not** a tool for controlling how many digits appear in the displayed output. (In fact, it is very rarely useful for anything.) If you want to control how many digits are output by `print`, then you should use string formatting tools. – Karl Knechtel May 10 '22 at 13:29
  • I don't understand the utility of mpmath, a float wouldn't work ? – Marius ROBERT May 10 '22 at 13:30
  • See also https://stackoverflow.com/questions/55727214/ and https://stackoverflow.com/questions/588004. @MariusROBERT the point of the library is to represent values with greater precision than `float` admits. – Karl Knechtel May 10 '22 at 13:32
  • @KarlKnechtel Using string formatting tools for solving mathematical problems (here: rounding) seems a strange way, but if it is Pythons way ok then. Any hint how to do it? – Intregal May 10 '22 at 13:43
  • I missed that you actually want the last digit to round up or down depending on the remainder of the value. String formatting tools will truncate the value. If you simply want to represent arbitrary-precision decimal data and aren't really concerned with number crunching, the `decimal.Decimal` class is the way to go as described in the first answer. – Karl Knechtel May 10 '22 at 13:48
  • With the * import I thought you were using a `mp.round`, but it turns out there isn't such a thing. This is the python `round`. It doesn't make sense to expect a python functoin, that returns a python float, to show more digits. – hpaulj May 10 '22 at 14:23
  • @KarlKnechtel, from what I can tell, `mpmath` with either `dps` or `nstr` handles "rounding" just as well as the python `round` function. – hpaulj May 10 '22 at 15:56

3 Answers3

0

You can try using the decimal.Decimal type:

import decimal    
    
pi_30=decimal.Decimal('3.141592653589793238462643383279')
    
for i in range(20):
    print(round(pi_30,i))

Output:

3
3.1
3.14
3.142
3.1416
3.14159
3.141593
3.1415927
3.14159265
3.141592654
3.1415926536
3.14159265359
3.141592653590
3.1415926535898
3.14159265358979
3.141592653589793
3.1415926535897932
3.14159265358979324
3.141592653589793238
3.1415926535897932385
lemon
  • 14,875
  • 6
  • 18
  • 38
  • I already used Decimal successfully, I wanted to recreate my code with mpmath (though have to say that I am a Python beginner, not always looking for the best solution but to solve problems with restricted tools just for learning to code) – Intregal May 10 '22 at 13:42
0

This looks like a bug, since round() should support this, but I am sure there's a reason for this behaviour (implicit conversion to float) to work with standard Python objects.

To print limited number of digits you can still use nstr()

for i in range(30):
     print(mp.nstr(pi_30,i))
Luka Rahne
  • 10,336
  • 3
  • 34
  • 56
  • `round` is a Python function returning a Python float. We can't expect it to treat a `mpmath` object in a `mpmath` way. – hpaulj May 10 '22 at 14:50
  • @hpaulj from documentation I would expect that round() works on anything that is _number_ (but I do not have more info about how number is defined). It does not require that return type is float. Edit: It states - The return value is an integer if ndigits is omitted or None. Otherwise, the return value has the same type as number. – Luka Rahne May 10 '22 at 16:04
  • That documenation paragraph applies to "For the built-in types supporting round()". `mpmath` is not a builtin type. For that this applies: "For a general Python object number, round delegates to number.__round__." `mpmath.__round__` returns `round(float(self), *args)` – hpaulj May 10 '22 at 16:33
  • @hpaulj This is bug i believe. `mpmath.__round__` should return mpf. I don't see a reason that it returns float. – Luka Rahne May 10 '22 at 16:44
  • You can certainly raise that issue with `mpmath`. But when I tested `mpmath's` own decimal display, I didn't see any differences from what `round` produces. So it may be a non-issue. – hpaulj May 10 '22 at 16:47
  • @hpaulj Issue is as reported by Intregal in his question. That is that round() is limited by accuracy of float() and is not possible to round on more than approx 16 digits. – Luka Rahne May 10 '22 at 16:49
  • I found an old open issue on rounding, https://github.com/fredrik-johansson/mpmath/issues/455 – hpaulj May 10 '22 at 18:12
0

(skipping the * import)

In [19]: import mpmath as mp

For some reason, directly setting dps isn't working:

In [20]: mp.dps=31
In [21]: print(mp.mp)
Mpmath settings:
  mp.prec = 53                [default: 53]
  mp.dps = 15                 [default: 15]
  mp.trap_complex = False     [default: False]

(solved it. With a mp import I need to set mp.mp.dps=31)

But I can set it in a context. Creating the variable with the full desire decimals:

In [22]: with mp.workdps(31):
    ...:     pi_30=mp.mpf('3.141592653589793238462643383279')
    ...:     print(pi_30)
    ...: 
3.141592653589793238462643383279

I can then display it with various dps using:

In [23]: for i in range(20):
    ...:     with mp.workdps(i):
    ...:         print(pi_30)
    ...: 
3.0
3.0
3.1
3.14
3.142
3.1416
3.14159
3.141593
3.1415927
3.14159265
3.141592654
3.1415926536
3.14159265359
3.14159265359
3.1415926535898
3.14159265358979
3.141592653589793
3.1415926535897932
3.14159265358979324
3.141592653589793238

round is a python function, returning a python float, so it can't display more than 15, even if the input is mpmath.

mp.nstr also works

In [29]: mp.nstr(pi_30,15)
Out[29]: '3.14159265358979'
In [30]: mp.nstr(pi_30,26)
Out[30]: '3.1415926535897932384626434'

My values match the ones you get with python round, so I'll skip any further discussion of rounding nuances. Look in mpmath docs for more information on the relation between binary values and decimal displays.

Regarding why round() is limited to python floats, here's the mpmath definition:

In [45]: pi_30.__round__??
Signature: pi_30.__round__(*args)
Docstring: <no docstring>
Source:   
    def __round__(self, *args):
        return round(float(self), *args)
File:      /usr/local/lib/python3.8/dist-packages/mpmath/ctx_mp_python.py
Type:      method

It is explicitly converting the mp value to float, and then applying the python round.

https://github.com/fredrik-johansson/mpmath/issues/455 is an old open issue regarding rounding.

hpaulj
  • 221,503
  • 14
  • 230
  • 353