如何在 Haskell 中准确缩放浮点数?

How do you scale floating point numbers accurately in Haskell?

提问人:pseuyi 提问时间:7/20/2023 更新时间:7/20/2023 访问量:94

问:

我想将一些小数点后四位的数字缩放为整数。在缩放这些数字时使用(或)会导致某些数字不准确吗?roundceiling

在 ghci 中: 期待 1009

ghci> let x = 1.009 * 1000
ghci> x
1008.9999999999999
ghci> round x
1009
ghci> floor x
1008
ghci> ceiling x
1009

预期 100900000

ghci> let y = 1.009 * 100000000
ghci> y
1.0089999999999999e8
ghci> round y
100900000
ghci> floor y
100899999
ghci> ceiling y
100900000
Haskell 浮点 双精度

评论

0赞 Daniel Wagner 7/20/2023
是的。(菲尔勒)
0赞 Fyodor Soikin 7/20/2023
这并不是 Haskell 的事情,这就是浮点数通常的工作方式。尝试同样的事情,比如 JavaScript。或者 Ruby。或者其他任何东西。
0赞 Daniel Wagner 7/20/2023
实际上,更准确地说:、、,并且不会添加新的不准确之处。但是乘以 1000 可以。roundfloorceiling
0赞 chux - Reinstate Monica 7/20/2023
@pseuyi 是一个 17 位值。鉴于浮点数和有限精度的二进制性质,四舍五入到小数点后 15 位或更少。1008.9999999999999
2赞 Ben 7/20/2023
请注意,根本问题并不在于缩放。源代码文本实际上并不代表正好等于 1.009 的运行时值,因为没有这样的 . 因为 64 位浮点数实际上是值(参见 exploringbinary.com/floating-point-converter)。以完美的精度将其乘以 1000 仍然会以 ;您需要获得的精确度从未出现过。浮动就是这样,在任何语言中。1.009DoubleDouble1.0091.0089999999999998969713033147854730486869812011718751008.9999...1009

答:

5赞 K. A. Buhr 7/20/2023 #1

对于序列 0.000、0.001、0.002、0.003 ...9.999、功能:

scale x = round (1000*x)

将生成“正确”的四位数结果。这是因为表达式引入的任何浮点误差都是很小的一部分,大约为 1e-13,因此,如果“正确”结果是 -- 比如 -- 1009,那么你可以得到的最小可能答案将是这样的:1000*x1000*x

1008.9999999999999

你能得到的最大可能的答案是这样的:

1009.0000000000001

这两个整数至少比任何其他整数更接近 10,000,000,009 倍,并且由于四舍五入到最接近的整数,因此它将始终四舍五入到正确答案。round

请注意,和是完全不同的。它们不会四舍五入到最接近的整数,而是四舍五入到特定方向上最接近的整数,因此它们可以清楚地给出错误的答案。具体来说,他们可以提供:floorceiling

floor 1008.9999999999999 == 1008    -- wrong!
ceiling 1009.0000000000001 == 1010  -- wrong!

缩放使用也适用于更大的缩放,在一定程度上 - 具体来说,缩放到1e16左右的点。如果缩放比例为“仅”1e15,则转换仍然有效(对于上述序列中的所有人):roundx

scale x = round (1000000000000000*x)

但是,当缩放比例为 1e16 时:

scale x = round (10000000000000000*x)

你开始得到错误的答案:

ghci> scale 0.5005
5004999999999999

如果要获得所有缩放的正确答案,请分两部分进行缩放:

scale :: RealFrac n => n -> Integer
scale x = 10000000000000000000000000000000000 * round (10000*x)

在这里,表达式可靠地将序列 0.000, 0.001, ..., 9.999 中的任意数字缩放到序列 0, 1, ..., 9999 中的相应整数,而不会出现浮点错误,如上所述。从那里开始,任意精度乘以任何整数,无论多大,都将没有错误。round (10000*x)Integer

评论

1赞 Daniel Wagner 7/20/2023
...但要注意,存在一些非常有趣和有用的数字,它们不属于 0.000、0.001、...9.999,并不是所有这些都会按 1000 扩展。