为什么Python的math.ceil()和math.floor()操作返回浮点数而不是整数?

187
有人能解释一下这段话吗(来自文档 - 强调是我的):

math.ceil(x) 返回一个浮点数,表示大于或等于x的最小整数值。

math.floor(x) 返回一个浮点数,表示小于或等于x的最大整数值。

为什么.ceil.floor返回浮点数,而它们根据定义应该计算整数呢?
编辑: 这些回答给出了很好的理由说明为什么它们应该返回浮点数,而我刚习惯这个想法,当@jcollado指出它们实际上在Python 3中确实返回整数时...

1
我的猜测是因为x是一个浮点数,而不是整数,但由于我不知道也不使用Python,所以我会让其他人更明确地回答。 :) - Adam V
7
但是 ceil/floor 操作的整个意义就在于将浮点数舍入为整数! - Yarin
2
这也让我感到不爽,第一次遇到它时,因为它似乎是错误的。至少,使用 int(floor(n)) 并不太难。 - wim
2
具有讽刺意味的是(因为浮点数被用来防止溢出),由于浮点表示法,在低位数字上floor/ceil返回的值是无意义的,远在64位int溢出之前就已经如此。【在32位旧日子里不是这样的。】 - user1196549
9个回答

116

正如其他答案所指出的那样,在Python中,它们返回浮点数,可能是为了历史原因,以防止溢出问题。然而,在Python 3中,它们返回整数。

>>> import math
>>> type(math.floor(3.1))
<class 'int'>
>>> type(math.ceil(3.1))
<class 'int'>

您可以在PEP 3141中找到更多信息。


5
我刚刚输入了上面的命令。如果你尝试使用 float("inf")float("nan"),你将会得到一个 OverflowError 异常。 - jcollado
11
为了完整起见,Python的numpy.floorceil返回浮点数(<class 'numpy.float64'>)。 - Neil G
1
@jcollado:在Python 2.7或3中,float("inf")不会产生异常。 - endolith
1
@endolith 你说得对,我已经检查过了,现在不会再发生这种情况了。可能自2011年12月以来有些改变。 - jcollado
1
有没有办法让 Python 2.x 在 Math.ceil() 中返回整数(例如,导入 future 模块)?我想避免使用 int(Math.ceil(..)) 并在升级到 3.x 时记得将其删除。谢谢。 - user1055761
显示剩余2条评论

107

浮点数的范围通常超过整数的范围。通过返回一个浮点值,函数可以为那些超出可表示整数范围的输入值返回一个合理的值。

考虑:如果floor()返回一个整数,那么floor(1.0e30)应该返回什么呢?

现在,虽然Python的整数现在是任意精度的,但它并不总是这样。标准库函数只是等效C库函数的薄包装器。


14
即使Python中的整数现在是任意精度的,仍然有浮点数的floor和ceiling无法用整数表示。尝试floor(float("inf"))ceil(float("nan")) - Michael Hoffman
4
据Michael所说,在P3中,如果你尝试那样做,现在会出现OverflowExceptions。请参见jcollado的答案。 - Yarin
5
嗯,它应该返回“long”类型(也称为“bigint”),对我来说这似乎是一个明显的答案,但现在我感觉自己有点天真。 - koschei
4
在Python 3.x中是可以这样做的,可以参考jcollado的答案。 - Greg Hewgill
1
请参见另一个答案的评论,了解即使对于在范围内的数字,这也是有意义的原因。 - ivan_pozdeev

20
因为Python的math库只是C math库的一个轻量级封装,它返回浮点数。

C数学库支持任意精度整数吗?因为其他Python数学函数返回的就是这种类型。 - endolith

18
你困惑的源头在于你的评论中:

ceil/floor操作的整个意义就是将浮点数转换为整数!

ceil和floor操作的目的是将浮点数据四舍五入为整数值,而不是进行类型转换。需要获取整数值的用户可以在操作后进行显式转换。

