【问题标题】:Finding the last 10 digits of 2^n查找 2^n 的最后 10 位数字
【发布时间】:2018-06-23 05:53:31
【问题描述】:

所以我应该找出 2^n(064 时程序失败。任何有关如何进行此操作的线索将不胜感激。

#include<stdio.h>
#include<math.h>


/* Iterative Function to calculate (x^y)%p in O(log y) */
int power(long long int x, long long int y, long long int p)
{
long long int res = 1; // Initialize result

x = x % p; // Update x if it is more than or
// equal to p

while (y > 0) {

    // If y is odd, multiply x with result
    if (y & 1)
        res = (res * x) % p;

    // y must be even now
    y = y >> 1; // y = y/2
    x = (x * x) % p;
}
return res;
}

// C function to print last 10 digits of a^b
void printLastDigits(long long int a,long long int b)
{    

long long int temp = pow(10,10);

// Calling modular exponentiation
temp = power(a, b, temp);

if (temp)
    printf("%d",temp);
}

int main()
{
long long int n;
scanf("%d",&n);
printLastDigits(2,n);
return 0;
}

【问题讨论】:

  • 这是对你观察能力的考验。您可能被期望找到模式(因为存在模式)并将您的知识应用于推断(因此您无需计算2^n)。
  • 使用 modpow 的想法可行,但是由于 x * x(和 res * x)臭名昭著的“提前溢出”,你的 modpow 不起作用,在你有机会减少它之前p.
  • @harold 那么有没有办法解决这个问题?
  • 一种简单的方法是将 1 乘以 2,n 次,同时丢弃最后 10 位以上的数字。您还可以找到 2 的幂的最后 10 位数字中的每一个的模式,但它涉及进位.
  • 对于n &lt;= 100 求平方并不是那么关键,您可以轻松地乘以 2^20(最多 5 步,所以仍然非常快)而不会过早溢出

标签: c unsigned-long-long-int


【解决方案1】:

您不必需要担心“高”位,因为乘以 2左移它们超出了乘积下部的范围你很感兴趣。只要确保你使用 unsigned long long 类型(至少 64 位)来保存足够宽的整数类型,例如,

#include <inttypes.h>
#include <stdio.h>

void low_digits (unsigned int n)
{
    unsigned long long base = 2, modulus = 10000000000ULL;

    for (unsigned int i = 1; i <= n; i++)
    {
        fprintf(stdout, "2^%u mod 10^10 = %llu\n", i, base);
        base = (base * 2) % modulus;
    }
}

您可以使用 bignum 计算器测试2^1000

10715086071862673209484250490600018105614048117055336074437503883703\ 51051124936122493198378815695858127594672917553146825187145285692314\ 04359845775746985748039345677748242309854210746050623711418779541821\ 53046474983581941267398767559165543946077062914571196477686542167660\ 429831652624386837205668069376

n = 1000 以上产生:5668069376


其他人已经注意到,这是一种naive 方法,对于足够大的(n) 值,模幂运算效率更高。不幸的是,这将需要超出无符号 64 位值范围的产品,因此除非您准备实现 [hi64][lo64] 多精度 mul / mod 操作,否则它可能超出您的任务范围。

幸运的是,更高版本的 gcc 和 clang 确实提供了扩展的 128 位整数类型:

#include <inttypes.h>
#include <stdio.h>

void low_digits (unsigned int n)
{
    unsigned long long base = 2, modulus = 10000000000ULL;
    __extension__ unsigned __int128 u = 1, w = base;

    while (n != 0)
    {
        if ((n & 0x1) != 0)
            u = (u * w) % modulus; /* (mul-reduce) */

        if ((n >>= 1) != 0)
            w = (w * w) % modulus; /* (sqr-reduce) */
    }

    base = (unsigned long long) u;
    fprintf(stdout, "2^%u mod 10^10 = %llu\n", n, base);
}

【讨论】:

  • 这是基本的迭代方法,每次x次。
  • 如果您将乘法转换为 128 位,然后让它再次回到 64 位,这会稍微好一些 - 这样编译器会使用更快的 64x64->128 乘法而不是 128x128->128乘法(在上半部分浪费乘法,为零)
  • @harold - 是的,但我认为最好在不过度转换的情况下展示 mod-exp 算法。但是,我认为编译器不够聪明,无法知道连续的除法总是适合一个词,并且运行时由对 __umodti3 之类的调用支配 - 例如,你不会得到一个简单的 @ 987654331@ x86-64 上的指令。
【解决方案2】:

假设您正在寻找 2^n 的最后一位,您只需要考虑最后一位并忽略所有其他数字

1. 2*2 = 4
2. 4*2 = 8
3. 8*2 = 16 (ignore last-but-one digit i.e 1)
4. 6*2 = 12 (ignore last-but-one digit i.e 1)
5. 2*2 = 4
6. 4*2 = 8
7. 8*2 = 16 (ignore last-but-one digit i.e 1)
8. 6*2 = 12 (ignore last-but-one digit i.e 1)
9. 2*2 = 4

... n-1 iterations

要查找 2^n 的最后 2 位,请忽略除最后 2 位之外的所有数字。

1. 2*2 = 4
2. 4*2 = 8
3. 8*2 = 16
4. 16*2 = 32
5. 32*2 = 64
6. 64*2 = 128 (Consider last 2 digits)
7. 28*2 = 56
8. 56*2 = 112 (Consider last 2 digits)
9. 12*2 = 24

... n-1 iterations

类似地,要找到 2^n 的最后 10 位,每次迭代只考虑最后 10 位,然后重复 n-1 迭代。

注意:

使用这种方法,您在计算过程中得到的最大数字可以是 11 位 ~ 10^11,而对于 64 位机器,最大值是 ~ 2^64 = ~ 10^18

【讨论】:

    【解决方案3】:

    由于您收到的其他答案实际上并没有显示您做错了什么:

    x = (x * x) % p;
    

    您假设x * x 仍然适合long long int。但是如果x0x100000000(4294967296,10 位十进制数字),long long int 是 64 位,那么它就不适合了。

    要么:

    您需要一种方法来准确地将两个任意 10 位数字相乘。结果可能有 20 位数字,可能不适合 long long int 甚至 unsigned long long int。这意味着您需要使用一些 bigint 库或自己实现类似的东西。

    或者:

    您需要避免将多个可能为 10 位的数字相乘。

    您接受的答案选择简单的重复乘以2。现在这足以解决您的问题,但请注意,如果您想允许非常大的指数,这确实会显着增加复杂性。

    【讨论】:

      【解决方案4】:

      以下使用字符串来执行乘法:

      void lastdigits(char digits[11], int n)
      {
          int i, j, x, carry;
          for (i=0; i<n;i++) {
              for (j=9, carry=0; j>=0; j--) {
                  x= digits[j]-'0';
                  x *= 2;
                  x += carry;
                  if (x>9) {carry= 1; x -= 10;}
                  else carry= 0;
                  digits[j]= x+'0';
              }
          }
      }
      
      void test(void)
      {
          char digits[11];
      
          strcpy(digits,"0000000001");
          lastdigits(digits,10);
          printf("%s\n",digits);
      
          strcpy(digits,"0000000001");
          lastdigits(digits,20);
          printf("%s\n",digits);
      
          strcpy(digits,"0000000001");
          lastdigits(digits,100);
          printf("%s\n",digits);
      }
      

      输出:

      0000001024
      0001048576
      6703205376
      

      【讨论】:

      • 感谢您使用字符串的想法。没想到。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-09-04
      • 2016-01-08
      • 1970-01-01
      • 1970-01-01
      • 2021-10-01
      • 1970-01-01
      • 2017-06-01
      相关资源
      最近更新 更多