Python 取最接近的 10 的整数幂

52
我如何使用math.ceil来将数字分配给下一个最高的10的幂次方?
# 0.04  ->  0.1
# 0.7   ->  1
# 1.1   ->  10  
# 90    ->  100  
# ...

我目前的解决方案是一个字典,它检查输入数字的范围,但它是硬编码的,我更喜欢一个一行的解决方案。也许我在这里错过了一个简单的数学技巧或相应的numpy函数吗?


3
看起来那些解决方案适用于10及以上的数字,这将需要使用例如log10的内容。 - jonrsharpe
3
你想要的词是“power”。也许你对母语中这个词的翻译有误。 - user2357112
谢谢,Monica!@bold: 我找到了这个问题,但是它是一个不同的问题。Jonrsharpe提供了一个完美的答案。 - offeltoffel
2
这也与数量级有关。1是0阶,10是1阶,100是2阶等等。 - wjandrea
7个回答

70

你可以使用math.ceilmath.log10来完成此操作:

>>> 10 ** math.ceil(math.log10(0.04))
0.1
>>> 10 ** math.ceil(math.log10(0.7))
1
>>> 10 ** math.ceil(math.log10(1.1))
10
>>> 10 ** math.ceil(math.log10(90))
100

log10(n)函数给出的解 x 满足 10 ** x == n,因此如果您将 x 向上取整,它会给出下一个最高的10的幂的指数。

请注意,对于值为整数的 n"下一个最高的10的幂"也将是 n

>>> 10 ** math.ceil(math.log10(0.1))
0.1
>>> 10 ** math.ceil(math.log10(1))
1
>>> 10 ** math.ceil(math.log10(10))
10

1
使用日志函数似乎是我无法想出的诀窍。我相信这正是我所希望的!非常感谢。 - offeltoffel
2
注意:根据您所需的行为,这对于10的幂不起作用,例如10 ** math.ceil(math.log10(1)) == 1,这不是“下一个最高的幂”。 - Cireo
7
请注意:该答案依赖于浮点数算术,因此可能会由于舍入误差而失败。例如,尝试输入1000000000000001。注:本回答使用浮点运算,因此可能会因舍入误差而出错。例如,尝试输入1000000000000001。 - plugwash
2
@plugwash 不一定,数学函数也可以接受例如 decimal.Decimals。 - jonrsharpe
6
是的,您可以传递其他类型的参数,但它们将被转换为双精度浮点数并传递给C语言中的"log10"函数。有一种特殊情况来防止大数的对数溢出,但没有任何方法来防止舍入误差。 - plugwash
显示剩余5条评论

25

你的问题没有给出足够的信息,需要回过头来询问一些问题。

  • 你的输入是哪种类型?
  • 你想要什么类型的输出?
  • 对于小于1的结果,你想要四舍五入到什么地方?你是想要10的实际幂次方还是浮点数近似值?你知道负幂次方在浮点数中不能被精确表示吗?我们先假设你想要浮点数的近似值。
  • 如果输入恰好是10的幂次方(或最接近10的幂次方的浮点数近似值),输出应该与输入相同吗?或者应该是更高一级的10的幂次方?比如“10 -> 10”还是“10 -> 100”?我们先假设前者。
  • 你的输入值可以是任何可能的类型值吗?还是有更多限制。

另一个答案建议取对数,然后向上取整(ceiling函数),最后求指数。

def nextpow10(n):
    return 10 ** math.ceil(math.log10(n))

不幸的是,这会遭受舍入误差的影响。首先,将n从任何数据类型转换为双精度浮点数,可能会引入舍入误差,然后计算对数,这可能会在内部计算和结果中引入更多的舍入误差。

因此,我很快就找到了一个例子,它给出了错误的结果。

>>> import math
>>> from numpy import nextafter
>>> n = 1
>>> while (10 ** math.ceil(math.log10(nextafter(n,math.inf)))) > n:
...     n *= 10
... 
>>> n
10
>>> nextafter(n,math.inf)
10.000000000000002
>>> 10 ** math.ceil(math.log10(10.000000000000002))
10

理论上也有可能它会失败并导致相反的结果,但这似乎更难引发。

因此,为了得到浮点数和整数的强大解决方案,我们需要假设我们的对数值只是近似的,并且因此必须测试几种可能性。类似以下方式:

def nextpow10(n):
    p = round(math.log10(n))
    r = 10 ** p
    if r < n:
        r = 10 ** (p+1) 
    return r;

我相信这段代码在合理的实际数量范围内,对所有参数都能给出正确的结果。由于类型转换问题,当参数是非整数且非浮点数类型的极小或极大值时,它会出现问题。Python会特殊处理log10函数中的整数参数,以尝试防止溢出,但仍然可能因舍入误差导致强制出现不正确的结果。

为了测试这两个实现,我使用了以下测试程序。

n = -323 # 10**-324 == 0
while n < 1000:
    v = 10 ** n
    if v != nextpow10(v): print(str(v)+" bad")
    try:
        v = min(nextafter(v,math.inf),v+1)
    except:
        v += 1
    if v > nextpow10(v): print(str(v)+" bad")
    n += 1