请注意,如果你只有返回整数的ceil或floor操作,那么要实现将浮点数四舍五入为整数值是不可能如此轻松的。你需要先检查输入是否在可表示的整数范围内,然后调用函数;你需要在单独的代码路径中处理NaN和无穷大。

另外,如果你想符合IEEE 754标准,你必须有返回浮点数的ceil和floor版本。


Stephen- 我重新写了我的评论- 我的意思是四舍五入,而不是转换。但这并不是我困惑的源头- 而是我没有认识到范围差异。 - Yarin
1
今天我已经没有投票的机会了。这是正确的答案,比任何表示限制更为重要。 - Marcin
15
在我多年的编程经验中,我不记得遇到过希望 floor/ceil 的结果为浮点数而不是整数的情况。事实上,Python3 返回整数表明这样做更有用。我不相信“...的意义”这种说法;它似乎是基于它所做的事情来定义其意义,而不是基于程序员可能想要的东西来定义。 - ShreevatsaR
2
@ShreevatsaR 我遇到过这种情况几次(主要是在 Python 上下文之外)。我记得的时候是在处理曼德博集合时。有时你需要将一个整数值转换为浮点数,但随后立即对该值应用一些浮点操作(例如,将其缩小 0.5 倍)。然后,保留向下取整的浮点数并对其应用浮点操作比先将其转换为整数再立即将其转换回浮点数更有效率。 - blubberdiblub

5

2
现在它也无法容纳“截断实数的全部范围”,因为显然这是一个无限集合,需要无限的内存。它可以容纳截断浮点数的范围,但这只是实数集合ℝ的一个小子集。 - leftaroundabout
6
@leftaroundabout - 挑剔挑剔!你知道我的意思。 - Mark Ransom

4
因为浮点数的范围大于整数 -- 返回一个整数可能会溢出。

8
在Python中,整数不会溢出;当它们变得太大时,整数会转换为bigints。打开一个Python解释器,输入"2 ** 500",你会看到你得到了一个对象,你可以像操作整数一样对待它。 - koschei
@koschei:即使是大整数在尝试表示无穷大时也会溢出。 - Stephen Canon

4
这是一个非常有趣的问题!由于浮点数需要一些位来存储指数(=bits_for_exponent),所以任何大于2 **(float_size-bits_for_exponent)的浮点数将始终是整数值!在另一个极端,具有负指数的浮点数将给出10-1中的一个。这使得讨论整数范围浮点数范围变得无意义,因为每当数字超出整数类型的范围时,这些函数将简单地返回原始数字。Python函数是C函数的包装器,因此这实际上是C函数的缺陷,它们应该返回一个整数并强制程序员在调用ceil/floor之前进行范围/NaN/Inf检查。
因此,逻辑答案是这些函数仅在返回整数范围内的值时才有用,因此它们返回浮点数是一个错误,你很聪明地意识到了这一点!

1
也许是因为其他编程语言也这样做,所以这是通常被接受的行为。(出于其他答案所显示的好原因)

1
最近这件事让我完全措手不及。因为自从70年代以来,我一直在编写C程序,现在才开始学习Python的细节。就像math.floor()这种奇怪的行为。
Python的math库是访问C标准数学库的方式。而C标准数学库是一个浮点数数值函数集合,如sin()、cos()、sqrt()等。在数值计算的上下文中,floor()函数始终返回一个浮点数,已经有50年历史了。它是数值计算标准的一部分。对于我们熟悉C语言的数学库的人来说,我们认为它不仅仅是"数学函数",更是一个浮点数算法集合。最好将其命名为NFPAL - 数值浮点算法库。 :)
我们理解历史的人立刻将Python的math模块视为长期存在的C浮点库的包装器。因此,我们毫不犹豫地期望math.floor()与C标准库floor()函数相同,接受一个浮点数参数并返回一个浮点数值。

根据维基百科关于此主题的页面,floor()作为数值数学概念可追溯至1798年。https://en.wikipedia.org/wiki/Floor_and_ceiling_functions#Notation

