Parsing specific numbers results in unfaithful representations of the original string
In general, it seems that some numbers just don't parse correctly, at least under latest SBCL. I assume this is due to a quirk in the IEEE 754 Float representation, or similar. I believe this snippet neatly explains the issue:
CL-USER> (lisp-implementation-type)
"SBCL"
CL-USER> (lisp-implementation-version)
"2.1.9"
CL-USER> (ql:system-apropos "parse-float")
#<SYSTEM parse-float / parse-float-20200218-git / quicklisp 2021-08-07>
#<SYSTEM parse-float-tests / parse-float-20200218-git / quicklisp 2021-08-07>
; No value
CL-USER> (parse-float:parse-float "4.78")
4.7799997
4
CL-USER> (parse-float:parse-float "5.78")
5.7799997
4
CL-USER> (parse-float:parse-float "6.78")
6.7799997
4
CL-USER> (parse-float:parse-float "7.78")
7.7799997
4
CL-USER> (parse-float:parse-float "8.78")
8.78
4
CL-USER> (parse-float:parse-float "9.78")
9.78
4
CL-USER> (parse-float:parse-float "10.78")
10.78
5
I'm of the opinion that the string "4.78" should parse to the number 4.78, rather than 4.7799997, and that the expected behaviour is that shown when parsing the string "9.78", such as when using read-from-string as such:
CL-USER> (read-from-string "7.78")
7.78
4
This is due to PARSE-FLOAT doing two successive roundings (one for creating the decimal part, one in the final sum) and the first rounding can change the direction of the second one.
For example, for 7.78:
7 111.000000000000000000000
0.78 0.11000111101011100001010001111010111000010100011110101110000101000111101011100001010001...
We have to round 0.78, because it doesn't have an exact binary representation.
Here are the relevant rounding options:
0.78 0.110001111010111000010 = 0.779999732971191406250 (A)
0.78 0.110001111010111000011 = 0.780000209808349609375 (B)
0.78 0.110001111010111000010100 = 0.779999971389770507812500 (C)
0.78 0.110001111010111000010101 = 0.780000030994415283203125 (D)
(A) would be incorrect rounding after bit 21, as 0.78 is closer to B.
(B) would be the correct rounding after bit 21, but we are truncating after bit 24 here.
(C) is the correct rounding after bit 24, as it's closer to 0.78 than D. However, after this rounding,
we lose the information that 0.78 was closer to B than A, because C is exactly between A and B.
(D) would be incorrect rounding after bit 24, as 0.78 is closer to C.
sum 111.110001111010111000010 = 7.779999732971191406250 (E)
111.110001111010111000011 = 7.780000209808349609375 (F)
The correct binary representation of 7.78 is rounded up (7.78 is closer to F than to E). But the correct binary representation of 0.78 is rounded down (0.78 is closer to C than to D). After 0.78 was rounded down, its addition to 7 is no longer closer to F than to E, so the sum gets truncated down (to E), which is incorrect.
Thanks, I'll look into this when I have time and fix it.