操作数大小大致相同的大整数除法

Big integer division with operands aproximately of the same size

本文关键字:整数 除法 小大 操作数      更新时间:2023-10-16

我正在尝试实现一个函数,该函数计算两个大小大致相同的数字之间的除法。我使用unsigned int的数组来存储每个数字的值(参见下面的类结构)。我想实现一个有效的除法函数当两者的unsigned int个数之差不大于1时。我研究过长除法算法(如维基百科所述),但它太慢了。有没有有效的算法来处理这种特殊情况?目前,我正在处理最多100位的数字。

class BigInt {
public:
    short sign; //sign of the number
    unsigned int size; //number of unsigned int's needed to store the number
    unsigned int* values; //values of the number
    ...
}

谢谢

我相信Knuth算法D (The Art of Computer Programming Vol.2, Section 4.3.1)的修改版本应该可以在线性时间最坏情况下,以恒定的平均时间处理随机输入。重要的简化源于只需要初始迭代产生的第一个商字。

下面是我无耻地改编Hacker’s Delight的通用实现的尝试。该实现假设32位字和64位中间/除法,但可以在更广泛的算术可用的地方自然扩展。

我确实担心这个函数实际上没有经过测试,所以请把它看作是一个想法的萌芽,而不是一个完全成熟的实现。老实说,我甚至不记得的微妙之处为什么算法有效。

// A helper function for finding the position of the most significant set bit.
// Please plug in your intrinsic of choice here
static unsigned int find_first_bit(uint32_t value) {
#   ifdef _MSC_VER
    unsigned long index;
    (void) _BitScanReverse(&index, value);
    return index + 1;
#   else
    unsigned int count = 0;
    for(count = 0; value; ++count)
        value >>= 1;
    return count;
#   endif
}
// Multi-word division in 32-bit big-endian segments, returning the most significant
// word of the quotient.
uint32_t divide(const uint32_t *num, const uint32_t *den, size_t len) {
    // Skip past leading zero denominator digits. The quotient must fit in a single
    // 32-bit word and so only the preceeding numerator digit needs be examined
    uint32_t norm_den;
    uint64_t norm_num = 0;
    size_t top = 0;
    while(norm_den = den[top], !norm_den)
        norm_num = num[top++];
    // Please pad the input to insure at least three denominator words counting from
    // the first non-zero digit
    assert(len >= top + 3);
    // Divide the first two digits of the numerator by the leading digit of the
    // denominator as an initial quotient digit guess, yielding an upper bound
    // for the quotient at most two steps above the true value.
    // Simultaneously normalize the denominator with the MSB in the 31st bit.
    unsigned int norm = find_first_bit(norm_den);
    norm_num = norm_num << (64 - norm);
    norm_num |= ((uint64_t) num[top + 0] << 32 | num[top + 1]) >> norm;
    norm_den = ((uint64_t) norm_den << 32 | den[top + 1]) >> norm;
    // We are using a straight 64/64 division where 64/32=32 would suffice. The latter
    // is available on e.g. x86-32 but difficult to generate short of assembly code.
    uint32_t quot = (uint32_t) (norm_num / norm_den);
    // Substitute norm_num - quot * norm_den if your optimizer is too thick-headed to
    // efficiently extract the remainder
    uint32_t rem = norm_num % norm_den;
    // Test the next word of the input, reducing the upper bound to within one step
    // of the true quotient. See Knuth for proofs of this reduction and the bounds
    // of the first guess
    norm_num = ((uint64_t) num[top + 1] << 32 | num[top + 2]) >> norm;
    norm_num = (uint64_t) rem << 32 | (uint32_t) norm_num;
    norm_den = ((uint64_t) den[top + 1] << 32 | den[top + 2]) >> norm;
    if((uint64_t) quot * norm_den > norm_num) {
        --quot;
        // There is no "add-back" step try to avoid and so there is little point
        // in looping to refine the guess further since the bound is sufficiently
        // tight already
    }
    // Compare quotient guess multiplied by the denominator to the numerator
    // across whole numbers to account for the final quotient step.
    // There is no need to bother with normalization here. Furthermore we can
    // compare from the most to the least significant and cut off early when the
    // intermediate result becomes large, yielding a constant average running
    // time for random input
    uint64_t accum = 0;
    do {
        uint64_t prod = (uint64_t) quot * *den++;
        accum = accum << 32 | *num++;
        // A negative partial result can never recover, so pick the lower
        // quotient. A separate test is required to avoid 65-bit arithmetic
        if(accum < prod)
            return --quot;
        accum -= prod;
        // Similarly a partial result which spills into the upper 32-bits can't
        // recover either, so go with the upper quotient
        if((uint64_t) accum >= 0x100000000)
            return quot;
    } while(--len);
    return quot;
}

假设您想要一个整数结果,您应该能够相当快地执行此操作。

最简单的方法可能是用十进制来表示,以稍微小一点的数字开始。

让我们考虑一些较小的数字,如:123456789/54321876。其中一个比另一个多一位,所以最大的可能结果是在10左右。

如果我们把两边同时除,那么(比如说)大数剩下三位数,小数剩下两位数,我们得到123/54,它产生的整数结果与我们在整个过程中做了完全精确的工作一样。

在你的例子中,你可以做几乎相同的事情,除了不使用十进制数字,你将在较大的项目中保留三个精确的单词,在较小的项目中保留两个精确的单词(实际上,即使这是保守的-分别使用2个和1个单词可能就足够了,但我现在懒得证明这一点,如果你这样做,你可能需要更小心舍入)。

所以,是的,你的想法,这是一种特殊的情况下,你可以节省工作基于数字几乎相同的大小是正确的。