浮点数、精度和Parsec

4
考虑以下代码:
import Text.Parsec
import Text.Parsec.Language
import Text.Parsec.String
import qualified Text.Parsec.Token as Token

float :: Parser Double
float = Token.float (Token.makeTokenParser emptyDef)

myTest :: String -> Either ParseError Double
myTest = parse float ""

现在,多亏了 QuickCheck,我知道了一个神奇的数字(为了方便,我已经对齐了结果):
λ> myTest "4.23808622486133"
Right      4.2380862248613305

有些浮点数无法在内存中精确表示,一些操作很容易使浮点数产生“波动”。我们都知道这一点。但是,导致此解析问题的原因似乎不同。
简单介绍一下帮助我发现这个问题的测试...在这些测试中,生成浮点值,打印并解析回来(使用Parsec)。例如,数字9.2被认为是无法表示为浮点值的,然而它通过了测试(显然是由于“智能”打印函数)。为什么4.23808622486133会失败?

对于那些认为这些数字相同,并且4.23808622486133只是4.2380862248613305的最短不含糊表示的人:

a1 :: Double
a1 = 9.2000000000000003

a2 :: Double
a2 = 9.200000000000001

b1 :: Double
b1 = 4.23808622486133

b2 :: Double
b2 = 4.2380862248613305

现在:
λ> a1 == a2
True
λ> b1 == b2
False

1
有些人可能会再次不同意,但我仍会说:当你使用浮点数时,你永远不应该期望任何东西完全相等。 - leftaroundabout
1
@leftaroundabout,完全正确。不过这种解析中出现这种情况还是挺有趣的。我的意思是,假设有一个数字 x,它不能准确地表示为浮点数,但当它被打印时,我们得到一个实际打印的数字 x'。那么当你将其解析回来时,你应该再次得到 x。我期望在此方面有某种一致性... - Mark Karpov
我很遗憾写了我的答案。你能取消接受它吗?另一个答案似乎对Haskell程序员更有用。 - Pascal Cuoq
@PascalCuoq,我看到了你和DanielWagner的讨论,我明白为什么你希望我取消接受答案。然而,我认为你的回答很好,因为它提供了坚实的证据表明该bug确实存在。这个bug在第三方库Parsec中,而不是在GHC中。你回答中的理论部分也很有启发性。我认为你的回答不应该因为一个踩而被取消接受。再次感谢你的时间和努力。 - Mark Karpov
2个回答

3
Parsec使用类似以下方式将数据转换为Double类型:
foldr (\d acc -> read [d] + acc / 10) 0 "423808622486133" :: Double

正如你所指出的那样,这并不等同于

423808622486133 / 100000000000000 :: Double

我认为这应该被视为Parsec中的一个bug。


1
这个问题在Parsec中仍未得到解决。如果这个问题影响了你的工作,请看一下Megaparsec,它是Parsec的一个分支,修复了许多错误和概念上的缺陷,提高了错误信息的质量等方面。
正如你所见,这个问题在那里得到了解决:
λ> parseTest float "4.23808622486133"
4.23808622486133
λ> parseTest float "4.2380862248613305"
4.2380862248613305

Disclosure: 我是Megaparsec的作者之一。


在Stack Overflow上讨论自己的项目/软件包时,您需要披露与它们的关联。 - dfeuer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接