Equality check with fractions in Common Lisp
Asked Answered
T

2

5

So I'm learning that Lisp does fractions which is great. But why then does this equality check return NIL:

* (= 0.2 1/5)          

NIL

...while it returns True if it's converted to float first:

* (= 0.2 (float 1/5))

T

I've tried in SBCL and CLISP.

Is the implementation incomplete or is there a special reason or logic behind this behaviour?

Titania answered 1/10, 2015 at 14:3 Comment(2)
Inaccuracy of floating point numbers? See https://mcmap.net/q/15914/-why-can-39-t-decimal-numbers-be-represented-exactly-in-binaryWeig
also, (= 0.5 1/2) returns T. 0.5 is terminating in both base 2 and 10: https://mcmap.net/q/15914/-why-can-39-t-decimal-numbers-be-represented-exactly-in-binaryWeig
B
6

Ratios in Common Lisp

Note that fractions (which itself is not a numeric type in Common Lisp) are converted to rationals in Lisp. rational, ratio and integer (and others) are actual numeric types in Common Lisp. If you enter a fraction, it is normalized to a rational (an integer or a ratio number).

CL-USER 16 > 3/9
1/3

CL-USER 17 > 9/9
1

CL-USER 18 > 6/9
2/3

Numeric comparison

When float and ratio are compared, the float value is converted to a rational and then exact comparison is done. See: CLHS, Rule of Float and Rational Contagion.

The ratio is not converted to a float, but the float is converted to a rational.

The problem appears because some floats are not converted to the ratios you would expect. The underlying issue is that floating point numbers have not necessarily an exact representation. The conversion of a non-exact number to an exact rational is not necessary giving the naively expected result.

Unfortunately the conversion of 0.2 to a rational number is not necessarily 1/5, but this:

CL-USER 7 > (rational 0.2)
13421773/67108864

But 0.5 is 1/2.

CL-USER 8 > (rational 0.5)
1/2

This is what happens in your examples:

CL-USER 9 > (= 1/2 (rational 0.5))
T

CL-USER 10 > (= 1/5 (rational 0.2))
NIL

So it is not

CL-USER 14 > (= 0.2 (float 1/5))
T

But:

CL-USER 15 > (= (rational 0.2) 1/5)
NIL

Note that the type rational combines the disjoint subtypes ratio and integer. Thus (rational 1.0) could be an integer, and not a ratio.

Buote answered 1/10, 2015 at 15:0 Comment(0)
E
3

The specification about = says:

The value of = is true if all numbers are the same in value; otherwise it is false.

While the specification on real says:

The types rational and float are disjoint subtypes of type real.

In other words, you can compare them because they are both numbers, but they are different because, since the subtypes are disjoint, they are different values.

What you can do is convert them to the same “subset” of numbers, float, and then the comparison will return true.

The reason for this behaviour is that float numbers in general have an approximate representation (see the comment by @coredump and the link), while rational numbers have an exact representation, so it is not very sensible to compare values that could “look” externally identical but are different in the internal (binary) representation.

Economic answered 1/10, 2015 at 14:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.