![](/img/trans.png)
[英]What is a good scripting language to integrate into high-performance applications?
[英]What algorithm should I use for high-performance large integer division?
我將大整數編碼為size_t
數組。 我已經有其他操作工作(加,減,乘); 以及一位數的划分。 但是如果可能的話,我想匹配我的乘法算法的時間復雜度(目前Toom-Cook)。
我收集有線性時間算法,用於采用我的紅利的乘法逆的各種概念。 這意味着我理論上可以在與乘法相同的時間復雜度中實現除法,因為無論如何,線性時間操作通過比較是“無關緊要的”。
我的問題是,我該怎么做呢? 什么類型的乘法逆在實踐中最好? Modulo 64^digitcount
? 當我將乘法逆乘以我的除數時,我可以推卸計算由於整數截斷而丟棄的數據部分嗎? 任何人都可以提供C或C ++偽代碼或准確解釋應該如何做到這一點?
或者是否存在比基於逆的方法更好的專用除法算法?
編輯:我挖出了上面提到的“反向”方法。 在“Art of Computer Programming,Volume 2:Seminumerical Algorithms”的第312頁上,Knuth提供了“算法R”,它是一種高精度的倒數。 他說它的時間復雜度小於乘法的時間復雜度。 然而,將它轉換為C並測試它並且不清楚將消耗多少開銷內存等,直到我對其進行編碼,這將花費一些時間,這是非常重要的。 如果沒有人打敗我,我會發布它。
GMP庫通常是良好算法的良好參考。 他們記錄的划分算法主要取決於選擇一個非常大的基數,所以你將4位數除以2位數,然后通過長除法進行。
長分區需要計算2位數乘1位數的商; 這可以遞歸地完成,或者通過預計算逆並估計商,就像使用Barrett減少一樣。
當將2n
位數除以n
位數時,遞歸版本花費O(M(n) log(n))
,其中M(n)
是乘以n
位數的成本。
如果使用牛頓算法計算逆,使用Barrett減少的版本將花費O(M(n))
,但根據GMP的文檔,隱藏常數要大得多,因此這種方法僅適用於非常大的划分。
更詳細地說,大多數除法算法背后的核心算法是“估計商與減少”計算,計算(q,r)
以便
x = qy + r
但沒有0 <= r < y
的限制。 典型的循環是
x/y
的商q
r = x - qy
r
處於某個期望的間隔 r
太大,則用r
代替x
重復。 x/y
的商是所有生成的q
的總和, r
的最終值將是真實的余數。
例如,教科書長期划分就是這種形式。 例如,步驟3涵蓋了您猜測的數字太大或太小的情況,並調整它以獲得正確的值。
分而治之的方法通過計算x'/y'
來估計x/y
的商,其中x'
和y'
是x
和y
的前導數字。 通過調整大小可以有很大的優化空間,但如果x'
是y'
兩倍,IIRC會得到最好的結果。
如果你堅持使用整數運算,那么乘以逆的方法是最簡單的IMO。 基本方法是
y
的倒數, m = floor(2^k / y)
x/y
, q = 2^(i+jk) floor(floor(x / 2^i) m / 2^j)
事實上,如果實際實現意味着您可以使用更快的互惠實現,那么實際實現可以容忍m
額外錯誤。
錯誤是分析的痛苦,但如果我記得這樣做的方法,你想選擇i
和j
使得x ~ 2^(i+j)
由於誤差的積累,你想選擇x / 2^i ~ m^2
最小化整體工作。
隨后的減少將具有r ~ max(x/m, y)
,因此給出了選擇k
的經驗法則:你希望m
的大小大約是你每次迭代計算的商的位數 - 或者相當於每次迭代要從x
刪除的位數。
我不知道乘法逆算法,但它聽起來像蒙哥馬利減少或巴雷特減少的修改。
我做bigint分區有點不同。
見bignum部門 。 特別是看一下近似分頻器和那里的2個鏈路。 一個是我的定點分頻器,其他是快速乘法算法(如NTT上的karatsuba,Schönhage-Strassen)和測量,以及我對32bit Base的快速NTT實現的鏈接。
我不確定逆乘法器是否正確。
它主要用於模運算,其中除法器是常量。 我擔心,對於任意划分,獲得bigint逆轉所需的時間和操作可能比標准划分本身更大,但由於我不熟悉它我可能是錯的 。
我在實現中看到的最常用的分頻器是Newton-Raphson分區,它與上面鏈接中的近似分頻器非常相似。
近似/迭代分頻器通常使用乘法來定義它們的速度。
對於足夠小的數字,通常是長二進制除法和32/64位數字基本除法,如果不是最快的話,它的速度足夠快:通常它們的開銷很小,並且n
是處理的最大值(不是數字位數!)
二進制除法示例:
是O(log32(n).log2(n)) = O(log^2(n))
。
它遍歷所有有效位。 在每次迭代中,您需要compare, sub, add, bitshift
。 這些操作中的每一個都可以在log32(n)
, log2(n)
是位數。
這里是我的一個bigint模板(C ++)的二進制除法示例:
template <DWORD N> void uint<N>::div(uint &c,uint &d,uint a,uint b)
{
int i,j,sh;
sh=0; c=DWORD(0); d=1;
sh=a.bits()-b.bits();
if (sh<0) sh=0; else { b<<=sh; d<<=sh; }
for (;;)
{
j=geq(a,b);
if (j)
{
c+=d;
sub(a,a,b);
if (j==2) break;
}
if (!sh) break;
b>>=1; d>>=1; sh--;
}
d=a;
}
N
是用於存儲bigint數的32位DWORD
的數量。
c = a / b
d = a % b
qeq(a,b)
是一個比較: a >= b
大於或等於(在log32(n)=N
) 0
表示a < b
, 1
表示a > b
, 2
表示a == b
sub(c,a,b)
是c = a - b
從不使用乘法獲得速度提升(如果不計算位移)
如果你使用像2 ^ 32(ALU塊)這樣的大基數的數字,那么你可以使用ALU操作中的32位構建以多項式樣式重寫整體。
這通常比二進制長除法更快,其想法是將每個DWORD處理為單個數字,或遞歸地將使用的算術除以一半直到達到CPU能力。
請參見半位寬算術分區
最重要的是用bignums計算
如果你已經優化了基本操作,那么復雜性可以進一步降低,因為子結果隨着迭代變小(改變基本操作的復雜性)一個很好的例子是基於NTT的乘法。
開銷會使事情變得混亂。
因此,運行時有時不會復制大的O復雜度,因此您應始終測量閾值並使用更快的方法來使用位數來獲得最大性能並優化您的能力。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.