使用位移除以10?

时间:2011-04-05 21:04:40

标签: math low-level bit

是否可以通过使用纯位移,加法,减法和可能乘法将无符号整数除以10?使用资源非常有限且分工缓慢的处理器。

9 个答案:

答案 0 :(得分:55)

以下是Microsoft编译器在使用小积分常量编译分区时所执行的操作。假设一台32位机器(代码可以相应调整):

int32_t div10(int32_t dividend)
{
    int64_t invDivisor = 0x1999999A;
    return (int32_t) ((invDivisor * dividend) >> 32);
}

这里发生的是我们乘以近似的1/10 * 2 ^ 32然后移除2 ^ 32。这种方法可以适应不同的除数和不同的位宽。

这适用于ia32架构,因为它的IMUL指令会将64位产品放入edx:eax,而edx值将是所需的值。 Viz(假设股息在eax中传递,商在eax中返回)

div10 proc 
    mov    edx,1999999Ah    ; load 1/10 * 2^32
    imul   eax              ; edx:eax = dividend / 10 * 2 ^32
    mov    eax,edx          ; eax = dividend / 10
    ret
    endp

即使在具有慢速乘法指令的机器上,这也会比软件划分更快。

答案 1 :(得分:30)

虽然到目前为止给出的答案与实际问题相符,但它们与标题不符。所以这是一个受Hacker's Delight启发的解决方案,它真正只使用了位移。

unsigned divu10(unsigned n) {
    unsigned q, r;
    q = (n >> 1) + (n >> 2);
    q = q + (q >> 4);
    q = q + (q >> 8);
    q = q + (q >> 16);
    q = q >> 3;
    r = n - (((q << 2) + q) << 1);
    return q + (r > 9);
}

我认为这是缺乏多重指令的架构的最佳解决方案。

答案 2 :(得分:15)

当然,如果你可以忍受一些精确的损失,你可以。如果你知道输入值的值范围,你可以得到一个位移和一个精确的乘法。 一些例子如何划分10,60 ......就像本博客中描述的格式time the fastest way一样。

temp = (ms * 205) >> 11;  // 205/2048 is nearly the same as /10

答案 3 :(得分:3)

考虑到库巴奥伯的回应,还有另外一个回应。 它使用结果的迭代近似,但我不希望任何令人惊讶的表现。

我们必须找到x x = v / 10

我们将使用逆操作v = x * 10,因为它具有良好的属性,即x = a + b,然后是x * 10 = a * 10 + b * 10

x作为变量保持到目前为止最好的结果近似值。搜索结束时,x将保留结果。我们会将b的{​​{1}}的每个位x从最重要的位置设置为不太重要的位,逐个将(x + b) * 10v进行比较。如果它小于或等于v,则在b中设置位x。为了测试下一位,我们只需将b一个位置向右移动(除以2)。

我们可以通过在其他变量中保留x * 10b * 10来避免乘以10。

这会产生以下算法,将v除以10。

uin16_t x = 0, x10 = 0, b = 0x1000, b10 = 0xA000;
while (b != 0) {
    uint16_t t = x10 + b10;
    if (t <= v) {
        x10 = t;
        x |= b;
    }
    b10 >>= 1;
    b >>= 1;
}
// x = v / 10

