为什么编程语言会将小数舍入到.6以下?

33
如果你在一个要求四舍五入到最近十分位的格式中插入一个小数,比如说1.55,那么它将被舍为1.5。1.56则会舍入为1.6。我记得在学校里我们学习到当数字以5结尾时应该向上舍入,如果是4或更低则向下舍入。为什么Python等语言不同呢?
下面是Python 2.6x(最新版本)的示例代码:
'{0:01.2f}'.format(5.555)  # This will return '5.55'

在尝试了一些提供的示例之后,我意识到了更加令人困惑的事情:

'{0:01.1f}'.format(5.55)  # This will return '5.5'
# But then
'{0:01.1f}'.format(1.55)  # This will return '1.6'
为什么在使用1.55和5.55时会有差异?两者都被视为字面值(即浮点数)。

1
我不知道有哪种计算机语言可以这样做。您手上是否有这种情况的示例代码? - wallyk
7
这是因为在你的系统上,把“1.55”转换成可表示的二进制浮点数时,它比十进制值1.55略大,所以四舍五入得到更大的值;而“5.55”转换成的浮点数比5.55略小,所以四舍五入得到更小的值。 - Stephen Canon
7个回答

91

首先,在大多数编程语言中,像 “1.55” 这样未经装饰的常量会被视为双精度值。然而,1.55 无法精确地表示为双精度值,因为它在二进制表示中没有终止的表示形式。这会导致许多奇怪的行为,但其中一个效果是,当您键入 1.55 时,您实际上并没有得到恰好介于 1.5 和 1.6 之间的值。

在二进制中,十进制数 1.55 是:

1.10001100110011001100110011001100110011001100110011001100110011001100...

当你输入"1.55"时,这个值实际上会被四舍五入为最接近的可表示双精度浮点数(在许多系统上...但也有例外情况,我将在后面讨论)。这个值是:

1.1000110011001100110011001100110011001100110011001101

这个数略微大于1.55;用小数表示,它确切地是:

1.5500000000000000444089209850062616169452667236328125

当被要求将此值舍入到小数点后一位时,它会向上舍入为1.6。这就是为什么大多数评论者说他们无法复制您看到的行为。

但是,在您的系统上,“1.55”向下舍入而不是向上。发生了什么?

可能有几种不同的情况,但最有可能的是您正在使用采用x87指令进行浮点运算的平台(可能是Windows),该指令使用不同的(80位)内部格式。 在80位格式中,1.55的值为:

1.100011001100110011001100110011001100110011001100110011001100110

这个数字略小于1.55,用十进制表示是:

1.54999999999999999995663191310057982263970188796520233154296875

因为它小于1.55,所以当它被舍入到小数点后一位时会向下舍入,这就是你观察到的结果"1.5"。

FWIW:在大多数编程语言中,默认的舍入模式实际上是“四舍五入到最近的偶数”,只是当您在十进制中指定分数值时,您几乎永远不会遇到精确的中间情况,因此对于普通人来说很难观察到这一点。但是,如果您查看将“1.5”舍入为零位数的方式,就可以看到它。

>>> "%.0f" % 0.5
'0'
>>> "%.0f" % 1.5
'2'
请注意,这两个值都会四舍五入为偶数;没有一个会四舍五入为 "1"。 编辑:在您修改的问题中,似乎已经切换到另一个Python解释器,在该解释器上,浮点数采用IEEE754双精度类型而不是x87 80位类型。因此,“1.55”向上四舍五入,就像我的第一个例子一样,但“5.55”转换为以下二进制浮点值:
101.10001100110011001100110011001100110011001100110011

就是:

5.54999999999999982236431605997495353221893310546875

以十进制计算;因为这个数字比5.55,所以它会向下取整。


在Python 2.5中,"%.0f" % 0.5返回的是'1'。 - David Webb
Dave: 有趣——在我的 Python 2.5.1 上它也返回了 '0'。你用的是什么平台? - Ken
我相信Python中的"%.0f"只是调用了您系统的C库版本的printf。C标准建议,但并不要求,printf实现IEEE754正确舍入的浮点格式化程序。您可能在一个库提供者选择不这样做的系统上。 - Stephen Canon
我的措辞也增加了一些混淆。我说“十进制格式化”,但我实际上正在使用的类型是浮点数。这是因为我只是在解释器中键入文字而不是Decimal('1.55'),所以它被表示为浮点数。 - orokusaki
@Ken - "%0.f" % 0.5 在 Windows XP 上的 ActivePython 2.5.4 和 Windows 7 上的 ActivePython 2.6.4 中都返回 '1'。 - David Webb

