golang的大數表示

2021-08-30 17:56:03 字數 4080 閱讀 1653

math/big包中定義了數值超出64位的大數資料結構和有關函式。

type int struct

type nat [

]word

type word uint

大數的絕對值儲存在abs中,實際上就是將絕對值的二進位制表示從右至左按64位切塊(這裡均以64位字長為例),依次放入abs[0]、abs[1]、… 。

用公式描述就是將大數以264為基數展開,形如xn-1264(n-1)+xn-2264(n-2)+ … + x1264+x0,按由右至左的順序記錄各個係數xi ,即abs[0]=x0,abs[1]=x1,… 。這裡0 <= xi

< 264,n為切片長度,顯然xn-1不等於0。

雖然看起來比較複雜,但實質上與我們熟悉的十進位制表示法完全相同,只不過現在是264進製,也就是以264為基數,係數不是10以內,而是264以內。例如:十進位制的8705表示的是8x103+7x102+0x10+5這個數,它的係數是8、7、0、5;如果換成是264進製,它表示的就是8x264x3+7x264x2+0x264+5這個數。也可以說abs儲存的是264進製表示法的係數。

感覺上,大數表示法似乎應該減少了儲存空間的占用,實際上是錯覺,不考慮符號儲存問題,它和原生態地儲存大數占用的空間是一樣的,對於任意大數的每乙個位元都要精確表達。

func

newint

(x int64

)*int

以x為基礎新建乙個大數,並返回int型指標。當機器字長為32位時,該函式會將x拆成2個係數。更有用的方法是:

func

(z *int)

setstring

(s string

, base int)(

*int,

bool

)

該方法將字串s解釋為以base為基數的乙個大數,並填充z,返回的bool值表示成功與否。base為0時,由s的字首決定字串所代表的數字進製,0-八進位制,0x或0x-十六進製制,0b或0b-二進位制,其它-十進位制。比如:

var a big.int

a.setstring

(「0x742e666d7400747970652e2e65712e662e74016d」,

0)

定義了乙個20位元組長的大數。這裡不接收setstring()的返回值也沒有問題。這個方法的實現有點意思,為了效率使用了組合語言,下面作一簡要剖析。

將字串s轉化為乙個位元組流。

確定符號,然後把後續的位元組流都當成無符號數來處理,即絕對值。

確定進製,如是否為0、0x、0b先導等等。

根據進製,確定乙個字長能夠容納的該進製數字的個數n和基本權重bn,比如:64位字長可容納的最大十進位制數為1.8447x1019,共20個數字,這裡取n=19,基本權重為1019。因為如果取20可能會「冒頂」,如2x1019也是20個數字但放不進乙個字長內。所以這個n實際上是該進製能安全地存入乙個字長的最大數字個數。至於基本權重將用於後續進製轉換計算。從這裡還能預感到乙個問題,比如1x1019雖然是20個數字,但能放在乙個字長內,golang是不是用了兩個字長?

從現在開始迴圈讀入位元組流,每n個數字呼叫方法muladdww將值寫入係數切片。首次寫入就是簡單的賦值,之後的寫入都要呼叫muladdvww這個彙編函式來處理。為了看清這個函式的過程,先以十進位制為例在紙面上推導一下新增切片元素的過程。

收尾,將剩餘不足n個數字的值按前法(權重有所變化)寫入切片。

下面是彙編**:

// func muladdvww(z, x word, y, r word) (c word)

// x為原係數切片,至少已有乙個元素

// z是要生成的新切片,已基於x建立,長度相同,容量比x大1至4

// r為要新增的值

// y是權重

// c是計算後最高位的係數,不在這裡寫入z

text ·muladdvww

(sb)

,nosplit,$-

1 movq z+

0(fp)

, r10 // r10=新切片元素指標,即reflect.sliceheader.data

movq x+

24(fp)

, r8 // r8 =原切片元素指標

movq y+

48(fp)

, r9 // r9 =權重

movq r+

56(fp)

, cx // cx =要新增的值

movq z_len+

8(fp)

, r11 // r11=新切片長度,即reflect.sliceheader.len

movq $0

, bx // bx =0,迴圈計數,以下用i表示

cmpq r11, $4

jl e5 // 新切片長度小於4

// 待處理元素數量》=4時,由u5過程處理,否則由l5處理,估計是為了提高快取命中率

// 處理的過程基本相同

u5: movq (0*

8)(r8)

(bx*8)

, ax

mulq r9

addq cx, ax

adcq $0

, dx

movq ax,(0

*8)(r10)

(bx*8)

movq dx, cx

movq (1*

8)(r8)

(bx*8)

, ax

mulq r9

addq cx, ax

adcq $0

, dx

movq ax,(1

*8)(r10)

(bx*8)

movq dx, cx

movq (2*

8)(r8)

(bx*8)

, ax

mulq r9

addq cx, ax

adcq $0

, dx

movq ax,(2

*8)(r10)

(bx*8)

movq dx, cx

movq (3*

8)(r8)

(bx*8)

, ax

mulq r9

addq cx, ax

adcq $0

, dx

movq ax,(3

*8)(r10)

(bx*8)

movq dx, cx

addq $4

, bx // 已處理的i += 4

leaq 4

(bx)

, dx // 測試 i+4 和 切片長度的大小

cmpq dx, r11

jle u5 // 還有4個以上待處理元素

jmp e5

l5: movq (r8)

(bx*8)

, ax // ax=原切片第i個元素值

mulq r9 // (dx, ax) = x[i] * y

addq cx, ax // (dx, ax) = x[i] * y + cx

adcq $0

, dx

movq ax,

(r10)

(bx*8)

// 低64位寫入新切片

movq dx, cx // 高64位放入cx

addq $1

, bx // i++

e5: cmpq bx, r11

jl l5 // i小於新切片長度

movq cx, c+

64(fp)

// 將最終的高64位返回

ret

func

(x *int)

string()

string

//以原生態的形式顯示大數的符號和各個係數。

func

(x *int)

text

(base int

)string

//以指定進製顯示大數的數值形式。

big包中提供了很多大數計算的方法,如加減乘除、與或非等等。當然還有大浮點數、大有理數。

羅馬數表示整數

羅馬數字共有七個,即 i 1 v 5 x 10 l 50 c 100 d 500 m 1000 按照下面三條規則可以表示任意正整數。重複數次 乙個羅馬數字重複幾次,就表示這個數的幾倍。右加左減 在乙個較大的羅馬數字的右邊記上乙個較小的羅馬數字,表示大數字加小數字。在乙個較大的數字的左邊記上乙個較小的...

整數表示問題。

我們知道,如果x,y互素時ax by可以表示任意整數 其中a,b為整數 如果設定條件x,y 0,並且ax by 0時,求能表示的整數集中連續的整數最小的是多少?例如輸入x 3,y 4.整數集為0,3,4,6,7,8,9 則輸出6 輸入1行x和y,輸出一行表示最小整數 其中x,y為32位整數且互素。現...

分數表示法

我們知道整數是可以用整數加分數的形式表示的,例如5 3 9562 4781 3 9712 4856 3 2956 1478 3 9172 4586 2 9762 3254等等 其中1 9只在前4種的整數部分和分子分母出現且只出現一次,我們求這樣的式子的個數即test x 其中65535 x 0,例如...