trealla icon indicating copy to clipboard operation
trealla copied to clipboard

not precise enough float printing

Open Jean-Luc-Picard-2021 opened this issue 2 years ago • 8 comments

Currently I get:

$ ./tpl -v
Trealla Prolog (c) Infradig 2020-2022, v2.2.10
$ ./tpl
?- N9 is 370370367037037036703703703670 / 123456789012345678901234567890.
   N9 = 3.0.
?- N9 is 370370367037037036703703703670 / 123456789012345678901234567890 - 3.
   N9 = 4.440892098500626e-16.
?-

Because the number is not 3.0, I expect something else than 3.0 is printed.

Like here:

/* Jekejeke Prolog 1.5.4 */
?- N9 is 370370367037037036703703703670 / 123456789012345678901234567890.
N9 = 3.0000000000000004.

?- N9 is 370370367037037036703703703670 / 123456789012345678901234567890 - 3.
N9 = 4.440892098500626E-16.

Jean-Luc-Picard-2021 avatar Sep 28 '22 21:09 Jean-Luc-Picard-2021

Strange on my side printing 3.0000000000000004 is not in conflict with printing 1/10. I still get:

/* Jekejeke Prolog 1.5.4 */
?- X is 1/10.
X = 0.1.

I don't know how Java does it. I guess they neither use 17 nor 18 digits, but switch between the two, when possible. Similarly no problem in JavaScript:

/* Dogelog Player 1.0.2, JavaScript */
?- N9 is 370370367037037036703703703670 / 123456789012345678901234567890.
N9 = 3.0000000000000004.

?- X is 1/10.
X = 0.1.

But in Python I use a more precise (/)/2, so the results are there as follows:

/* Dogelog Player 1.0.2, Python */
?- N9 is 370370367037037036703703703670 / 123456789012345678901234567890.
N9 = 3.0.

?- N9 is 370370367037037036703703703670 / 123456789012345678901234567890 - 3.
N9 = 0.0.

?- N9 is float(370370367037037036703703703670) / float(123456789012345678901234567890) - 3.
N9 = 4.440892098500626E-16.

?- X is 1/10.
X = 0.1.

Maybe I should give JavaScript and Java also a more precise (/)/2 ? Didn't test yet whether Python is not only precise, but also shows good rounding in their (/)/2.

Jean-Luc-Picard-2021 avatar Sep 28 '22 22:09 Jean-Luc-Picard-2021

Another problem with 18...

$ tpl ?- X is 1/10. X = 0.10000000000000001. ?- X is 1/3. X = 0.33333333333333331.

It seems the last digit is subject to error so should not really be printed.

$ scryer-prolog -f ?- X is 1/3. X = 0.3333333333333333.

infradig avatar Sep 28 '22 22:09 infradig

Well usually people use the predicate format/2 if they want to see less, don't they? And for indempodent input/output the predicate write/1 needs to show more?

format(+Format, :Arguments) e Output next argument as a floating point number in exponential notation. The numeric argument specifies the precision. Default is 6 digits. Exact representation depends on the C library function printf(). This function is invoked with the format %.e. f Floating point in non-exponential notation. The numeric argument defines the number of digits right of the decimal point. If the colon modifier (:) is used, the float is formatted using conventions from the current locale, which may define the decimal point as well as grouping of digits left of the decimal point. https://www.swi-prolog.org/pldoc/man?predicate=format/2

Jean-Luc-Picard-2021 avatar Sep 28 '22 22:09 Jean-Luc-Picard-2021

Concerning your examples I get, these are all systems that can also show 3.0000000000000004:

/* Java,  JavaScript and Python */
?- X is 1/3.
X = 0.3333333333333333.

For example in JavaScript I use this routine:

        let res = norm_float_string(num.toPrecision());
        if (res.lastIndexOf("E") === -1) {
            num = norm_float_string(num.toExponential());
            if (num.length < res.length)
                res = num;
        }

The methods toPrecision() and toExponential() are called without specifying a fraction length, the routines decide by themselves whether 17 or 18 should be used.

I don't know what tricks or libraries are around in C or C++ to archive the same.

Jean-Luc-Picard-2021 avatar Sep 28 '22 22:09 Jean-Luc-Picard-2021

Yeah, I was thinking of something like that. I might ponder a bit.