5

问题不是说“Python等”没有使用四舍五入功能吗? 我认为问题的重点是四舍五入是最常用的方法,而提问者没有看到这种行为。尽管根据我的经验,Python和其他语言确实使用四舍五入,这就是我感到困惑的原因。 - David Webb

2

你能给出一些示例代码吗?因为我在Python中看不到这种行为:

>>> "%.1f" % 1.54
'1.5'
>>> "%.1f" % 1.55
'1.6'
>>> "%.1f" % 1.56
'1.6'

2
这似乎不是事实。你正在使用“float”字符串格式化程序,对吗?
>>> "%0.2f" % 1.55
'1.55'
>>> "%0.1f" % 1.55
'1.6'
>>> "%0.0f" % 1.55
'2'

2
舍入和截断在每种编程语言中都不同,所以你的问题可能直接与Python有关。
然而,舍入作为一种实践取决于你的方法论。
你还应该知道,在许多编程语言中,将小数转换为整数的结果与实际舍入数字的结果不同。
编辑:根据其他帖子的一些内容,似乎Python没有展示你描述的舍入行为。
>>> "%0.2f" % 1.55 
'1.55' 
>>> "%0.1f" % 1.55 
'1.6' 
>>> "%0.0f" % 1.55 
'2' 

很奇怪,如果你有%0.2f并输入1.555,你会得到1.55,但是如果你有%0.1f和1.55,你会得到1.6。所以不一致。 - orokusaki
这是因为将"1.555"四舍五入成二进制浮点数时,它刚好在小数1.555的另一侧,与1.55发生的情况略有不同。 - Stephen Canon
@orokusaki:浮点数的一致性绝对存在于它们所处的基于二进制算术的上下文中。在整数的情况下,十进制算术与二进制算术之间是一一映射的,但在分数的情况下很少如此。例如,0.125、0.25、0.375、0.5、0.625、0.75和0.875在基于二进制的表示法中可以精确地表示,因为它们是2^-1、2^-2和/或2^-3的组合之和。不一致...并非真正如此。 - Olof Forshell

2
我看不出你描述的确切行为的原因。如果你的数字只是举例,那么类似的情况可以解释为使用银行家舍入:
1.5 rounds to 2
2.5 rounds to 2
3.5 rounds to 4
4.5 rounds to 4

即,.5的值将四舍五入到最近的偶数整数。这样做的原因是,长期来看,四舍五入很多数字会平均分布。例如,如果一家银行要向100万客户支付利息,并且其中10%的客户最终需要四舍五入为.5美分,如果值被向上舍入,则银行将支付500美元以上。
意外舍入的另一个原因是浮点数的精度。大多数数字无法准确表示,因此它们由最接近的可能近似值表示。当您认为您拥有的数字为1.55时,您可能实际上得到的数字是1.54999。将该数字四舍五入到一位小数当然会导致1.5而不是1.6。

你说3.5四舍五入为2,5.5四舍五入为4。这是打错字了吧? - orokusaki

0

解决舍入问题(至少在某些情况下)的一种方法是进行预处理。单精度和双精度格式可以准确表示从-2^24-1到2^24-1和-2^53-1到2^53-1的所有整数。对于实数(具有非零小数部分),可以执行以下操作:

  1. 去掉符号并将其保留以备后用
  2. 将剩余的正数乘以10^(所需小数位数)
  3. 如果您的环境舍入模式设置为chop(向零舍入),则加上0.5
  4. 将数字四舍五入到最近的值
  5. 使用0个小数位的格式将数字sprintf为字符串
  6. 根据sprintf、所需小数位数、小数点和符号“手动”格式化字符串
  7. 现在,该字符串应包含精确的数字

请记住,如果第3步后的结果超出特定格式(上述)的范围,则您的答案将不正确。


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