牛顿迭代司与大整数整数、迭代

2023-09-11 23:19:23 作者:气场征服一切

我在做一个BIGINT类编程练习。它采用了2的补签署整数向量中的基65536(使32位乘法没有溢出,我会增加基础一旦我得到它完全正常)。

所有基本的数学运算是codeD,有一个问题:分工的痛苦的缓慢的基本算法我能创造。 (有点它像二元划分为商的每一位...我没有要发布它,除非有人想看看吧......)

而不是我慢的算法,我想用牛顿迭代找到(移动)倒数,然后乘以(和移位)。我想我有我的头周围的基础知识:你给的公式(X1 = X0(2 - X0 *除数))一个很好的初始猜测,然后反复一定量后,X收敛于倒数。这部分似乎很容易...但我遇到了一些问题,试图将这个公式来大整数的时候:

问题1:

由于我与整数工作...好...我不能用分数。这似乎导致x可总是发散(X0 *除数必须是2似乎?)。我的直觉告诉我,应该有一定的修改公式,将允许它的工作整数(在一定的精度),但我真的在努力寻找出它是什么。 (我缺乏数学技能都打我在这里......)我想我需要找到一些等价方程其中,而不是ð有 D * [基地^ somePower] ?还能有一些公式例如?(X1 = X0(2 - X0 * D))与整数作品

问题2:

当我用牛顿公式找到一些数字的倒数,结果最终被下面有什么,答案应该只是一个小派......恩。试图寻找4倒数(十进制)时:

  X0 = 0.3
X1 = 0.24
X2 = 0.2496
X3 = 0.24999936
X4 = 0.2499999999983616
X5 = 0.24999999999999999999998926258176
 
牛顿迭代如何迭代

如果我进行了重新presenting号码10为基数的,我想25的结果(和2记往右移产品)。对于一些倒数,例如1/3,你可以简单地截断后的结果,你知道你有足够的精度。但是,我怎么能拉出正确的倒数从上面的结果?

抱歉,如果这一切都太模糊,或者如果我要求太多。我看了看,通过维基百科和所有的研究论文,我能找到谷歌,但我觉得我敲我的头靠在墙上。我AP preciate任何帮助,任何人都可以给我!

...

编辑:得到的算法的工作,虽然它比我预想的要慢得多。我居然瘦了很多的速度比我的老算法,即使是在拥有数千位......我还是失去了一些东西的数字。它不是用乘法,这是非常快的问题。 (我确实使用Karatsuba的算法)。

有关兴趣的人,这里是我目前的牛顿迭代算法的迭代:

  BIGINT运营商/(常量BIGINT和放大器; LHS,常量BIGINT和放大器; RHS){
    如果(右== 0)抛出溢出错误(除以零异常);
    BIGINT红利= LHS;
    BIGINT除数= RHS;

    布尔负= 0;
    如果(股息℃,){
        负=负数!;
        dividend.invert();
    }
    如果(除数℃,){
        负=负数!;
        divisor.invert();
    }

    INT K = dividend.numBits()+ divisor.numBits();
    BIGINT POW2 = 1;
    POW2&其中;&其中; = K + 1;

    BIGINT X =被除数 - 除数;
    BIGINT lastx = 0;
    BIGINT lastlastx = 0;
    而(1){
        X =(X *(POW2  -  X *除数))>> K表;
        如果(X == lastx || x == lastlastx)破;
        lastlastx = lastx;
        lastx = X;
    }
    BIGINT商=股息* X>> K表;
    如果(分红 - (商*除数)> =除数)商++;
    如果(负)quotient.invert();
    回商;
}
 

和这里是我的(真正丑陋的)旧算法,该算法速度快:

  BIGINT运营商/(常量BIGINT和放大器; LHS,常量BIGINT和放大器; RHS){
    如果(右== 0)抛出溢出错误(除以零异常);
    BIGINT红利= LHS;
    BIGINT除数= RHS;

    布尔负= 0;
    如果(股息℃,){
        负=负数!;
        dividend.invert();
    }
    如果(除数℃,){
        负=负数!;
        divisor.invert();
    }

    BIGINT余数= 0;
    BIGINT商= 0;
    而(dividend.value.size()大于0){
        remainder.value.insert(remainder.value.begin(),dividend.value.at(dividend.value.size() -  1));
        remainder.value.push_back(0);
        remainder.unPad();
        dividend.value.pop_back();

        如果(除数>其余部分){
            quotient.value.push_back(0);
        } 其他 {
            诠释计数= 0;
            INT I = MSB;
            BIGINT值= 0;
            而(ⅰ大于0){
                BIGINT增加=除数*我;
                BIGINT下一=价值+增加;
                如果(下一个< =余数){
                    值=下一个;
                    数+ =我;
                }
                I>> = 1;
            }
            quotient.value.push_back(计数);
            余 -  =价值;
        }
    }

    的for(int i = 0; I< quotient.value.size()/ 2;我++){
        INT交换= quotient.value.at(我);
        quotient.value.at(ⅰ)= quotient.value.at((quotient.value.size() -  1) - ⅰ);
        quotient.value.at(quotient.value.size() -  1  -  I)=掉;
    }

    如果(负)quotient.invert();
    quotient.unPad();
    回商;
}
 

