丑陋的双打 - 为什么2.9000000000000004而不是2.9?

时间:2013-07-21 07:36:46

标签: haskell

当我在ghci中做5.2 - 2.3时,我将获得2.9000000000000004而不是2.9。 当使用Double或Float时,这样的丑陋(以及人类错误)结果也出现在其他地方。

为什么会这样? (这只是为了好奇,而不是我真正的问题)

我真正的问题: 我怎么告诉ghci不这样做,并且像任何其他编程语言(和计算器)那样显示双打操作的结果,就像每个15岁的人都会写它们一样?

当我使用ghci作为一个不错的计算器并处理我执行此类操作的列表时,这真烦人。

map ((-)2.3) [4.0, 3.8, 5.2, 6.4, 1.3, 8.3, 13.7, 9.0, 7.5, 2.4]
[-1.7000000000000002,-1.5,-2.9000000000000004,-4.1000000000000005,0.9999999999999998,-6.000000000000001,-11.399999999999999,-6.7,-5.2,-0.10000000000000009]

这在以后使用纸张上的数字时无效

提前致谢:)

4 个答案:

答案 0 :(得分:18)

  

为什么会这样?

因为某些浮点数不能用有限数量的位表示而不能舍入。 Floating-point numbers的数字位数有限,它们不能准确地代表所有real numbers:当数字多于格式允许的数字时,剩余的数字将被省略 - 数字是四舍五入的。

您应该阅读What Every Computer Scientist Should Know About Floating-Point Arithmeticthis answer

答案 1 :(得分:8)

  

我如何告诉ghci不这样做,并像其他任何编程语言(和计算器)一样显示双打操作的结果,就像每个15岁的人都会写它们一样?

由于这些结果是实际结果GHCI(以及您的标准计算器 * )计算出您无法更改结果的内部表示(see TNI's answer)。由于您只想显示固定数量的小数,因此更多的是演示文稿(与C中的printf("%f.2",...)相比)。

可以在https://stackoverflow.com/a/2327801/1139697中找到解决方法。它可以像这样应用:

import Numeric
fixedN :: (RealFloat b) => Int -> b -> String
fixedN a b = showFFloat (Just a) b ""

map (fixedN 2 . (-)2.3) [4.0, 3.8, 5.2, 6.4, 1.3, 8.3, 13.7, 9.0, 7.5, 2.4]
-- result: ["-1.70","-1.50","-2.90","-4.10","1.00","-6.00",...]

请注意,如果要继续计算,这将不可行。如果你想要完全算术,你最好还是使用Rationals。在这种情况下,不要忘记你的输入应该是理性的。

*是的,即使您的标准计算器做同样的事情,唯一的原因是您没有看到它是固定的表示,它不能显示超过固定数量的小数。

答案 2 :(得分:3)

浮点数的本质是它们不能完全代表真实(或理性)数字。 Haskell默认转换为字符串可确保在读回数字时获得完全相同的表示形式。如果您想要一种不同的方式来打印数字,您可以创建自己的类型,以不同的方式显示数字。

像(未经测试)的东西:

newtype MyDouble = MyDouble {getMyDouble :: Double}
            deriving (Eq, Ord, Num, Real, RealFrac, Fractional, Floating)
instance Show MyDouble where show = printf "%g" . getMyDouble
default (MyDouble)

这将创建Double类型的副本,但使用不同的Show实例,只打印几个小数。 default声明使编译器在存在歧义时选择此类型。哦,为了完成这项工作,你需要一些语言扩展。

您还可以尝试CReal包中的numbers类型。

答案 3 :(得分:3)

  

如何告诉ghci不这样做,并显示操作的结果   就像其他任何编程语言(和计算器)一样   将

你真的尝试过“任何其他编程语言”吗?或者你只是在欺负?

FWIW,这是使用JVM的语言的解释器输出:

frege> 5.2 - 2.3
2.9000000000000004

在我看来,好像你会用所有JVM语言获得相同的结果。由于JVM是用C ++编写的,因此结果可能相同。而且由于大多数语言运行时都是用C / C ++编写的,因此使用这些语言也可能获得相同的结果。除非他们是如此“用户友好”并执行四舍五入,否则你没有要求。