On Thu, Sep 29, 2022 at 8:30 AM Jean-Luc-Picard-2021 < @.***> wrote:

Concerning your examples I get, all systems that can also show 3.0000000000000004:

/* Java, JavaScript and Python */ ?- X is 1/3. X = 0.3333333333333333.

For example in JavaScript I use this routine:

    let res = norm_float_string(num.toPrecision());
    if (res.lastIndexOf("E") === -1) {
        num = norm_float_string(num.toExponential());
        if (num.length < res.length)
            res = num;
    }

The methods toPrecision() and toExponential() are called without specifying a fraction length, the routines decide by themselves whether 17 or 18 should be used.

— Reply to this email directly, view it on GitHub https://github.com/trealla-prolog/trealla/issues/49#issuecomment-1261528238, or unsubscribe https://github.com/notifications/unsubscribe-auth/AFNKSEWYAJYEEYGZYXO3RGDWATBHHANCNFSM6AAAAAAQYGDQ4Y . You are receiving this because you commented.Message ID: @.***>

infradig avatar Sep 28 '22 22:09 infradig

Ok, I've had a go at it, see devel branch.

infradig avatar Sep 28 '22 23:09 infradig

BTW: SWI-Prolog is full of bugs, I get this strange result, a bigger numerator, leads to a smaller result?

/* SWI-Prolog 8.5.17 */
?- N9 is 370370367037037036703703703670 / 123456789012345678901234567890.
N9 = 3.

?- N9 is 370370367037037036703703703671 / 123456789012345678901234567890.
N9 = 2.9999999999999996.

Python doesn’t have this bug:

/* Python 3.11.0rc1 (main, Aug  8 2022, 11:30:54) */
>>> 370370367037037036703703703670 / 123456789012345678901234567890
3.0
>>> 370370367037037036703703703671 / 123456789012345678901234567890
3.0

Jean-Luc-Picard-2021 avatar Sep 28 '22 23:09 Jean-Luc-Picard-2021

Yes, and it's not just a printing thing there with swipl. Direct arithmetic comparison is incorrect.

On Thu, Sep 29, 2022 at 9:03 AM Jean-Luc-Picard-2021 < @.***> wrote:

BTW: SWI-Prolog is full of bugs, I get this strange result, a bigger numerator, leads to a smaller result?

/* SWI-Prolog 8.5.17 */ ?- N9 is 370370367037037036703703703670 / 123456789012345678901234567890. N9 = 3.

?- N9 is 370370367037037036703703703671 / 123456789012345678901234567890. N9 = 2.9999999999999996.

— Reply to this email directly, view it on GitHub https://github.com/trealla-prolog/trealla/issues/49#issuecomment-1261550979, or unsubscribe https://github.com/notifications/unsubscribe-auth/AFNKSEREIZWCT3JBJPJJ3JLWATFCLANCNFSM6AAAAAAQYGDQ4Y . You are receiving this because you commented.Message ID: @.***>

infradig avatar Sep 28 '22 23:09 infradig

This is also a nice test case:

/* Scryer, SWI-Prolog and Jekejeke */
?- X is 0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1.
   X = 0.7999999999999999.

/* Trealla 2.7.15 */
?- X is 0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1.
   X = 0.79999999999999993.

I think 18 digits might be too much?

Jean-Luc-Picard-2021 avatar Jan 04 '23 09:01 Jean-Luc-Picard-2021

Now its overshooting in the other direction. Was expecting that when it shows 0.8, I can also subtract 0.8 and get zero?

Trealla Prolog (c) Infradig 2020-2023, v2.9.4
?- X is 0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1.
   X = 0.8.
?- X is 0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1-0.8.
   X = -1.1102230246251565e-16.

Jean-Luc-Picard-2021 avatar Feb 15 '23 21:02 Jean-Luc-Picard-2021

Thanks to format/2 can investigate the case in more detail. The number seems to be a challenge for the "g" format:

/* Java, JavaScript and Python */
?- X is 370370367037037036703703703670 / 123456789012345678901234567890,
    format('~16g~n', [X]).
3

?- X is 370370367037037036703703703670 / 123456789012345678901234567890,
    format('~17g~n', [X]).
3.0000000000000004

But the other test case is not so much affected, except for a difference in Java:

/* Java, JavaScript and Python */
?- X is 0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1, format('~16g~n', [X]).
0.7999999999999999

