Haskell 将浮点四舍五入到特定的小数位数

Haskell 将浮点四舍五入到特定的小数位数,haskell,floating-point,rounding,Haskell,Floating Point,Rounding,我试图显示一个浮点数,但将其舍入,因此 f = 5.545 显示为:5.55while f = 5.544 显示为:5.54 我见过只显示前两位小数的方法,但我想把它四舍五入 谢谢大家! 这是因为计算机中浮点数的表示方式:它们实际上是以2为基数,而不是以10为基数(有点过于简单,但已经足够了)。因此,当您输入0.545时,计算机实际上会将其记录为0.549999999…——非常接近0.545,但稍小一些。由于它比0.545小,难怪它会四舍五入到0.54 如果您确实需要精确的基数-10,则应

我试图显示一个浮点数,但将其舍入,因此

f = 5.545
显示为:
5.55
while

f = 5.544 
显示为:
5.54

我见过只显示前两位小数的方法,但我想把它四舍五入


谢谢大家!

这是因为计算机中浮点数的表示方式:它们实际上是以2为基数,而不是以10为基数(有点过于简单,但已经足够了)。因此,当您输入
0.545
时,计算机实际上会将其记录为
0.549999999…
——非常接近
0.545
,但稍小一些。由于它比
0.545
小,难怪它会四舍五入到
0.54

如果您确实需要精确的基数-10,则应使用
Float
Double
。该包特别注意以10为基数表示浮点数而不会丢失

x :: Decimal
x = 0.545

show x
> "0.545"
需要注意的是,
printf
不支持
Decimal
,因此必须通过四舍五入和通过
show
转换为字符串来显示它。另一个警告是,
roundTo
进行“银行家四舍五入”-如果最后一个数字是五,它将四舍五入到最近的偶数数字,因此我们需要将其作为特例加以抵消(我找不到一个按算术规则四舍五入的现成函数):

但是,如果您只想让它适用于小数点后三位的数字,您可以在四舍五入之前只添加一个非常小的值,比如
0.00001
。该值足够小,不会弄乱您的实际数字,但足够大,可以补偿base-2与base-10的差异:

displayRounded :: Double -> String            
displayRounded x = printf "%.2f" (x + 0.00001)

displayRounded 0.544
> "0.54"

displayRounded 0.545
> "0.55"

所以我没有找到一个真正的解决办法,但我意识到:

Printf已经完成了这项工作,但不是我想要的。 假设我想要1.445,它将显示1.44。但是如果数字是1.446,那么它将显示1.45


不完全是我想要的,但足够接近。

您当前使用什么来显示这些数字?我使用printf,我可以将5.545显示为5.54,但我希望它是5.55,因为第三个小数点是5。当您的实现使用IEEE-754二进制64作为浮点时,类型中没有数字5.545。该格式以二进制格式表示数字,与5.545最接近的数字是5.54499999999928945726423989981412887532421875。因此,显示方法可能会显示“5.545”,但不会显示实际数字。数字不是5.545这一事实就是为什么将其四舍五入到两个十进制数字会产生“5.54”而不是“5.55”。其结果是,只要您使用IEEE-754 binary64格式,任何四舍五入技术都不会导致5.545显示为“5.55”因为不可能将5.545作为要舍入的数字传递,所以IEEE-754 binary64中不存在该数字,并且任何算法都无法处理从未传递给它的输入。可能还有其他解决方案,例如使用内置浮点类型以外的格式(例如,使用整数算术作为手动定点解决方案,并使用自己的代码格式化结果)。你应该解释更多关于这个问题的背景。添加0.005也不是很有效
printf“%.2f”(5.544+0.005)
产生
5.55
,但正确的四舍五入答案是
5.54
。是的,@DanielWagner,我完全误解了这个问题,不是一次,而是两次!:-/谢谢你指出。不管你多么希望它工作,在四舍五入之前加一个小ε是不正确的。对于接近舍入边界的数字,它将给出错误的答案。例如,尝试
displayDecimal 0.5441
displayRounded 0.544995
@DanielWagner With
displayDecimal
我刚刚错过了一个零(增量需要比舍入精度小两个数量级;现在更正),并且
displayRounded
的前缀是明确的“如果你只需要三位小数的数字。。。“,因此
0.544995
的测试用例不算数。使ε变小只意味着坏用例必须更接近舍入边界;它实际上并不能解决问题。如果将
x+e
四舍五入,而不是将
x
四舍五入,则设置
x=0.5-e/2
将在reals中生成反例。(诚然,这不适用于
Double
;但当您将
e
设置得足够小,以致此特定反例对
Double
不起作用时,您反而开始导致非常大的
x
的舍入误差。)
displayRounded :: Double -> String            
displayRounded x = printf "%.2f" (x + 0.00001)

displayRounded 0.544
> "0.54"

displayRounded 0.545
> "0.55"