查找小数位数和的最快方法
Posted
技术标签:
【中文标题】查找小数位数和的最快方法【英文标题】:Fastest way to find the sum of decimal digits 【发布时间】:2012-01-23 05:34:22 【问题描述】:求十进制数字和的最快方法是什么?
以下代码是我写的,但对于范围1 to 1000000000000000000
来说非常慢
long long sum_of_digits(long long input)
long long total = 0;
while (input != 0)
total += input % 10;
input /= 10;
return total;
int main ( int argc, char** argv)
for ( long long i = 1L; i <= 1000000000000000000L; i++)
sum_of_digits(i);
return 0;
【问题讨论】:
分解输入,多线程,然后合并结果? 似乎不会那么慢——您需要多快? 如果这在您的计算机上花费超过一秒钟,我想知道您是如何在不到 100 年的时间内编译它的。 一台计算机在合理的时间内不能做任何十亿次的事情。也许你对要求有误解。 @Avinash:现在您已经添加了 main 函数,我想彻底修改我的声明。即使您可以编写一些能够在单个周期内执行的魔术函数,并且您拥有一台每台 4ghz 的 6 核计算机,您的代码也不可能花费不到一年的时间来执行。算一下:1000000000000000000 个周期/(4 GHz * 6 个内核) 【参考方案1】:我假设您正在尝试做的事情是
#include <iostream>
const long long limit = 1000000000000000000LL;
int main ()
long long grand_total = 0;
for (long long ii = 1; ii <= limit; ++ii)
grand_total += sum_of_digits(i);
std::cout << "Grand total = " << grand_total << "\n";
return 0;
这不起作用有两个原因:
这将需要很长时间。 会溢出。要处理溢出问题,您要么必须设置上限,要么使用一些 bignum 包。这个问题的解决就交给你了。
要处理您需要发挥创意的计算负担。如果您知道上限限制为 10 的幂,这相当容易。如果上限可以是任意数字,您将不得不更有创意。
先看计算0到10的所有整数位数之和的问题n-1(例如0到9(n=1),0到99(n=2) ) 等) 将 10n-1 的所有整数的位数之和表示为 Sn。对于 n=1(0 到 9),这只是 0+1+2+3+4+5+6+7+8+9=45(9*10/2)。因此S1=45。
对于 n=2(0 到 99),您将 0-9 相加十次,然后再将 0-9 相加十次。对于 n=3(0 到 999),您将 0-99 相加十次,并且您将 0-9 相加 100 次。对于 n=4(0 到 9999),您将 0-999 相加十次,并且您将 0-9 相加 1000 次。一般来说,Sn=10Sn-1+10n-1S1作为递归表达式。这简化为 Sn=(9n10n)/2。
如果上限是 10n 的形式,则解决方案是上面的 Sn 加上一个数字 1000...000。如果上限是任意数字,您将需要再次发挥创意。按照开发 Sn 公式的思路进行思考。
【讨论】:
【参考方案2】:您可以递归地分解它。 18位数字的数字之和是前9位数字加上后9位数字的总和。同样,9 位数字的数字总和将是前 4 或 5 位数字的总和加上后 5 或 4 位数字的总和。当然,当值为 0 时,您可以进行特殊情况。
【讨论】:
很难看出这对解决问题有何帮助(将所有数字中的数字相加直到 n)。这涉及单个数字的数字总和,但对于那个询问者的代码来说就很好了。 - 对于这个问题,它可能有助于不将数字分成两半,但也许将 18 位数字中的数字总和视为通过 magic 操作组合的 magic 与数字 1-9 i> 17 位数字中的数字总和.【参考方案3】:阅读您的编辑:在 i 1 到 1000000000000000000 之间循环计算该函数需要很长时间。这很简单。
1000000000000000000 是十亿。您的处理器每秒最多可以执行数十亿次操作。即使使用不存在的 4-5 Ghz 处理器,并且假设最好的情况下它编译为一个 add、一个 mod、一个 div 和一个比较跳转,你每秒也只能进行 10 亿次迭代,这意味着它将采取大约10 亿秒。
【讨论】:
【参考方案4】:您可能不想以蛮力的方式进行操作。这似乎更像是一个逻辑思维问题。
注 - 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 = N(N+1)/2 = 45。
---- 在大卫发表评论后更改答案以使其更清楚
查看大卫的回答 - 我错了
【讨论】:
-1:不希望以蛮力方式执行此操作的好主意,实施的坏主意。从 1 到 10^n 的数字和的正确公式是 1+(9*n*10^n)/2。您的公式给出 541 和 5041 作为 1 到 100 和 1 到 1000 的总和。正确的值是 901 和 13501。 你可能是对的..我没有按照并完成计算。你的公式有证据吗?在我看来,它非常像 1 + 45 ( 1 + 10 + .... + 10^n-1) = 1 + 45 ( 10^n - 1) / 9 = 1 + 5 (10^n -1 ) @David,我的证明已经更清楚了。如果你发现逻辑上的缺陷,请告诉我。 仍然不正确。看我的回答。正确的公式是 1+(9*n*10^n)/2。 你能解释一下你是怎么得到这个公式的吗?【参考方案5】:聚会迟到了,但无论如何,这是我的解决方案。抱歉,它是 Python 而不是 C++,但它应该相对容易翻译。因为这主要是一个算法问题,我希望没问题。
至于溢出问题,唯一想到的是使用数字数组而不是实际数字。鉴于此算法,我希望它不会对性能产生太大影响。
https://gist.github.com/frnhr/7608873
它使用了我通过查看和探究问题找到的这三个递归。这里有三个例子,而不是试图提出一些一般和神秘的方程。从这些案例中应该很容易看到一般案例。
关系 1
将具有任意参数的函数调用减少为具有更多可预测参数的递归调用,以便在关系 2 和 3 中使用。
foo(3456) == foo(3000)
+ foo(400) + 400 * (3)
+ foo(50) + 50 * (3 + 4)
+ foo(6) + 6 * (3 + 4 + 5)
关系 2
使用L*10^M
(例如:30、7000、900000)形式的参数将调用减少为可用于关系 3 的递归调用。这些triangular numbers 非常不受欢迎(但受欢迎)突然出现:)
triangular_numbers = [0, 1, 3, 6, 10, 15, 21, 28, 36] # 0 not used
foo(3000) == 3 * foo(1000) + triangular_numbers[3 - 1] * 1000
仅在L > 1
时有用。它适用于L = 1
,但微不足道。在这种情况下,直接转到关系 3。
关系 3
递归地将带有参数的调用以1*10^M
格式减少为带有除以 10 的参数的调用。
foo(1000) == foo(100) * 10 + 44 * 100 + 100 - 9 # 44 and 9 are constants
最终,您只需要真正计算数字 0 到 10 的总和或数字,结果证明,这些计算最多只需要 3 次。这个递归处理了其他所有事情。我很确定它会在O(logN)
时间运行。太棒了!!!!!!11个
在我的笔记本电脑上,它可以在 7 秒内计算出超过 1300 位数字的给定数字的总和!您的测试 (1000000000000000000) 的计算时间为 0.000112057 秒!
【讨论】:
【参考方案6】:我认为你不能比O(N)
做得更好,其中N is the number of digits in the given number
(计算成本并不高)
但是,如果我正确理解了您的问题(范围),您希望输出一系列数字的数字总和。在这种情况下,您可以在从 number0 到 number9 时加 1,然后再减 8。
【讨论】:
最后一段对我来说没有意义。此外,虽然他不能比 O(N) 做得更好,但他当然可以让它更快或更慢。【参考方案7】:你需要作弊 - 寻找可以让你的计算捷径的数学模式。
例如,您真的需要每次都测试输入 != 0 吗?多次添加 0/10 有关系吗?既然没关系,请考虑展开循环。 您能否在更大的基数(例如基数 10^2、10^3 等)中进行计算,这可能允许您减少位数,然后您必须将其转换回基数 10?如果这可行,您将能够更轻松地实现缓存。 考虑查看编译器内在函数,它可以为编译器提供分支预测提示。 鉴于这是 C++,请考虑使用模板元编程来实现它。 鉴于 sum_of_digits 纯粹是函数式,请考虑缓存结果。现在,这些建议中的大多数都会适得其反 - 但我要说的是,如果您已经达到了计算机对给定算法所能做的极限,那么您确实需要找到不同的解决方案。
如果您想详细调查,这可能是一个很好的起点:http://mathworld.wolfram.com/DigitSum.html
【讨论】:
在进行所有的微优化之前,也许首先测量将数字相加到例如 100,000,000 所需的时间,然后将其乘以 1,000,000,000 以粗略估计该算法需要多长时间将采取? - 如果某件事需要一百万年,微优化最多可以将其缩短到 10 万年。 @visitor:他不会已经这样做了吗?他在问题中的 main() 不是展示他想要的那种性能的例子吗? 我不明白。您是否建议任何数量的微优化都会让 OP 在人类生命周期内的某个有意义的时间内执行 1000000000000000000L 操作? - 这个问题不能用sum_of_digits(n)
函数解决,无论你如何优化它。相反,您需要类似 sum_of_digits_in_n_digit_numbers(d)
函数。
@UncleBens:除非他做了某种形式的惰性评估,以便在概念上完成这些优化,但直到有必要时才实际评估。他需要作弊。【参考方案8】:
可能性一:
您可以通过将循环的一次迭代的结果提供给下一次迭代来使其更快。
例如,如果i == 365
,则结果为14
。在下一个循环中,i == 366
-- 比上一个结果多 1 个。总和还多了 1 个:3 + 6 + 6 = 15
。
当有进位数字时会出现问题。如果i == 99
(即result = 18),下一个循环的结果不是19,而是1。你需要额外的代码来检测这种情况。
可能性2:
在考虑上述情况时,我想到sum_of_digits
的结果序列在绘制时类似于锯齿。通过对结果图的一些分析(我将其作为练习留给读者),也许可以确定一种方法来允许直接计算总和结果。
但是,正如其他人指出的那样:即使使用最快的sum_of_digits
实现和最优化的循环代码,您也不可能在任何有用的时间范围内计算出 1000000000000000000 个结果,而且肯定不会少于一秒.
【讨论】:
【参考方案9】:编辑:您似乎想要实际数字的总和,这样:12345 = 1+2+3+4+5 不是数字的计数,也不是所有数字的总和 1 到 12345(含);
因此,您可以获得的最快速度是:
long long sum_of_digits(long long input)
long long total = input % 10;
while ((input /= 10) != 0)
total += input % 10;
return total;
当您运行足够多的迭代时,这仍然会很慢。您对 1,000,000,000,000,000,000L 次迭代的要求是一百万,百万,百万。假设 1 亿次在我的计算机上大约需要 10,000 毫秒,可以预期每 100 万条记录需要 100 毫秒,而您还想再做一百万次。一天只有 86400 秒,所以我们每天最多可以计算大约 864 亿条记录。需要一台电脑
假设您的方法可以在单个浮点运算中执行(不知何故),假设您使用的 K 计算机是目前最快 (Rmax) 的超级计算机,运算速度超过 10 petaflops,如果您进行的数学运算是 = 10,000 百万每秒百万次浮动操作。这意味着您的 1,000,000,000 万循环将花费世界上最快的非分布式超级计算机 100 秒来计算总和(如果需要 1 个浮点运算来计算,它不能),所以您需要等待在相当长的一段时间内,计算机变得强大了 100 倍,以至于您的解决方案可以在一秒钟内运行。
无论您尝试做什么,您要么是在尝试近乎实时地解决无法解决的问题(例如:与图形计算相关),要么是您误解了给您的问题/任务,或者您是预期的执行比任何(非分布式)计算机系统都快的事情。
如果您的任务实际上是对显示的范围内的所有数字求和然后输出它们,那么答案不是改进 for 循环。例如:
1 = 0
10 = 46
100 = 901
1000 = 13501
10000 = 180001
100000 = 2250001
1000000 = 27000001
10000000 = 315000001
100000000 = 3600000001
据此,您可以计算出一个公式来实际计算从 1 到 N 的所有数字的所有数字的总和。但除了速度更快的计算机之外,您还不清楚您真正想要什么。
【讨论】:
这将是一个数字计数,而不是一个总和。 OP 需要更具体一些,这里的几乎每个答案都有相同的解决方案.. "N*(N+1)/2" 答案已更新以匹配问题的“实际”问题。 +1:编辑后的答案似乎是在解决问题的“实际”问题,即计算 1 和 N(含)之间所有整数的数字之和。 @Seph 说“你能得到的最快的是”意味着你有一个数学证明,证明你的算法是最好的,或者至少任何其他算法只会那么快而不是更快。这是一个沉重的主张。它也把你变成了一个有缺陷的人:)哦,+1 实际解决了 OP 问题【参考方案10】:没有最好,但很简单:
int DigitSumRange(int a, int b)
int s = 0;
for (; a <= b; a++)
for(c : to_string(a))
s += c-48;
return s;
【讨论】:
【参考方案11】:下面给出了一个 Python 函数,它将数字转换为字符串,然后转换为数字列表,然后找到这些数字的总和。
def SumDigits(n):
ns=list(str(n))
z=[int(d) for d in ns]
return(sum(z))
【讨论】:
【参考方案12】:在 C++ 中,最快的方法之一是使用字符串。 首先从字符串中获取用户的输入。然后将字符串的每个元素转换为int后添加。可以使用 -> (str[i] - '0') 来完成。
#include<iostream>
#include<string>
using namespace std;
int main()
string str;
cin>>str;
long long int sum=0;
for(long long int i=0;i<str.length();i++)
sum = sum + (str[i]-'0');
cout<<sum;
【讨论】:
请您编辑您的回复 1) 不要使用 bits/stdc++.h,因为它是一个不好的示例,以及 2) 以便很好地缩进?【参考方案13】:求1到N之间数字之和的公式为:
(1 + N)*(N/2)
http://mathforum.org/library/drmath/view/57919.html
有一个用 C# 编写的类,它支持超过支持的最大长度的数字。 你可以在这里找到它。 Oyster.Math
使用这个类,我用c#生成了一段代码,可能对你有一些帮助。
using Oyster.Math;
class Program
private static DateTime startDate;
static void Main(string[] args)
startDate = DateTime.Now;
Console.WriteLine("Finding Sum of digits from 0 to 1", 1L, 1000000000000000000L);
sum_of_digits(1000000000000000000L);
Console.WriteLine("Time Taken for the process: 0,", DateTime.Now - startDate);
Console.ReadLine();
private static void sum_of_digits(long input)
var answer = IntX.Multiply(IntX.Parse(Convert.ToString(1 + input)), IntX.Parse(Convert.ToString(input / 2)), MultiplyMode.Classic);
Console.WriteLine("Sum: 0", answer);
如果与您的上下文无关,请忽略此评论。
【讨论】:
这是 numbers 1 到 100 的总和,而不是特定数字的 digits 的总和。【参考方案14】:如果您想找到范围为 1 到 N 的总和,那么只需执行以下操作
long sum = N(N+1)/2;
这是最快的方法。
【讨论】:
我认为他想找到数字本身的总和,而不是数字。 在匆忙回答错误问题之前请先分析问题。以上是关于查找小数位数和的最快方法的主要内容,如果未能解决你的问题,请参考以下文章