解决方案

首先,你可以实现时分为O(n ^ 2),并以合理的恒,所以它不是(多)比天真的乘法慢。但是,如果您使用 Karatsuba的式的算法,甚至的 FFT 基于乘算法,那么你的确可以用牛顿迭代加速你的分裂算法。

一个牛顿 - 拉夫逊迭代计算的 X Q [N + 1] = Q [N] *(2-Q的倒数[N] * X)

假设我们要计算地板(2 ^ K / B),其中 B 是一个正整数。 WLOG,B≤2^氏/ code>;否则,商数为 0 。牛顿 - 拉夫逊迭代 X = B / 2 ^氏/ code>收益率 Q [N + 1] = Q [N] *(2-Q [ N] * B / 2 ^ K)。我们可以重新排列为

Q [N + 1] = Q [N] *(2 ^(K + 1)-q [N] * B)>>氏/ code>

这种每次迭代只需要整数乘法和移位。是否收敛到地板(2 ^ K / B)?不必要。然而,在最坏的情况下,它最终地板(2 ^ K / B)上限(2 ^ K / B)(证明它!)。所以,你可以使用一些不那么聪明的测试,看看你是在这种情况下,并提取地板(2 ^ K / B)。 (这个不那么聪明的测试应该比很多在每次迭代的乘法快;但是,这将是很好的优化这件事)。

事实上,计算地板(2 ^ K / B)就足够了,以便计算楼(A / B)对任意正整数 A,B 。以 K ,使得 A *B≤2^氏/ code>,并验证楼(A / B)= A *天花板(2 ^ K / B)>>氏/ code>。

最后,这种方法简单,但重要的是优化截断的乘法(即计算产品的唯一的较高位)中的Newton-Raphson方法的早期的迭代。这样做的原因是,在早期的迭代的结果是远离商数,并不要紧不准确执行它们。 (缩小这种说法,并表示,如果你适当地做这件事,你可以将两个≤N比特整数时间 O(M(2N) ),假设你可以将两个≤k比特及时整数 M(K) M(X)是一个递增的凸函数)。

