浮点数步长范围

51

文档基本上说明,range 函数必须与这个实现行为完全相同(对于正的 step):

def range(start, stop, step):
  x = start
  while True:
    if x >= stop: return
    yield x
    x += step

它还说它的参数必须是整数。为什么?如果步长是浮点数,这个定义也不完全有效吗?

在我的情况下,我特别需要一个接受浮点类型作为步长参数的range函数。Python中是否有这样的函数,还是我需要自己实现?


更具体地说:我如何以一种好的方式将此C代码直接转换为Python(即不仅仅通过手动使用while循环来完成):

for(float x = 0; x < 10; x += 0.5f) { /* ... */ }

在那个循环中,你不能同时使用 returnyield 关键字,应该使用 break - Tim McNamara
看起来你可以!我没想到那是可能的... 我相信当我尝试类似的东西时会出现错误。 - Tim McNamara
将您发布的代码示例重命名为float_range - 完成。 - Mark Ransom
4
@Tim: return 变成了 raise StopIterationreturn <expression> 会导致语法错误 SyntaxError: 'return' with argument inside generator,即使是 return None 也是如此。 - John Machin
1
实际上,range()返回一个列表。OP描述的实际上是xrange(),它一次返回一个元素。 - BobC
8个回答

53

3
第一段陈述:当使用非整数步长(例如0.1)时,结果通常不会一致。在这种情况下最好使用 linspace - yurisich
2
请注意,linspace具有不同的接口。 - Beni Cherniavsky-Paskin
甚至可以不用numpy.arange。 我写了一个xfrange函数,避免了浮点精度问题。看看吧 ;) https://dev59.com/oHRB5IYBdhLWcg3w4bKv#20549652 - Carlos Vega

36

可能的一个解释是浮点数舍入问题。例如,如果您可以调用

range(0, 0.4, 0.1)

你可能期望的输出结果是

[0, 0.1, 0.2, 0.3]

但实际上您得到的是类似于

[0, 0.1, 0.2000000001, 0.3000000001]

因为舍入问题,所以range通常只用于生成整数索引。如果您仍然需要一个针对浮点数的范围生成器,您可以自己编写。

def xfrange(start, stop, step):
    i = 0
    while start + i * step < stop:
        yield start + i * step
        i += 1

1
@Albert:通常想知道为什么会做出这样的决定是没有太多意义的。在这种情况下,我认为有潜在的严重错误风险,而实际使用场景却很有限,因此不值得冒险。 - Katriel
2
你正在积累舍入误差。请使用以下代码: i = 0; r = start while r < stop: i += 1; r = start + i * step; yield r - Cees Timmerman
为什么这个得到了这么多赞?请考虑 @CeesTimmerman 的评论并将其纳入您的答案中,以解决不累积舍入误差的问题。 - josch
1
@josch,我显然错过了Cees的评论。当然,你是绝对正确的,我的版本会累积舍入误差。事实上,重新审视它让我有点不安,因为这显然是错误的做法!但现在它已经被修复了。感谢你提醒我。 - Zarkonnen
1
如果step为负数,它会出现问题,请像这样修复您的while条件:while (start + i * step) * (-1 if step < 0 else 1) < stop: - user443854
显示剩余2条评论

13
为了在范围表达式中使用小数,一个很酷的方法是: [x * 0.1 for x in range(0, 10)]。 请注意保留源代码中的HTML标签。

3
如果你的“范围”有很多数字,而你的“步长”为0.1,则如果步长变小至0.00000001,那么这种方法将无法运行,并且大多数计算机都会卡住。我们需要模拟xrange的功能,即需要一个可迭代/生成器,而不是列表解析式。在这种情况下,fxrange比上述range更有效。 - ekta
1
非常简单的列表推导式解决方案,谢谢! - avtomaton
1
@ekta 使用 () 而不是 [] 将其转换为生成器。 - Cees Timmerman
我只需要一些简单的东西,就像我从本地range函数中期望的那样--我对这个解决方案感到满意。 - juanmirocks

8

浮点数的问题在于由于不精确性,您可能得不到您期望的相同数量的项。如果您正在处理多项式,那么确切的项数非常重要,这可能是一个真正的问题。

