如何有效地检索数字的第一个十进制数字
Posted
技术标签:
【中文标题】如何有效地检索数字的第一个十进制数字【英文标题】:How to retrieve the first decimal digit of number efficiently 【发布时间】:2013-06-30 18:53:58 【问题描述】:一个明显的解决方案是:
int n = 2134;
while(n > 9)
n /= 10;
这需要线性时间。我们能做得更快吗?
这是否比线性时间快:
char s[100];
sprintf(s, "%d", n);
n = s[0]-'0';
还有哪些其他方式(效率是首要考虑因素)? 我见过this,只是我只需要找到第一个数字。 (另外,我不明白答案)。
【问题讨论】:
您的第二个示例可能应该使用sprintf
而不是scanf
?
格式化 I/O 肯定比除法慢很多(数量级)。但是你对它进行了基准测试吗?另外,sscanf()
不应该真的是snprintf()
吗?
“我不明白答案”不是发布新问题的好理由,除非该答案实际上很糟糕。事实并非如此,它或多或少是 digits = floor(log10(n)); firstDigits = n/10^digits;
以及将 double
s 强制转换为整数类型的必要条件。
你能想出一个在数字的对数中不是线性的“第一位”的定义吗?!
关于算法和复杂性的一个有趣的事情是,您可以为同一个精确算法声明不同的渐近复杂性。您说除法方法是线性,或者如果您认为位数是有限的(在sizeof(int)==4
位于最多 10 个十进制数字)。只是说 linear 听起来可能比实际情况更糟......
【参考方案1】:
一些处理器有指令可以非常快速地计算出一个数字的“大小”(参见http://en.wikipedia.org/wiki/Leading_zero_count)。这可以用来快速选择 10 的幂,然后除以它,而不是重复除以 10。
假设您有一个函数clz
,它计算数字二进制表示(0...32)中前导零位的数量。然后,您可以使用一个查找表,为每个前导零数提供 10 的适当幂。
uint32_t powers_of_10[33] =
1000000000, 1000000000,
100000000, 100000000, 100000000,
10000000, 10000000, 10000000,
1000000, 1000000, 1000000, 1000000,
100000, 100000, 100000,
10000, 10000, 10000,
1000, 1000, 1000, 1000,
100, 100, 100,
10, 10, 10,
1, 1, 1, 1, 1
;
int CalcFirstDecimalDigit(uint32_t x)
int leading_zeros = clz(x);
x /= powers_of_10[leading_zeros];
if (x >= 10)
return 1;
else
return x;
【讨论】:
对于支持此类操作的处理器来说,这似乎是最快的解决方案。 非常小的建议:使用uint32_t powers_of_10[33]
。
恕我直言 - 查找表并不快。通常,它们需要访问 RAM,并且充其量是在缓存中 - 这仍然比在寄存器中工作要慢得多。【参考方案2】:
例如对于 32 位无符号:
第 1 步:(通过二分查找)确定该值位于以下哪个区间:
0 .. 9
10 .. 99
100 .. 999
1000 .. 9999
10000 .. 99999
100000 .. 999999
1000000 .. 9999999
10000000 .. 99999999
100000000 .. 999999999
1000000000 .. 4294967295
最多进行 4 次比较
第 2 步:
比以一个除法计算前导数。
【讨论】:
您可以对Step 1的二分查找进行硬编码,避免循环,提高速度。【参考方案3】:我很确定sprintf
(我认为是这样)会明显变慢。您可以进行一些优化以减少除法操作的数量(这是几乎所有处理器上最慢的指令之一)。
所以人们可以这样做:
while(n > 10000)
n /= 1000;
while(n >= 9)
n /= 10;
当然,如果速度真的很重要的话。
【讨论】:
这似乎就是libdivide
的本意。
如果速度那么很重要,那么对十次方 1, 10, 100,... 等进行二分搜索可能会更快。之后你只需要一个部门。您可以将二进制搜索硬编码为一系列测试和跳转,最好使用汇编语言。
好吧,对于 32 位,您将在第一个循环中执行最多 3 次迭代。对于 64 位数字来说,减少除法的数量变得更有意义。
while(n >= 9) ,对于 n=9,输出为 0 作为 9/10=0 ,而不是 9。应该是 while(n>=10) 。【参考方案4】:
您的第二个示例应使用sprintf
。无论如何,它不能更快,因为整个数字都被打印出来了,因此所有数字都被搜索了。
链接的问题/答案使用对数属性:对于多个 x
数字,它的以 10 为底的对数介于 x
和 x+1
之间。但是,由于浮点错误,这种方法在某些情况下并不能真正正常工作。另外,请考虑浮点运算比整数运算慢的事实。
因此,最简单的解决方案也更快。
【讨论】:
嗯,对数解不可能更快,因为它使用了对数函数。这比现代处理器上的任何浮点运算都要昂贵得多。所以,是的,这个小循环肯定比sprintf
或 log()
快,甚至比 MrSmith42 的答案还要快。【参考方案5】:
您可以在 O(1) 恒定时间内完成此操作,但会消耗大量内存。这是相同的旧时间/内存权衡。
您可以创建一个包含 2^31 个条目(有符号整数)的查找表,每个条目 4 位(使用 4 位您可以将数字的第一个数字 1-9 编码为十进制表示)。
然后您可以使用 int 访问查找表并获取 O(1) 中的第一个数字。 查找表将占用 2^31 * 4 位 -> 1024 MB
这是我能想到的最快的方法……
【讨论】:
-1 表示内存膨胀,由于内存延迟而抵消了任何性能优势。 @cmaster “内存膨胀”很明显,并且显示了如何达到 O(1)。这种方法在部分案例中非常实用。假设 int 范围被限制为 0...1000。这样的桌子大小是可行的。通常当需要高性能例程时,存在这样的限制。我不希望 OP 列出所有条件 - 只是主要条件。这种方法虽然对 GP 库没有用处,但可能只是 OP 或未来审阅者的门票。 对,我只是想证明一个人可以很容易地用记忆交换时间 另外,使用重复划分的天真方法在空间中是 O(1)。 @chux:什么是OP?什么是全科医生? 我知道用内存换取性能的可能性,但这不是这样做的地方。正如其他答案所指出的,您可以在不触及 L1 缓存的情况下分四步找到完整整数范围的正确答案,这肯定比等待内存子系统传递答案要快。选择更小的数字范围实际上会削弱你的论点,因为它只需要两步就可以达到 9999 的范围。这不是人们应该把内存扔到的那种问题。【参考方案6】:这是二分搜索的一种变体。就像二进制搜索一样,它是 O(log n)。是否更快取决于您进行整数除法的速度。
if (n >= 100000000)
n /= 100000000
if (n >= 10000)
n /= 10000
if (n >= 100)
n /= 100
if (n >= 10)
n /= 10
该方法很容易扩展到更大范围的整数。
【讨论】:
【参考方案7】:你可以这样做:
//Shashank Jain
#include<iostream>
#include<cmath>
using namespace std;
int main()
int num,fdigit;
cin>>num;
if(num<0)
num*=-1;
int l=log10(num); // l = (length of number -1)
fdigit=num/pow(10,l);
cout<<fdigit<<endl;
return 0;
【讨论】:
谢谢。但它有时并不真正起作用。请参阅 Mihai Maruseac 的上述答案。【参考方案8】:int FirstDigit ( int Number )
// to obtain the <number of digits -1> use this math formula:
int DigitsInNumber = (int) lg(Number);
long TenExpon = pow(10, DigitsInNumber);
return (Number / TenExpon); //first digit
还有:lg(n) = ln(n) / ln(10);
【讨论】:
浮点计算不精确;舍入错误可能导致 10 次方的结果不正确(例如,10000
在我的机器上产生不正确的结果)。幸运的是,它很容易修复。
(由于不接受编辑而重命名变量,仅使用“改进”格式)【参考方案9】:
您的第一个解决方案(假设已知 n >= 0)几乎是最优的,我认为它只能通过使用内联汇编语言来大幅改进。但这只有在处理数百万个此类数字时才值得。
您的第二个解决方案是——我怎样才能把它很好地表达出来? ——更像是一种 Java 风格的方法:性能?拉迪达,谁在乎……
【讨论】:
【参考方案10】: for(int i=0; i<n; i++)
e=5; //Specify the number of digits in the number OR Exponential value of 10
while(e>=0)
tenp=pow(10,e); //#include <math.h>
if(arr[i]/tenp!=0)
q[i][e]=arr[i]/tenp%10;
e--;
但是,这段代码的复杂度应该是 O(n^2),这是不可取的。
【讨论】:
【参考方案11】:另一种解决方案: 以 BCD 格式存储所有值,大端。访问第一个半字节以获得第一个数字
Es.
Decimal value: 265
BCD format: 0010-0110-0101
【讨论】:
【参考方案12】:首先制作一个保存数字的双变量。然后将该数除以 10 循环使该数连续丢失一位数,直到成为一位数。将 double 变量转换为 int 以将其四舍五入。结果将是之前的第一个十进制数字。
代码将以 O(log(n)) 运行。
#include <iostream>
using namespace std;
int main()
double a;
cin >> a;
while (true)
a /= 10;
if (a < 10)
a = int(a);
cout << a;
break;
return 0;
【讨论】:
【参考方案13】:如果你的数字是 x9x8x7x6x5x4x3x2x1 那么你只需除以 10^8 所以你需要:找到有多少位数的最佳方法。 你可以使用二分搜索/
【讨论】:
以上是关于如何有效地检索数字的第一个十进制数字的主要内容,如果未能解决你的问题,请参考以下文章