strange arithmetic with swi-prolog [duplicate]

2019-09-20 14:20发布

问题:

This question already has an answer here:

  • Is floating point math broken? 30 answers

I find the result very strange. Why not 0.3? Can somebody tell me why this result? Is it possible to fix this.

 ?- X is 5.3-5.
    X = 0.2999999999999998.

    ?- 

My second question is how would I transform from 'hour' notation '13.45' ---->'15.30' into numbers of hours ? For example the period above calculated 15.30-13.45 would be 1.85. But I need to operate on parts of the hour and not the remainders of the numbers. Like 15 1/2 - 13 /4, this way is better. I try

?- X is (5.3-5)*100/60.
X = 0.4999999999999997.

?- X is (5.3-5)*100//60.
ERROR: ///2: Type error: `integer' expected, found `29.999999999999982'

Any suggestions?

回答1:

The answer you get from SWI is correct.
In many programming languages, and practically all Prolog systems, floating point implementations are based on a binary (radix = 2) system. The number 5.3 cannot be represented precisely in this system, so some approximation is chosen. Subtracting is particularly well suited to expose such inaccuracies.

?- X is 5.3-5-0.3.
X = -1.6653345369377348e-16.

As long as you are staying with numbers that are the sum of powers of 2 (including 2-1, 2-2, ...) you will get precise results as far as the precision of the floats behind permits it:

?- X is 5.000244140625-5-0.000244140625.
X = 0.0.

What ISO conforming Prolog systems have to do is to ensure that when writing a float with write-option quoted(true), the float is written in such a manner that it can be read back precisely.

As to your second question: (//)/2 is defined on integers only. If you want to convert a float to an integer you have in ISO Prolog the usual LIA-1 functions:

floor/1, truncate/1, round/1, ceiling/1.

?- X is round(5.3).
X = 5.

However, I'd rather recommend using (div)/2 in place of (//)/2. The meaning of (//)/2 is no longer supported by LIA-1:2012 (Standard ISO/IEC 10967-1:2012) — for good reason. See this and this answer for details.



回答2:

Regarding this line:

X is (5.3-5)*100//60.

The // predicate is specifically integer divide and you're operating on floats. You can either do this:

X is round((5.3-5)*100/60).

Or

X is round((5.3-5)*100)//60.

Of course, in both of these cases, you get 0 because it's about 0.3 if you were doing floats. So it's unclear if that's what you really intended.

Other interesting options:

?- X is (5.3-5)*100/ 60.
X = 0.4999999999999997.

?- X is float(round((5.3-5)*100) rdiv 60).
X = 0.5.


回答3:

This is due to floating point arithmetic, which is (almost*) always imperfect. Computers work in binary, but people mostly work in base 10. This introduces some imprecision here and there; how bad the imprecision is depends on how the hardware and (sometimes) software in question works. But the key is that you can't predict exactly what the errors will be, only that there will be errors.

The result is that you can't expect 5.3-5 to be exactly 0.3; you need to test whether it is really close (like within 0.00000000000001) instead. When you get results like in your second example, you need to explicitly convert to an integer before using the result. You can read more in this answer.

[footnote:] Per the comments below, I will add that arithmetic using floating point numbers that happen to be powers of 2 (that is, 2^i) or sums of powers of 2 (e.g., 7=4+2+1) can be calculated exactly by a computer. There's a philosophical argument as to whether this is actually "floating point arithmetic." But it explains why, for example, most languages will report that 2.0 + 1.0 is 3, but 0.2 + 0.1 is something like 0.30000000000000004.