这个测试发现了天真实现中的许多失败,但在改进后的实现中没有发现任何失败。


感谢您在这里更加详细地解释。虽然jonrsharpe的答案已经解决了我的问题,但是这个答案可能对其他有类似但更具体问题的人有用。 - offeltoffel
1
为什么你使用 round 而不是 math.ceil?这会引入许多不必要的情况,其中 r < n 为真,因此需要执行额外的工作。 - a_guest
1
由于日志可能朝任何方向偏移。 - plugwash

4

看起来你想要的是比当前数小的下一个10的次幂......这里有一种使用纯数学和递归而不使用对数的方法。

def ceiling10(x):
    if (x > 10):
        return ceiling10(x / 10) * 10
    else:
        if (x <= 1):
            return ceiling10(10 * x) / 10
        else:
            return 10
for x in [1 / 1235, 0.5, 1, 3, 10, 125, 12345]:
    print(x, ceiling10(x))

0

对于已经以字符串格式出现的大整数,有一个特定的快捷方式可用:

而不必先将其转换为整数,或运行它通过 log()/ceiling() 函数,或执行任何类型的模数数学运算,下一个最大的10的幂次方就是:

10 ** length(big_int_str_var)

——以下:第一个生成格式化的10次幂字符串,第二个是数字

echo 23958699683561808518065081866850688652086158016508618152865101851111111111111 | 
tee >( gpaste | gcat -n >&2; ) | gcat - |
 
python3 -c '\
import sys; [ print("1"+"0"*len(_.strip("\n"))) for _ in sys.stdin ]' 
  
  or   '... [ print(  10 ** len(_.strip("\n"))) for _ in sys.stdin ]' 
1 23958699683561808518065081866850688652086158016508618152865101851111111111111

1 100000000000000000000000000000000000000000000000000000000000000000000000000000

0

看看这个!

>>> i = 0.04123
>>> print i, 10 ** len(str(int(i))) if int(i) > 1 else 10 if i > 1.0 else 1 if i > 0.1 else 10 ** (1 - min([("%.100f" % i).replace('.', '').index(k) for k in [str(j) for j in xrange(1, 10) if str(j) in "%.100f" % i]]))               
0.04123 0.1
>>> i = 0.712
>>> print i, 10 ** len(str(int(i))) if int(i) > 1 else 10 if i > 1.0 else 1 if i > 0.1 else 10 ** (1 - min([("%.100f" % i).replace('.', '').index(k) for k in [str(j) for j in xrange(1, 10) if str(j) in "%.100f" % i]]))                 
0.712 1
>>> i = 1.1
>>> print i, 10 ** len(str(int(i))) if int(i) > 1 else 10 if i > 1.0 else 1 if i > 0.1 else 10 ** (1 - min([("%.100f" % i).replace('.', '').index(k) for k in [str(j) for j in xrange(1, 10) if str(j) in "%.100f" % i]]))                   
1.1 10
>>> i = 90
>>> print i, 10 ** len(str(int(i))) if int(i) > 1 else 10 if i > 1.0 else 1 if i > 0.1 else 10 ** (1 - min([("%.100f" % i).replace('.', '').index(k) for k in [str(j) for j in xrange(1, 10) if str(j) in "%.100f" % i]]))                    
90 100

这段代码基于十的幂原理,使用 len(str(int(float_number)))

有四种情况:

    1. int(i) > 1

      Float 数字 - 转换为 int,然后从中获取字符串 str(),将会给我们一个恰好符合要求的 stringlength。因此,对于输入 i > 1.0 的第一部分,它是这个长度的十的幂。

    1. & 3. 小分支:当 i > 1.0i > 0.1 时,分别等于 101
    1. 最后一种情况是当 i < 0.1 时:这里,十应该是负数幂。为了获得逗号后的第一个非零元素,我使用了这样的结构:("%.100f" % i).replace('.', '').index(k),其中 k 在区间 [1:10] 上运行。然后,取结果列表的最小值。并减去一,这是第一个零,应该被计算。此外,在这里标准的 Python 的 index() 可能会崩溃,如果它在区间 [1:10] 中找不到至少一个非零元素,这就是为什么最后我必须通过出现次数来“过滤”列表:if str(j) in "%.100f" % i。此外,为了获得更深入的精度 - 可以采用不同的 %.100f

Add some explanation please. - Mobin Ranjbar

0

我认为最简单的方法是:

import math


number = int(input('Enter a number: '))
next_pow_ten = round(10 ** math.ceil(math.log10(number)))
print(str(10) + ' power ' + str(round(math.log10(number))) + ' = '\
      + str(next_pow_ten))

希望这能帮到你。


-1
y = math.ceil(x)
z = y + (10 - (y % 10))

像这样的东西吗?我随口说了一下,但在终端中试了几个数字后它就起作用了。


这将四舍五入到下一个10的倍数,但问题要求四舍五入到下一个10的幂。例如,42应该四舍五入到100,1234应该四舍五入到10000。 - Marius Gedminas

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