【问题标题】:Trying to multiply a number from 0..4096 by 0.3 using only integer math尝试仅使用整数数学将 0..4096 中的数字乘以 0.3
【发布时间】:2015-05-27 02:14:30
【问题描述】:

我正在努力思考如何在 C 语言中仅使用带有移位运算和缩放的整数将 0..4096 乘以 0.3,不进行除法。我我是新手,任何输入或分步建议都会非常有帮助。

【问题讨论】:

  • 我认为他的意思是[0, 4096]范围内的任何给定数字乘以0.3
  • 乘以3再除以10。
  • 这可能会有所帮助:stackoverflow.com/questions/11694546/…
  • (a*9830 + 16384)>>15
  • @DanielKamilKozar 我赞成你的评论然后注意到'没有在C'中划分'

标签: c integer scaling


【解决方案1】:

简单地尝试了很多(A*x + B) >> n形式的组合与下面的测试代码并想出了:

// Scale by 4915, then shift 14.
int Mult3Tenths(int x) {
  return (x*4915 + 0) >> 14;  // Use 4915L if `int` is 16 bit.
}

测试代码

#define N3 (4096)
int main(void) {
  int target[N3 + 1];
  unsigned i;
  for (i = 0; i <= N3; i++) {
    target[i] = 0.3 * i;
  }

  // form (A*x + B) >> n
  int A, B, n;
  int besti = 0;
  for (n = 0; n < 31; n++) {
    int Amin = ((N3 * 0.3 - 1) * (1 << n) - (1 << n)) / N3 - 1;
    int Amax = ((N3 * 0.3 + 1) * (1 << n) + (1 << n)) / N3 + 1;
    for (A = Amin; A <= Amax; A++) {
      int Bmax = 1 << n;
      for (B = -Bmax; B <= Bmax; B++) {
        for (i = 0; i <= N3; i++) {
          int y = (A * i + B) >> n;
          if (y != target[i])
            break;
          if (i > besti) {
            besti = i;
            if (i == N3) {
              printf("i:%i A:%d B:%d n:%d\n", i, A, B, n);
              printf("!!!\n");
              exit(0);
            }
          }
        }
      }
    }
  }
  printf("???\n");
  return 0;
}

