【问题标题】:Fastest method for adding/summing the individual digit components of a number将数字的各个数字分量相加/求和的最快方法
【发布时间】:2013-02-19 05:24:42
【问题描述】:

不久前,我在数学论坛上看到一个问题,有人在讨论一遍又一遍地将数字中的数字相加,直到得到一个数字。 (即“362”将变为“3+6+2”,将变为“11”......然后“11”将变为“1+1”将变为“2”,因此“362”将返回 2......我写了一些很好的代码来得到这个答案并发布它只是被一个用户超越,他建议模9中的任何数字都等于这个“无限数字和”,我检查了它,他是对的......好吧几乎正确,如果返回零,您必须用“9”将其切换出来,但这是一个非常快速的解决方案......

362 = 3+6+2 = 11 = 1+1 = 2

或者...

362%9 = 2

Anways,mod9 方法非常适合无限添加数字的总和,直到你只剩下一个数字......但是只做一次呢(即 362 只会返回“11”)......谁能想到快速算法?

【问题讨论】:

  • 出于好奇 - 是否提供了关于 %9 为何有效的解释?
  • @Dukeling 不,我认为这与我们处于以 10 为基数的数字系统中这一事实有关,这是 place 之前的最高个位数(例如十位,数百个位置等)重置...在以 6 为基数的数字系统中,我认为这适用于模 5。
  • 可能是一个简单的迭代 - 继续取商和余数除以十,将余数相加并将商作为值以在下一次迭代中检查,并在商为零时停止。跨度>
  • @Dukeling:对于任何整数 k,10^k mod 9 == 1。所以对于整数 d1, d2, ... 和 e1, e2, ...,我们有 d1*10^e1 + d2*10^e2 + ... mod 9 = d1*10^e1 mod 9 + d2*10 *e2 mod 9 ... = d1*1 mod 9 + d2*1 mod 9 + ... = d1 + d2 + ... mod 9。这里,d1...是数字的数字,而e1。 .. 是对应于每个数字位置的 10 的指数。 10和9没什么特别的;它适用于任何基数 b 和相应的模数 b-1。
  • @AlbertRenshaw:你猜对了。更严格地说,基数b (b > 2) 中的数字N 就是Sum[exp=0..inf](digit*b^exp),我们知道b^exp mod (b - 1) = 1,因此N mod (b - 1) = Sum[exp=0..inf](digit) mod (b - 1)

标签: algorithm math sum iteration digit


【解决方案1】:

有一个很酷的技巧可以将二进制中的 1 位数字和固定宽度的整数相加。在每次迭代中,您将每个数字的一​​半分成两个值,将一个值向下移位,然后相加。第一次迭代,将其他数字分开。第二次迭代,数字对,等等。

假设 27 是 00011011 作为 8 位二进制,则过程是......

00010001 + 00000101 = 00010110  <-  every other digit step
00010010 + 00000001 = 00010011  <-  pairs of digits
00000011 + 00000001 = 00000100  <-  quads, giving final result 4

你可以用十进制做一个类似的技巧,但它会比一个简单的循环效率低,除非你有一个十进制数字的直接表示,并通过快速操作将选定的数字归零并进行数字移位。所以对于 12345678 你得到...

02040608 + 01030507 = 03071115  <-  every other digit
00070015 + 00030011 = 00100026  <-  pairs
00000026 + 00000010 = 00000036  <-  quads, final result

所以 1+2+3+4+5+6+7+8 = 36,这是正确的,但只有当你的数字表示是固定宽度的十进制时,你才能有效地做到这一点。它总是需要 lg(n) 次迭代,其中 lg 表示以 2 为底的对数,然后向上取整。

为了稍微扩展一下(基于 cmets 内的讨论),让我们假装这有点理智......

如果你计算个位数的加法,这里实际上更多工作比一个简单的循环。与计算位的按位技巧一样,这个想法是重新排序这些加法(使用关联性),然后并行计算尽可能多的计算,使用单个全角加法来实现两个半角加法,四个四分之一宽度加法等。数字清除和数字移位操作有很大的开销,如果将其实现为循环(计算或查找每个步骤的数字掩码和移位距离值),则开销更大。 “循环”可能应该完全展开,并且那些掩码和移位距离作为常量包含在代码中以避免这种情况。

支持Binary Coded Decimal (BCD) 的处理器可以处理这个问题。数字掩码和数字移位将使用位掩码和位移来实现,因为每个十进制数字将被编码为 4(或更多)位,独立于其他数字的编码。

一个问题是现在 BCD 支持非常少见。它曾经在 8 位和 16 位时代相当普遍,但据我所知,仍然支持它的处理器现在这样做主要是为了向后兼容。原因包括...

  1. 非常早期的处理器不包括硬件乘法和除法。对这些操作的硬件支持意味着现在将二进制转换为十进制更容易、更高效。现在几乎所有东西都使用二进制,而 BCD 几乎被遗忘了。

  2. 库中有十进制数字表示,但很少有高级语言曾经为硬件 BCD 提供可移植支持,因此自从汇编程序不再是大多数开发人员的实际选项后,BCD 支持就不再被使用了。

  3. 随着数字变大,即使是打包的 BCD 打包的效率也很低。以 10^x 为底的数字表示具有以 10 为底的最重要的属性,并且很容易被解码为十进制。基数 1000 只需要每三位数字 10 位,而不是 12 位,因为 2^10 是 1024。这足以表明您获得了 32 位的额外十进制数字 - 9 位而不是 8 位 - 而且您还剩下 2 位,例如一个符号位。

问题是,要使这种数字总计算法完全值得,您需要使用可能至少 32 位(8 位)的固定宽度十进制小数。这为(完全展开的)简单循环提供了 12 次操作(6 个掩码、3 个班次、3 个加法)而不是 15 次加法。不过,这是一个临界增益 - 代码中的其他问题很容易意味着它实际上更慢。

在 64 位(16 位十进制数字)下效率增益更明显,因为仍然只有 16 个操作(8 个掩码、4 个移位、4 个加法)而不是 31 个,但是找到支持 64 位 BCD 操作的处理器的几率看起来很苗条。即使你这样做了,你多久需要一次?似乎不太可能值得付出努力和失去可移植性。

【讨论】:

  • 1+2+3+4+5+6+7+8 -> 7 次加法运算。你的 -> 8 个加法运算。只是说...
  • @Dukeling - 像往常一样,只有当您的问题足够大时,渐近增益才是真正的增益。如果您的固定宽度数字具有足够大的宽度,您将从 O(log n) 而不是 O(n) 中获益。但是我第一次手动操作这些例子就搞砸了。如果您知道所有数字都足够小,那么最快的解决方案是预先计算结果的查找表。您甚至可以通过在开始时使用基数 100 或基数 10000 来优化它 - 实际上绕过了几次迭代。
  • @Dukeling - 也就是说,我的示例只有三个 addition 8 位操作 - 只是说 ;-)
  • * 个位数加法运算。诚然,这并不是最重要的(但是一旦我们超出了高级语言中最大整数基元的大小,它就很重要了)。在高级语言中,您可能很难提高效率,而不仅仅是添加单个数字。
  • @Dukeling - 好的,你就在那儿 - 只有固定宽度的 hardware 十进制表示才有意义,沿着那些二进制编码的十进制表示曾经由处理器支持。我怀疑这些天他们会打扰,但也许是为了向后兼容。即便如此,您也需要考虑其他额外的操作(屏蔽和移位),所以这根本没有实际价值 - 十六进制以上的十进制数字硬件 BCD 将很难非常找到。
【解决方案2】:

这是 Haskell 中的一些东西:

sumDigits n = 
  if n == 0 
     then 0
     else let a = mod n 10
          in a + sumDigits (div n 10)

哦,但我刚刚读到你已经这样做了......

(还有很明显的:

sumDigits n = sum $ map (read . (:[])) . show $ n

)

【讨论】:

    【解决方案3】:

    对于短代码,试试这个:

    int digit_sum(int n){
        if (n<10) return n;
        return n%10 + digit_sum(n/10);
    }
    

    或者说,

    -如果数字小于十,那么数字和就是数字本身。

    -否则,数字总和是当前最后一位数字(也称为 n mod10 或 n%10)加上该数字左侧所有内容的数字总和(n 除以 10,使用整数除法)。

    -该算法也可以推广到任何基数,将基数替换为 10。

    【讨论】:

      【解决方案4】:
      int digit_sum(int n)
      Do
          if (n<10) return n;
          Exit do
          else
      
          n=n%10 + digit_sum(n/10);
      
      Loop 
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-02-08
        • 2011-02-07
        • 2022-06-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多