尽管在逻辑上它是一个类似的概念,但它从来不是计算机科学中的浮点到整数存储格式函数。

在这种情况下,floor()函数一直是浮点数计算,就像数学库中的所有(大多数)函数一样。浮点数超越了整数所能做到的事情。它们包括特殊的+inf、-inf和Nan(非数字),它们都有明确定义的方式通过浮点数数值计算进行传播。Floor()始终正确地保留诸如Nan和+inf、-inf等值在数值计算中。如果Floor返回一个int,它完全破坏了数值floor()函数的整个概念。如果要成为真正的浮点数floor()函数,math.floor(float("nan"))必须返回"nan"。

最近我看到一段Python教育视频告诉我们使用:

i = math.floor(12.34/3)

为了获得一个整数,我自嘲地笑了笑,因为那位教师显然不知道该怎么做。但在写下一些尖刻的评论之前,我进行了一些测试,震惊的发现Python中的数值算法库返回的是一个整数。更奇怪的是,我认为从除法中获得整数的明显答案,竟然是使用:

i = 12.34 // 3

为什么不使用内置的整数除法来获取所需的整数呢!从我的C背景来看,这是显而易见的正确答案。但惊讶的是,在Python中进行整数除法时,返回的是浮点数!哇!Python可以是一个多么奇怪的颠倒世界。

在Python中更好的答案是,如果你真的需要int类型,那么你应该明确地要求int:

i = int(12.34/3)

需要记住的是,floor() 向负无穷舍入,而 int() 向零舍入,因此它们对于负数给出不同的答案。因此,如果可能存在负值,则必须使用适合应用程序所需结果的函数。

然而,Python 是一个不同的东西,有充分的理由。它试图解决与 C 不同的问题集。Python 的静态类型对于快速原型设计和开发非常有用,但当针对一种对象类型(如浮点数)进行测试的代码在传递 int 参数时以微妙且难以找到的方式失败时,它可能会导致一些非常复杂和难以找到的错误。正因为如此,Python 做出了许多有趣的选择,将最小化意外错误的需求置于其他历史规范之上。

将除法始终返回 float(或某种非 int 形式)是朝着正确方向迈出的一步。出于同样的考虑,将 // 设计为 floor(a/b) 函数而不是 "int divide" 是很合理的。

将浮点数除以零变成致命错误而不是返回float("inf")同样明智,因为在大多数Python代码中,除以零不是数值计算,而是编程错误,其中数学错误或有误差的问题。当发生这种错误时,捕获该错误对于平均Python代码更为重要,而不是传播形式为“inf”的隐藏错误,该错误会导致与实际错误相隔千里。

只要语言的其余部分在需要时能够很好地将int转换为float,例如在除法或math.sqrt()中,就有逻辑将math.floor()返回一个int,因为如果稍后需要它作为float,则会正确地将其转换回float。如果程序员需要一个int,那么函数会给他们需要的东西。math.floor(a/b)和a//b应该表现出相同的方式,但它们没有,我想这只是历史原因尚未调整为一致性的问题。也许由于向后兼容性问题而太难“修复”。也许并不重要???

在Python中,如果您想编写硬核数值算法,则正确的答案是使用NumPy和SciPy,而不是内置的Python math模块。

import numpy as np

nan = np.float64(0.0) / 0.0    # gives a warning and returns float64 nan

nan = np.floor(nan)            # returns float64 nan

Python与其他语言不同,这是有充分理由的,需要一些时间才能理解。在这个例子中,我们可以看到,OP并没有理解数值floor()函数的历史,他们期望它返回一个整数,这是基于他们对数学整数和实数的思考。现在Python正在做我们数学(而不是计算机科学)培训所暗示的事情。这使得初学者更有可能做出他们期望的操作,同时仍然涵盖了使用NumPy和SciPy进行高级数值算法的所有更复杂的需求。我经常对Python的发展方式印象深刻,即使有时候我也会完全措手不及。

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