I'm making a BigInt class as a programming exercise. It uses a vector of 2's complement signed ints in base-65536 (so that 32-bit multiplications don't overflow. I will increase the base once I get it fully working).

All of the basic math operations are coded, with one problem: division is painfully slow with the basic algorithm I was able to create. (It kind of works like binary division for each digit of the quotient... I'm not going to post it unless someone wants to see it....)

Instead of my slow algorithm, I want to use Newton-Raphson to find the (shifted) reciprocal and then multiply (and shift). I think I have my head around the basics: you give the formula (x1 = x0(2 - x0 * divisor)) a good initial guess, and then after some amount of iterations, x converges to the reciprocal. This part seems easy enough... but I am running into some problems when trying to apply this formula to big integers:

Problem 1:

Because I am working with integers... well... I can't use fractions. This seems to cause x to always diverge (x0 * divisor must be <2 it seems?). My intuition tells me there should be some modification to the equation that would allow it to work integers (to some accuracy) but I am really struggling to find out what it is. (My lack of math skills are beating me up here....) I think I need to find some equivalent equation where instead of d there is d*[base^somePower]? Can there be some equation like (x1 = x0(2 - x0 * d)) that works with whole numbers?

Problem 2:

When I use Newton's formula to find the reciprocal of some numbers, the result ends up being just a small faction below what the answer should be... ex. when trying to find reciprocal of 4 (in decimal):

x0 = 0.3
x1 = 0.24
x2 = 0.2496
x3 = 0.24999936
x4 = 0.2499999999983616
x5 = 0.24999999999999999999998926258176

If I were representing numbers in base-10, I would want a result of 25 (and to remember to right shift product by 2). With some reciprocals such as 1/3, you can simply truncate the result after you know you have enough accuracy. But how can I pull out the correct reciprocal from the above result?

Sorry if this is all too vague or if I'm asking for too much. I looked through Wikipedia and all of the research papers I could find on Google, but I feel like I'm banging my head against a wall. I appreciate any help anyone can give me!

...

Edit: Got the algorithm working, although it is much slower than I expected. I actually lost a lot of speed compared to my old algorithm, even on numbers with thousands of digits... I'm still missing something. It's not a problem with multiplication, which is very fast. (I am indeed using Karatsuba's algorithm).

For anyone interested, here is my current iteration of the Newton-Raphson algorithm:

bigint operator/(const bigint& lhs, const bigint& rhs) {
    if (rhs == 0) throw overflow_error("Divide by zero exception");
    bigint dividend = lhs;
    bigint divisor = rhs;

    bool negative = 0;
    if (dividend < 0) {
        negative = !negative;
        dividend.invert();
    }
    if (divisor < 0) {
        negative = !negative;
        divisor.invert();
    }

    int k = dividend.numBits() + divisor.numBits();
    bigint pow2 = 1;
    pow2 <<= k + 1;

    bigint x = dividend - divisor;
    bigint lastx = 0;
    bigint lastlastx = 0;
    while (1) {
        x = (x * (pow2 - x * divisor)) >> k;
        if (x == lastx || x == lastlastx) break;
        lastlastx = lastx;
        lastx = x;
    }
    bigint quotient = dividend * x >> k;
    if (dividend - (quotient * divisor) >= divisor) quotient++;
    if (negative)quotient.invert();
    return quotient;
}

And here is my (really ugly) old algorithm that is faster:

bigint operator/(const bigint& lhs, const bigint & rhs) {
    if (rhs == 0) throw overflow_error("Divide by zero exception");
    bigint dividend = lhs;
    bigint divisor = rhs;

    bool negative = 0;
    if (dividend < 0) {
        negative = !negative;
        dividend.invert();
    }
    if (divisor < 0) {
        negative = !negative;
        divisor.invert();
    }

    bigint remainder = 0;
    bigint quotient = 0;
    while (dividend.value.size() > 0) {
        remainder.value.insert(remainder.value.begin(), dividend.value.at(dividend.value.size() - 1));
        remainder.value.push_back(0);
        remainder.unPad();
        dividend.value.pop_back();

        if (divisor > remainder) {
            quotient.value.push_back(0);
        } else {
            int count = 0;
            int i = MSB;
            bigint value = 0;
            while (i > 0) {
                bigint increase = divisor * i;
                bigint next = value + increase;
                if (next <= remainder) {
                    value = next;
                    count += i;
                }
                i >>= 1;
            }
            quotient.value.push_back(count);
            remainder -= value;
        }
    }

    for (int i = 0; i < quotient.value.size() / 2; i++) {
        int swap = quotient.value.at(i);
        quotient.value.at(i) = quotient.value.at((quotient.value.size() - 1) - i);
        quotient.value.at(quotient.value.size() - 1 - i) = swap;
    }

    if (negative)quotient.invert();
    quotient.unPad();
    return quotient;
}

解决方案

First of all, you can implement division in time O(n^2) and with reasonable constant, so it's not (much) slower than the naive multiplication. However, if you use Karatsuba-like algorithm, or even FFT-based multiplication algorithm, then you indeed can speedup your division algorithm using Newton-Raphson.

A Newton-Raphson iteration for calculating the reciprocal of x is q[n+1]=q[n]*(2-q[n]*x).

Suppose we want to calculate floor(2^k/B) where B is a positive integer. WLOG, B≤2^k; otherwise, the quotient is 0. The Newton-Raphson iteration for x=B/2^k yields q[n+1]=q[n]*(2-q[n]*B/2^k). we can rearrange it as

q[n+1]=q[n]*(2^(k+1)-q[n]*B) >> k

Each iteration of this kind requires only integer multiplications and bit shifts. Does it converge to floor(2^k/B)? Not necessarily. However, in the worst case, it eventually alternates between floor(2^k/B) and ceiling(2^k/B) (Prove it!). So you can use some not-so-clever test to see if you are in this case, and extract floor(2^k/B). (this "not-so-clever test" should be alot faster than the multiplications in each iteration; However, it will be nice to optimize this thing).

Indeed, calculating floor(2^k/B) suffices in order to calculate floor(A/B) for any positive integers A,B. Take k such that A*B≤2^k, and verify floor(A/B)=A*ceiling(2^k/B) >> k.

Lastly, a simple but important optimization for this approach is to truncate multiplications (i.e. calculate only the higher bits of the product) in the early iterations of the Newton-Raphson method. The reason to do so, is that the results of the early iterations are far from the quotient, and it doesn't matter to perform them inaccurately. (Refine this argument and show that if you do this thing appropriately, you can divide two ≤n-bit integers in time O(M(2n)), assuming you can multiply two ≤k-bit integers in time M(k), and M(x) is an increasing convex function).