您真正想要的是算术级数; 以下代码将适用于 intfloatcomplex……以及字符串和列表。

def arithmetic_progression(start, step, length):
    for i in xrange(length):
        yield start + i * step

请注意,这段代码比任何维护运行总数的替代方案更有可能使您的最后一个值接近预期值。
>>> 10000 * 0.0001, sum(0.0001 for i in xrange(10000))
(1.0, 0.9999999999999062)
>>> 10000 * (1/3.), sum(1/3. for i in xrange(10000))
(3333.333333333333, 3333.3333333337314)

更正一下:这里是一个高效的累加小工具

def kahan_range(start, stop, step):
    assert step > 0.0
    total = start
    compo = 0.0
    while total < stop:
        yield total
        y = step - compo
        temp = total + y
        compo = (temp - total) - y
        total = temp

>>> list(kahan_range(0, 1, 0.0001))[-1]
0.9999
>>> list(kahan_range(0, 3333.3334, 1/3.))[-1]
3333.333333333333
>>>

这是一个很棒的答案。为什么它没有得到比那些累积不精确或需要额外库的答案更多的赞? - josch

5
当你将浮点数相加时,通常会出现一些误差。一个range(0.0, 2.2, 1.1)会返回[0.0, 1.1, 2.199999999]还是[0.0, 1.1]?这要根据严格的分析才能确定。
你发表的代码是一个可以使用的解决方法,但是请注意可能存在的缺陷。

1
是的,有一种方法可以确定。当您的步长为1.1时,在内存中表示为双精度浮点数时,其确切值为0x1.199999999999ap+0。 OP发布的代码不是完美的解决方法,因为重复添加不精确的值会导致误差累积。更好的想法是在迭代期间跟踪循环编号,并像其他答案已经显示的那样将步长乘以循环编号。 - josch
@josch 我的意思是一般情况,而不是特定的那些数字。 - Mark Ransom
那么我们对“确定”的定义不同。即使是使用浮点数,计算机也不会给出“不确定”或“随机”的结果。结果始终是确定性的。无论输入什么,只要给定任何有限浮点数,您总是能够确切地知道在给定精度下将它们相加X次后的结果是什么。 - josch
@josch,根据您的反馈,我进行了一些编辑,谢谢。 - Mark Ransom
现在明白了,谢谢! - josch

3
这里有一个特殊的情况可能足够好:
 [ (1.0/divStep)*x for x in range(start*divStep, stop*divStep)]

在您的情况下,这将是:
#for(float x = 0; x < 10; x += 0.5f) { /* ... */ } ==>
start = 0
stop  = 10
divstep = 1/.5 = 2 #This needs to be int, thats why I said 'special case'

因此:

>>> [ .5*x for x in range(0*2, 10*2)]
[0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0, 5.5, 6.0, 6.5, 7.0, 7.5, 8.0, 8.5, 9.0, 9.5]

2
这是我会使用的内容:
numbers = [float(x)/10 for x in range(10)]

改为:

numbers = [x*0.1 for x in range(10)]
that would return :
[0.0, 0.1, 0.2, 0.30000000000000004, 0.4, 0.5, 0.6000000000000001, 0.7000000000000001, 0.8, 0.9]

希望这能有所帮助。

这不提供问题的答案。要评论或请求作者澄清,请在他们的帖子下留言 - 您可以随时在自己的帖子上发表评论,并且一旦您拥有足够的声望,您将能够在任何帖子上发表评论。 - Rais Alam
@RaisAlam 请注意,如果他使用0.5而不是0.1,就不会出现意外的行为。 - Cees Timmerman

-2

可能是因为你不能拥有一个可迭代对象的一部分。此外,浮点数 是不精确的。


浮点数非常精确。但它们不擅长表示在十进制下有限表示的某些值。例如,十进制下的0.1将变为0x1.999999999999ap-4(使用精确的十六进制浮点表示法)存储在内存中,这并不完全等于十进制下的0.1。基于这个论点,你也可以说十进制数是不精确的,因为它们有一些没有有限表示的值... - josch

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