【讨论】:

    【解决方案2】:

    乘以 0.3 与乘以 (0.3*2^n),然后除以 2^n 相同。第二阶段相当于n右移。

    但是n最佳值是多少?

    要找到它,请取最大整数并找到n 的最大值,这样您就可以乘以(0.3*2^n) 而不会溢出。 64位无符号整数,最大值4096,需要

    0.3*2^n <= 2^(64-12)
    

    0.3 <= 2^(64-12-n)
    

    当 RHS 等于 0.5 时,这个不等式最大 n,所以

    2^-1 = 2^(64-12-n)
    

    所以-1 = 64-12-nn = 64-12+1 = 53

    所以答案是乘以2^53*0.3,然后右移53,即

    /* designed to work with input values 0 .. 4096 only */
    uint64_t
    multiplyby0_3 (uint64_t x)
    {
        return (x * 2702159776422297ULL) >> 53;
    }
    

    检查没有溢出,我们有最好的n,来自bc

    2702159776422297*4096 = 11068046444225728512
    2^64                  = 18446744073709551616
    

    IE它不会溢出,但如果我们再次将它乘以2,它就会溢出。

    对于 32 位整数,答案是乘以 2^21*0.3 然后右移 21,即

    /* designed to work with input values 0 .. 4096 only */
    uint32_t
    multiplyby0_3 (uint32_t x)
    {
        return (x * 629146U) >> 21;
    }
    

    最后,您可以通过查看乘法器中的二进制1 将任何乘法分解为多个加法。所以你允许 'scaling' 我认为这意味着乘法。如果没有,这里是 32 位版本(留给读者练习的 64 位版本)利用了 62914610011001100110011010 的事实(由于重复出现二进制分数,这是一个简洁的模式)。我们将反过来使用10011001100110011001

    /* designed to work with input values 0 .. 4096 only */
    uint32_t
    multiplyby0_3 (uint32_t x)
    {
        uint32_t y;
        x += x<<3;    /* * 1001 */
        y = x<<4 + x; /* * 10011001 */
        y += y<<8;    /* * 1001100110011001 */
        y += x<<16;   /* * 10011001100110011001 */
        return y >> 21;
    }
    

    【讨论】:

    • 使用这么多位是多余的 - 因为输入范围只有 ~ 12 位,那么您应该能够在 32 位中间值内完全准确地做到这一点。
    • @duskwuff 我认为这不对。可能的最大值(假设输入限制为 0 .. 4096)是 4096,因此括号中的位最多为 ``2702159776422297 * 4096 = 11068046444225728512` 64 位。我将对代码示例发表评论。
    • @PaulR 这不是 32 位示例的作用吗?是的,我意识到这是矫枉过正,但我​​想教 OP 如何在一般情况下解决它。在任何情况下,证明较少的位数永远不会给出不同的答案是更多的工作!
    • 当然 - 如果您将 629146ULL 更改为 629146U 以便您只使用 32 位算术,那么就可以了。
    • @abligh 获得向上箭头。提交到 0.3 的位数是无符号字(64 或 32)中的位数减去 12(因为 2^12 = 4096)。因此,您希望 0.3 表示为 0.3 * 2^52 或 0.3 * 2^20,乘以您的值,然后(四舍五入)添加 2^51 并右移 52 位或添加 2^19 并右移20 位。
    【解决方案3】:

    我知道这不是一个编码示例,对此感到抱歉,但如果集合如此之小(范围 [0;4096]),为什么不创建一个结果块并使用指针来提取值呢?它将大大减少 GPU 周期,因为没有内存限制。

    【讨论】:

    • GPU 周期?内存限制?
    • 是的......它从我的脑海中滑落......这个整数限制让我想到了 GPU 计算、ATI 芯片和比特币挖掘......我的错误 :)
    【解决方案4】:

    对下面的divu10() 使用经典破解。我更喜欢 *n 和 shift 方法,但我认为我提供了另一个 POV。

    unsigned mult3tenths(unsigned x) {
      return divu10(x*3);
    }
    
    unsigned divu10(unsigned n) {
      unsigned q, rem;
      q = (n >> 1) + (n >> 2);
      q = q + (q >> 4);
      q = q + (q >> 8);
      q = q + (q >> 16);
      q = q >> 3;
      rem = n - q*10;
      return q + ((rem + 6) >> 4);  
    }
    

    【讨论】:

    • 简洁,虽然我很遗憾第二行没有阅读 div10(x+(x&lt;&lt;1)) 和后来的 rem=n-(q&lt;&lt;3)-(q&lt;&lt;1) 只是为了消除所有乘法:-)
    • @abligh 怀疑一个好的编译器会采用x*3x+(x&lt;&lt;1) 并生成相同的代码。
    • 是的,确实会。但是为了一笑,我找到了一种更短的方法,没有显式乘法并将其添加到我的答案中:-)
    【解决方案5】:

    如果你有快速整数乘法,但你没有整数除法,你可以通过乘以 1229,然后右移 12 位得到一个合理的近似值。例如:

    >> 100 * 1229
    122900
    >> 122900 >> 12
    30
    

    这是有效的,因为 1229 大约是 0.3 * 1 &lt;&lt; 12。 “真实”值是 1228.8,因此在某些情况下估计值会高 1(4097 个值中有 68 个)。不过,它的偏差永远不会超过 1。

    【讨论】:

    • 我会在 >>12 之前做一个 +2048 来做一个简单的四舍五入。
    • 如果乘法溢出怎么办?
    • @EOF: 122900 只有 17 位,所以使用 32 位整数和 12 位输入范围应该没问题。
    • @EOF 给定问题中指定的范围,最大中间结果是1229 * 4096 = 5033984。这完全在 32 位整数的范围内;如果你需要保持在 16 位以内,那就有点困难了。
    • 是的,但这似乎并不适用于所有输入值。我认为 OP 试图找到一种方法来产生与将整数乘以 0.3(将结果四舍五入)相同的结果,这似乎偶尔会出错。你需要更多的位(可能没有我的答案那么多!)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-12-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-03
    • 1970-01-01
    • 2023-03-05
    相关资源
    最近更新 更多