修改以获取Kuba Ober的算法,避免了变量x10的需要,我们可以从b10和{{1}中减去v而是。在这种情况下,不再需要v10。算法变为

x10

循环可以展开,uin16_t x = 0, b = 0x1000, b10 = 0xA000; while (b != 0) { if (b10 <= v) { v -= b10; x |= b; } b10 >>= 1; b >>= 1; } // x = v / 10 b的不同值可以预先计算为常量。

答案 4 :(得分:2)

分裂是减法,所以是的。向右移1(除以2)。现在从结果中减去5,计算减法的次数,直到值小于5.结果是你做的减法次数。哦,划分可能会更快。

如果分频器中的逻辑尚未为您执行此操作,则使用正常除法将右移然后除以5的混合策略可能会提高性能。

答案 5 :(得分:2)

在一次只能移动一个地方的架构上,一系列显着的比较减少2的幂乘以10可能比黑客的喜悦解决方案更好。假设有16位红利:

if (isset($_SERVER['CONTENT_TYPE'])
    and stripos($_SERVER['CONTENT_TYPE'], 'application/json') !== false
) {
    $jsonEncoded = file_get_contents('php://input');
    $jsonDecoded = json_decode($jsonEncoded, true);
    if (is_array($jsonDecoded)) {
        foreach ($jsonDecoded as $varName => $varValue) {
            $_POST[$varName] = $varValue;
        }
    }
}

答案 6 :(得分:1)

要稍微扩展Alois的答案,我们可以将建议的y = (x * 205) >> 11扩展一些倍数/班次:

y = (ms *        1) >>  3 // first error 8
y = (ms *        2) >>  4 // 8
y = (ms *        4) >>  5 // 8
y = (ms *        7) >>  6 // 19
y = (ms *       13) >>  7 // 69
y = (ms *       26) >>  8 // 69
y = (ms *       52) >>  9 // 69
y = (ms *      103) >> 10 // 179
y = (ms *      205) >> 11 // 1029
y = (ms *      410) >> 12 // 1029
y = (ms *      820) >> 13 // 1029
y = (ms *     1639) >> 14 // 2739
y = (ms *     3277) >> 15 // 16389
y = (ms *     6554) >> 16 // 16389
y = (ms *    13108) >> 17 // 16389
y = (ms *    26215) >> 18 // 43699
y = (ms *    52429) >> 19 // 262149
y = (ms *   104858) >> 20 // 262149
y = (ms *   209716) >> 21 // 262149
y = (ms *   419431) >> 22 // 699059
y = (ms *   838861) >> 23 // 4194309
y = (ms *  1677722) >> 24 // 4194309
y = (ms *  3355444) >> 25 // 4194309
y = (ms *  6710887) >> 26 // 11184819
y = (ms * 13421773) >> 27 // 67108869

每行都是一个独立的计算,并且您将在注释中显示的值处看到第一个“错误” /不正确的结果。通常情况下,您最好对给定的误差值进行最小的偏移,因为这样可以最大程度地减少在计算中存储中间值所需的额外位,例如(x * 13) >> 7(x * 52) >> 9“更好”,因为它只需要少一点点的开销,而两者都会在68以上时给出错误的答案。

如果您要计算更多这些内容,可以使用以下(Python)代码:

def mul_from_shift(shift):
    mid = 2**shift + 5.
    return int(round(mid / 10.))

我做了明显的事情来计算这种近似何时开始出现错误:

def first_err(mul, shift):
    i = 1
    while True:
        y = (i * mul) >> shift
        if y != i // 10:
            return i
        i += 1

(请注意,//用于“整数”除法,即它会向零截断/四舍五入)

出现“ 3/1”模式错误的原因(即8次重复3次,后跟9次)似乎是由于基数的变化,即log2(10)为〜3.32。如果绘制错误,则会得到以下结果:

errors

相对误差由以下公式给出:mul_from_shift(shift) / (1<<shift) - 0.1

答案 7 :(得分:0)

我在 AVR 汇编中设计了一种新方法,只有 lsr/ror 和 sub/sbc。它除以 8,然后减去除以 64 和 128 的数字,然后减去第 1,024 和第 2,048,依此类推。工作非常可靠(包括精确舍入)和快速(1 MHz 时为 370 微秒)。 16位数字的源代码在这里: http://www.avr-asm-tutorial.net/avr_en/beginner/DIV10/div10_16rd.asm 注释此源代码的页面在这里: http://www.avr-asm-tutorial.net/avr_en/beginner/DIV10/DIV10.html 我希望它有所帮助,即使这个问题已有十年历史了。 brgs, gsc

答案 8 :(得分:-1)

elemakil的注释的代码可在这里找到:https://doc.lagout.org/security/Hackers%20Delight.pdf 第233页。“无符号除以10 [和11]”