/* JavaScript and Python */
?- X is 0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1, format('~17g~n', [X]).
0.79999999999999993

/* Java */
?- X is 0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1, format('~17g~n', [X]).
0.7999999999999999

Jean-Luc-Picard-2021 avatar Apr 15 '23 19:04 Jean-Luc-Picard-2021

Since Trealla Prolog can produce the same:

/* Trealla Prolog 2.14.32 */
?- X is 370370367037037036703703703670 / 123456789012345678901234567890,
    format('~16g~n', [X]).
3

?- X is 370370367037037036703703703670 / 123456789012345678901234567890,
    format('~17g~n', [X]).
3.0000000000000004

?- X is 0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1, format('~16g~n', [X]).
0.7999999999999999

?- X is 0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1, format('~17g~n', [X]).
0.79999999999999993

Maybe there is no harm in that the top-level doesn't show that much precision.

So I am closing this ticket.

Jean-Luc-Picard-2021 avatar Apr 15 '23 20:04 Jean-Luc-Picard-2021

Maybe I hit the jackpot now, with this adaptive algorithm in JavaScript:

        let res = num.toPrecision(16);
        if (Number(res) === num) {
            return shape_number(res);
        } else {
            return shape_number(num.toPrecision(17));
        }

It now shows me, before the adaptive algorithm, it was full of nasty 2.2999999999999998, 2.7000000000000002 etc..:

?- between(1,10,N), X is (20+N)/10, write(X), nl, fail; true.
2.1
2.2
2.3
2.4
2.5
2.6
2.7
2.8
2.9
3.0

And these test cases are also fine I guess:

?- X is 370370367037037036703703703670 / 123456789012345678901234567890.
X = 3.0000000000000004.

?- X is 0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1.
X = 0.7999999999999999.

Jean-Luc-Picard-2021 avatar Jul 28 '24 12:07 Jean-Luc-Picard-2021

Scryer Prolog v0.9.4-107-geaa95293 agrees with the last two results. But Trealla Prolog v2.55.10, doesn't agree with the last two results, it still shows some bogus floating point number:

?- X is 370370367037037036703703703670 / 123456789012345678901234567890.
   X = 3.0.

?- X is 370370367037037036703703703670 / 123456789012345678901234567890 - 3.0.
   X = 4.4408920985006262e-16.

?- X is 0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1.
   X = 0.8.

?- X is 0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1 - 0.8.
   X = -1.1102230246251565e-16.

What I don't know is, whether the new adaptive algorithm has a high performance price. It is more expensive than just calling num.toPrecision(17). It will in mimimum call num.toPrecision(16)

and do the back conversion, i.e. Number(res). So unparsing has a parsing cost. And for critical numbers, it has a second unparsing via num.toPrecision(17) cost.

The division test case disagrees in recent SWI-Prolog, since in recent versions it has a more precise (/)/2 arithmetic operation implementation.

Jean-Luc-Picard-2021 avatar Jul 28 '24 12:07 Jean-Luc-Picard-2021

Yeah, floating-point is a pain.

infradig avatar Jul 28 '24 13:07 infradig

I guess the problem is that going from binary to decimal, the notion of precision becomes a little bit fuzzy.

You cannot directly transate a binary precision measured in bits basically a range from 0 to 2^N for some N, to a decimal a decimal precision, basically a range from 0 to 10^N for some N.

When expanding a binary number to decimal, sometimes N=16 is enough, there is no ambigiuity in that we hit a neighbouring binary number, and sometimes we need N=17.

Jean-Luc-Picard-2021 avatar Jul 28 '24 13:07 Jean-Luc-Picard-2021

GNU Prolog seems to still use the non-adaptive algorithm with 17 decimal precision. It could profit from the adaptive algorithm that arbitrates between 16 and 17 decimal precision:

/* GNU Prolog 1.5.0 */

?- X is 0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1.
X = 0.79999999999999993

?- 0.79999999999999993 == 0.7999999999999999.
Yes

?- X is 23/10.
X = 2.2999999999999998

?- 2.2999999999999998 == 2.3.
Yes

All discrepancies are not incorrect displays, since reparsing decimal numbers shows that they hit the same floating point values. But 0.7999999999999999 and 2.3

would be the more shorter display.

Jean-Luc-Picard-2021 avatar Jul 28 '24 14:07 Jean-Luc-Picard-2021