有没有一种快速的方法来找到一个给定数字小于最大的10的幂次方?
目前我正在使用这个算法,但每次看到它都会内心崩溃:
10**( int( math.log10(x) ) ) # python
pow( 10, (int) log10(x) ) // C
我可以使用一个循环分别实现简单的log10
和pow
函数来解决我的问题,但我仍然想知道是否有针对十进制数字的一些位运算技巧。
有没有一种快速的方法来找到一个给定数字小于最大的10的幂次方?
目前我正在使用这个算法,但每次看到它都会内心崩溃:
10**( int( math.log10(x) ) ) # python
pow( 10, (int) log10(x) ) // C
我可以使用一个循环分别实现简单的log10
和pow
函数来解决我的问题,但我仍然想知道是否有针对十进制数字的一些位运算技巧。
i = 1;
while((i * 10) < x)
i *= 10;
记录日志和求幂是代价昂贵的操作。如果您想要快速,您可能需要查找IEEE二进制指数表以获取十的近似幂次,然后检查尾数是否强制进行+1变化。这应该是3或4个整数机器指令(或具有相当小的常数的O(1))。
给定表格:
int IEEE_exponent_to_power_of_ten[2048]; // needs to be 2*max(IEEE_exponent)
double next_power_of_ten[600]; // needs to be 2*log10(pow(2,1024)]
// you can compute these tables offline if needed
for (p=-1023;p>1023;p++) // bounds are rough, see actual IEEE exponent ranges
{ IEEE_exponent_to_power_of_ten[p+1024]=log10(pow(2,p)); // you might have to worry about roundoff errors here
next_power_of_ten[log10(pow(2,p))+1024]=pow(10,IEEE_exponent_to_power_of_ten[p+1024]);
}
power_of_ten=IEEE_exponent_to_power_of_10[IEEE_Exponent(x)+1023];
if (x>=next_power_of_ten[power_of_ten]) power_of_ten++;
answer=next_power_of_ten[power_of_ten];
[你可能需要使用汇编语言来尽可能地提高速度。] [此代码未经过测试。]
然而,如果您坚持要用Python编写,解释器的开销可能会淹没对数/指数运算所需的时间,这时速度可能并不重要。
所以,您想要快速还是想要写得简短?
编辑12/23:原帖现在告诉我们他的"x"是整数。 在假设它是64(或32)位整数的情况下,我的建议仍然有效,但显然没有“IEEE_Exponent”。 大多数处理器都有一个“查找第一个1”的指令,可以告诉您值左侧(最高有效位)部分的0位数,例如前导零;您可能需要将其转换为64(或32)减去该值的2的幂次方。 假设exponent = 64 - leadingzeros,则具有2的幂次方指数,大部分算法基本上没有改变(修改留给读者)。
如果处理器没有查找第一个1的指令,那么最好的选择可能是使用平衡判别树来确定10的幂次。 对于64位,这样的树最多需要18次比较才能确定指数(10^18 ~~ 2^64)。
func LogFloor(int value, int base) as int
//iterates values of the form (value: base^(2^i), power: 2^i)
val superPowers = iterator
var p = 1
var c = base
while c <= value
yield (c, p)
c *= c
p += p
endwhile
enditerator
//binary search for the correct power
var p = 0
var c = 1
for val ci, pi in superPowers.Reverse()
if c*ci <= value
c *= ci
p += pi
endif
endfor
return p
我认为最快的方法是O(log(log(n))^2),while循环需要O(log(log(n))的时间,它可以递归调用有限次数(我们可以说O(c),其中c是常数),第一次递归调用需要log(log(sqrt(n)))的时间,第二次需要...而sqrt(sqrt(sqrt....(n)) < 10中sqrt的数量是log(log(n))和常数,因为受到机器限制。
static long findPow10(long n)
{
if (n == 0)
return 0;
long i = 10;
long prevI = 10;
int count = 1;
while (i < n)
{
prevI = i;
i *= i;
count*=2;
}
if (i == n)
return count;
return count / 2 + findPow10(n / prevI);
}
10 ** (len(str(int(x))) - 1)
a
的size_t
类型(内联改善了性能但不改变相对顺序)。size_t try1( size_t a )
{
size_t scalar = 1ul;
while( scalar * 10 < a ) scalar *= 10;
return scalar;
}
size_t try2( size_t a )
{
return ( a < 10ul ? 1ul :
( a < 100ul ? 10ul :
( a < 1000ul ? 100ul :
( a < 10000ul ? 1000ul :
( a < 100000ul ? 10000ul :
( a < 1000000ul ? 100000ul :
( a < 10000000ul ? 1000000ul :
( a < 100000000ul ? 10000000ul :
( a < 1000000000ul ? 100000000ul :
( a < 10000000000ul ? 1000000000ul :
( a < 100000000000ul ? 10000000000ul :
( a < 1000000000000ul ? 100000000000ul :
( a < 10000000000000ul ? 1000000000000ul :
( a < 100000000000000ul ? 10000000000000ul :
( a < 1000000000000000ul ? 100000000000000ul :
( a < 10000000000000000ul ? 1000000000000000ul :
( a < 100000000000000000ul ? 10000000000000000ul :
( a < 1000000000000000000ul ? 100000000000000000ul :
( a < 10000000000000000000ul ? 1000000000000000000ul :
10000000000000000000ul )))))))))))))))))));
}
尝试3:修改自@Saaed Amiri的findPow10,使用平方来更快地找到比尝试1更大的幂。
size_t try3( size_t a )
{
if (a == 0)
return 0;
size_t i, j = 1;
size_t prev = 1;
while( j != 100 )
{
i = prev;
j = 10;
while (i <= a)
{
prev = i;
i *= j;
j *= j;
}
}
return prev;
}
static const std::array<size_t,64> ltable2{
1ul, 1ul, 1ul, 1ul, 1ul, 10ul, 10ul, 10ul,
100ul, 100ul, 100ul, 1000ul, 1000ul, 1000ul,
1000ul, 10000ul, 10000ul, 10000ul, 100000ul,
100000ul, 100000ul, 1000000ul, 1000000ul,
1000000ul, 1000000ul, 10000000ul, 10000000ul,
10000000ul, 100000000ul, 100000000ul,
100000000ul, 1000000000ul, 1000000000ul,
1000000000ul, 1000000000ul, 10000000000ul,
10000000000ul, 10000000000ul, 100000000000ul,
100000000000ul, 100000000000ul, 1000000000000ul,
1000000000000ul, 1000000000000ul, 1000000000000ul,
10000000000000ul, 10000000000000ul, 10000000000000ul,
100000000000000ul, 100000000000000ul, 100000000000000ul,
1000000000000000ul, 1000000000000000ul, 1000000000000000ul,
1000000000000000ul, 10000000000000000ul, 10000000000000000ul,
10000000000000000ul, 100000000000000000ul, 100000000000000000ul,
100000000000000000ul, 100000000000000000ul, 1000000000000000000ul,
1000000000000000000ul };
size_t try4( size_t a )
{
if( a == 0 ) return 0;
size_t scalar = ltable2[ 64 - __builtin_clzl(a) ];
return (scalar * 10 > a ? scalar : scalar * 10 );
}
时间安排如下(gcc 4.8)
for( size_t i = 0; i != 1000000000; ++i) try1(i) 6.6
for( size_t i = 0; i != 1000000000; ++i) try2(i) 0.3
for( size_t i = 0; i != 1000000000; ++i) try3(i) 6.5
for( size_t i = 0; i != 1000000000; ++i) try4(i) 0.3
for( size_t i = 0; i != 1000000000; ++i) pow(10,size_t(log10((double)i)))
98.1
在C++中,查找/多路if是最快的,但需要我们知道整数是有限大小的。对于循环结束值较小的情况,try3
比try1
慢,在处理大数字时,try3
胜过try1
。在Python中,由于整数没有限制,因此我会将try2
与try3
结合起来,快速处理固定范围内的数字,然后处理可能非常大的数字。
在Python中,我认为使用列表推导式进行查找可能比多路if更快。
# where we previously define lookuptable = ( 1, 10, 100, ..... )
scalar = [i for i in lookuptable if i < a][-1]
鉴于这是与语言无关的,如果您可以获得该数字所涉及的二次幂,例如 x*2^y 中的 y(这是存储该数字的方式,尽管我不确定是否有一种简单的方法来在我使用过的任何语言中访问 y),那么如果
z = int(y/(ln(10)/ln(2)))
(一个浮点数除法)
答案将是10^z或10^(z+1),尽管10^z仍然不太简单(请斧正)。
10 **(int(math.log10(987654321987654321)))
需要0.0161微秒,实际上相当令人印象深刻。 - Rafe Kettlerx
的大小。您可能需要对混合方法进行分析。 - Nick Larsenx
是一个整数,但我猜它不会有太大改变(可以将浮点类型的x
强制转换为整型值,反之亦然)。 - peoro