• Division-Bug in decimal and mpmath

    From Martin Ruppert@3:633/280.2 to All on Sat Dec 14 18:39:35 2024
    Hi,

    the division 0.4/7 provides a wrong result. It should give a periodic
    decimal fraction with at most six digits, but it doesn't.

    Below is the comparison of the result of decimal, mpmath, dc and calc.

    0.0571428571428571460292086417861615440675190516880580357142857 decimal: 0.4/7 0.0571428571428571460292086417861615440675190516880580357142857 mpmath: 0.4/7 0.0571428571428571428571428571428571428571428571428571428571428 dc: 0.4/7 0.0571428571428571428571428571428571428571428571428571428571429 calc: 0.4/7 0.05714285714285715 builtin: 0.4/7

    Both decimal and mpmath give an identical result, which is not a
    periodic decimal fraction with at most six digits.

    calc and dc provide as well an identical result, which *is* a periodic
    decimal fraction with six digits, so I think that's right.

    Below ist the python-script, with which the computation was done.

    Best regards
    Martin Ruppert

    #!/usr/bin/env python3
    from decimal import Decimal as dec
    from mpmath import *
    from os import popen
    import decimal

    z=.4
    nen=7

    decimal.getcontext().prec=60
    print(dec(z)/dec(nen),end=' ');print(f"decimal: {z}/{nen}")

    mp.dps=60
    a=fdiv(z,nen);print(a,end=' ');print(f"mpmath: {z}/{nen}")

    f=popen(f"dc -e'61k{z} {nen}/f'")
    for i in f:i=i.rstrip()
    f.close()
    print(f"0{i}",end=' ');print(f"dc: {z}/{nen}")

    f=popen(f"calc 'config(\"display\",61);{z}/{nen}'")
    j=0
    for i in f:
    if j>0:i=i.rstrip();print(i,end=' ');print(f"calc: {z}/{nen}")
    j+=1
    f.close()

    print(f"{z/nen}",end=' ');print(f"builtin: {z}/{nen}")

    --
    ruppert@hs-worms.de

    --- MBSE BBS v1.0.8.4 (Linux-x86_64)
    * Origin: A noiseless patient Spider (3:633/280.2@fidonet)
  • From Mark Bourne@3:633/280.2 to All on Sat Dec 14 23:08:29 2024
    Martin Ruppert wrote:
    Hi,

    the division 0.4/7 provides a wrong result. It should give a periodic
    decimal fraction with at most six digits, but it doesn't.

    Below is the comparison of the result of decimal, mpmath, dc and calc.

    0.0571428571428571460292086417861615440675190516880580357142857 decimal: 0.4/7
    0.0571428571428571460292086417861615440675190516880580357142857 mpmath: 0.4/7 0.0571428571428571428571428571428571428571428571428571428571428 dc: 0.4/7 0.0571428571428571428571428571428571428571428571428571428571429 calc: 0.4/7 0.05714285714285715 builtin: 0.4/7

    Both decimal and mpmath give an identical result, which is not a
    periodic decimal fraction with at most six digits.

    calc and dc provide as well an identical result, which *is* a periodic decimal fraction with six digits, so I think that's right.

    I looks like you might be running into limitations in floating-point
    numbers. At least with decimal, calculating 4/70 instead of 0.4/7
    appears to give the correct result. As does:
    ```
    from decimal import Decimal as dec
    z2 = dec(4) / dec(10)
    print(z2 / dec(nen))
    ```
    You can also pass a string, and `dec("0.4")/dec(10)` gives the correct
    result as well.

    Your `z` is a float, and therefore limited by the precision of a float.
    It doesn't represent exactly 0.4, since that can't be exactly
    represented by a float. Anything you do from then on is limited to that precision.

    I can't easily find documentation for dc and calc (links from PyPI are
    either broken or don't exist), but I'm guessing they use some heuristics
    to determine that the float passed in very close to 0.4 so that was
    probably intended, rather than using the exact value represented by that float.

    Below ist the python-script, with which the computation was done.

    Best regards
    Martin Ruppert

    #!/usr/bin/env python3
    from decimal import Decimal as dec
    from mpmath import *
    from os import popen
    import decimal

    z=.4
    nen=7

    decimal.getcontext().prec=60
    print(dec(z)/dec(nen),end=' ');print(f"decimal: {z}/{nen}")

    mp.dps=60
    a=fdiv(z,nen);print(a,end=' ');print(f"mpmath: {z}/{nen}")

    f=popen(f"dc -e'61k{z} {nen}/f'")
    for i in f:i=i.rstrip()
    f.close()
    print(f"0{i}",end=' ');print(f"dc: {z}/{nen}")

    f=popen(f"calc 'config(\"display\",61);{z}/{nen}'")
    j=0
    for i in f:
    if j>0:i=i.rstrip();print(i,end=' ');print(f"calc: {z}/{nen}")
    j+=1
    f.close()

    print(f"{z/nen}",end=' ');print(f"builtin: {z}/{nen}")


    --- MBSE BBS v1.0.8.4 (Linux-x86_64)
    * Origin: A noiseless patient Spider (3:633/280.2@fidonet)
  • From 2QdxY4RzWzUUiLuE@potatochowder.com@3:633/280.2 to All on Sun Dec 15 06:21:06 2024
    On 2024-12-14 at 12:08:29 +0000,
    Mark Bourne via Python-list <python-list@python.org> wrote:

    Martin Ruppert wrote:
    Hi,

    the division 0.4/7 provides a wrong result. It should give a periodic decimal fraction with at most six digits, but it doesn't.

    Below is the comparison of the result of decimal, mpmath, dc and calc.

    0.0571428571428571460292086417861615440675190516880580357142857 decimal: 0.4/7
    0.0571428571428571460292086417861615440675190516880580357142857 mpmath: 0.4/7
    0.0571428571428571428571428571428571428571428571428571428571428 dc: 0.4/7 0.0571428571428571428571428571428571428571428571428571428571429 calc: 0.4/7 0.05714285714285715 builtin: 0.4/7

    Both decimal and mpmath give an identical result, which is not a
    periodic decimal fraction with at most six digits.

    calc and dc provide as well an identical result, which *is* a periodic decimal fraction with six digits, so I think that's right.

    I looks like you might be running into limitations in floating-point
    numbers. At least with decimal, calculating 4/70 instead of 0.4/7 appears
    to give the correct result. As does:
    ```
    from decimal import Decimal as dec
    z2 = dec(4) / dec(10)
    print(z2 / dec(nen))
    ```
    You can also pass a string, and `dec("0.4")/dec(10)` gives the correct
    result as well.

    Your `z` is a float, and therefore limited by the precision of a float. It doesn't represent exactly 0.4, since that can't be exactly represented by a float. Anything you do from then on is limited to that precision.

    I can't easily find documentation for dc and calc (links from PyPI are
    either broken or don't exist), but I'm guessing they use some heuristics to determine that the float passed in very close to 0.4 so that was probably intended, rather than using the exact value represented by that float.

    I'm going to guess that since dc is a shell utility and calc is either
    another shell utility or the calculator in emacs, and that they both do
    their own conversion from a string to an internal representation without
    going through an IEEE float.

    Why couldn't we have evolved with eight fingers on each hand? ;-)

    --- MBSE BBS v1.0.8.4 (Linux-x86_64)
    * Origin: ---:- FTN<->UseNet Gate -:--- (3:633/280.2@fidonet)
  • From Mark Bourne@3:633/280.2 to All on Sun Dec 15 22:59:40 2024
    2QdxY4RzWzUUiLuE@potatochowder.com wrote:
    On 2024-12-14 at 12:08:29 +0000,
    Mark Bourne via Python-list <python-list@python.org> wrote:

    Martin Ruppert wrote:
    Hi,

    the division 0.4/7 provides a wrong result. It should give a periodic
    decimal fraction with at most six digits, but it doesn't.

    Below is the comparison of the result of decimal, mpmath, dc and calc.

    0.0571428571428571460292086417861615440675190516880580357142857 decimal: 0.4/7
    0.0571428571428571460292086417861615440675190516880580357142857 mpmath: 0.4/7
    0.0571428571428571428571428571428571428571428571428571428571428 dc: 0.4/7 >>> 0.0571428571428571428571428571428571428571428571428571428571429 calc: 0.4/7 >>> 0.05714285714285715 builtin: 0.4/7

    Both decimal and mpmath give an identical result, which is not a
    periodic decimal fraction with at most six digits.

    calc and dc provide as well an identical result, which *is* a periodic
    decimal fraction with six digits, so I think that's right.

    I looks like you might be running into limitations in floating-point
    numbers. At least with decimal, calculating 4/70 instead of 0.4/7 appears >> to give the correct result. As does:
    ```
    from decimal import Decimal as dec
    z2 = dec(4) / dec(10)
    print(z2 / dec(nen))
    ```
    You can also pass a string, and `dec("0.4")/dec(10)` gives the correct
    result as well.

    Your `z` is a float, and therefore limited by the precision of a float. It >> doesn't represent exactly 0.4, since that can't be exactly represented by a >> float. Anything you do from then on is limited to that precision.

    I can't easily find documentation for dc and calc (links from PyPI are
    either broken or don't exist), but I'm guessing they use some heuristics to >> determine that the float passed in very close to 0.4 so that was probably
    intended, rather than using the exact value represented by that float.

    I'm going to guess that since dc is a shell utility and calc is either another shell utility or the calculator in emacs, and that they both do
    their own conversion from a string to an internal representation without going through an IEEE float.

    Oh yes. Thinking the question was about 4 different Python packages,
    I'd just looked up the packages with those names on PyPI and hadn't
    noticed they were separate commands being called via popen! In that
    case, the string formatting of a float in the commands defaults to 6
    decimal places, and `z` is rounded back to 0.4 rather than the exact
    value represented by the float. From there, as you say, `dc` and `calc` probably handle that string similarly to `decimal.Decimal` being passed
    the string "0.4".

    Why couldn't we have evolved with eight fingers on each hand? ;-)

    Yeah, it would have made conversions to and from binary somewhat more intuitive...

    --
    Mark.

    --- MBSE BBS v1.0.8.4 (Linux-x86_64)
    * Origin: A noiseless patient Spider (3:633/280.2@fidonet)
  • From Oscar Benjamin@3:633/280.2 to All on Mon Dec 16 08:29:18 2024
    On Sat, 14 Dec 2024 at 19:02, Mark Bourne via Python-list <python-list@python.org> wrote:

    Martin Ruppert wrote:
    Hi,

    the division 0.4/7 provides a wrong result. It should give a periodic decimal fraction with at most six digits, but it doesn't.

    Below is the comparison of the result of decimal, mpmath, dc and calc.
    ....

    I looks like you might be running into limitations in floating-point
    numbers. At least with decimal, calculating 4/70 instead of 0.4/7
    appears to give the correct result. As does:
    ```
    from decimal import Decimal as dec
    z2 = dec(4) / dec(10)
    print(z2 / dec(nen))
    ```
    You can also pass a string, and `dec("0.4")/dec(10)` gives the correct
    result as well.

    For completeness this is how to do it with mpmath:

    from mpmath import mp
    mp.dps = 60
    mp.mpf('0.4') / 7
    mpf('0.0571428571428571428571428571428571428571428571428571428571428549')

    You can also use SymPy:

    from sympy import Rational
    a = Rational('0.4') / 7
    a
    2/35
    a.evalf(60)
    0.0571428571428571428571428571428571428571428571428571428571429

    SymPy uses mpmath for evalf but it allows doing exact calculations
    first and then evaluating the final exact expression to however many
    digits are desired at the end which means that you don't need to
    accumulate rounding errors before calling evalf.

    --
    Oscar

    --- MBSE BBS v1.0.8.4 (Linux-x86_64)
    * Origin: ---:- FTN<->UseNet Gate -:--- (3:633/280.2@fidonet)