為什麼兩個非十進位的數轉化一定要通過十進位?

比如說二進位轉六進位的數第一步是將二進位數轉成十進位的數第二步是將十進位的數轉成六進位的數。

那麼問題在於

1.是否能夠給出一種方法直接在兩種不同進位(甚至是任意的兩種進位)的數中轉換。

2.如果沒有,那麼十進位的數有什麼特殊之處使得十進位數成為其他兩種進位數轉換的樞紐?


只是因為你背的九九表是十進位而已。如果你會背7進位九九表(或稱之為六六表),那5進位轉7進位完全可以直接轉。

7進位六六表如下。

1x1=1

2x1=2 2x2=4

3x1=3 3x2=6 3x3=12

4x1=4 4x2=11 4x3=15 4x4=22

5x1=5 5x2=13 5x3=21 5x4=26 5x5=34

6x1=6 6x2=15 6x3=24 6x4=33 6x5=42 6x6=51

對於把5進位轉到7進位這個操作,尤其重要的是要衍生出一個7進位下5的前n次方冪的結果。

5 ^ 1 = 5

5 ^ 2 = 34

5 ^ 3 = 236

5 ^ 4 = 1552

5 ^ 5 = 12053

比如要把5進位的"12344」轉化成7進位。(之前舉例是12345,忘記了5進位下每一位最多是4)。

1 * 5 ^ 4 = 1552

2 * 5 ^ 3 = 505

3 * 5 ^ 2 = 135

4 * 5 ^ 1 = 26

4 * 5 ^ 0 = 4

用長加法加起來,別忘了逢10(7)進一,你就得到了7進位的結果2561


不是十進位有啥特殊,而是我們恰好熟悉十進位的書寫運算。

理論上,n進位數轉m進位,可以直接轉。例如只是正整數的情況下,不斷重複取余,整除直到最後商變成0即可。計算過程中的餘數序列就是m進位下從低位到高位的每一位的數。

例如7進位下的1334561轉16進位。

那麼需要在7進位下進行整除和取余運算。

哦,等等,這樣我還先要把16轉成7進位下的,emm,22.

然後你順帶建立一個7進位下的0-21的轉換表。

想一下小學你做除法求商和餘數是不是要試商,是不是需要乘法、減法……

小學學乘法是不是需要先學「個位數」乘以「個位數」的乘法口訣,再多位數乘以個位數,最後多位數乘以多位數……

所以你是不是要把這個7進位下的四則運算搞出來……

1334561%22,1334561/22

emm,最主要的是,你要不斷的提醒自己這是7進位下的運算,是不是感覺很反人類……

所以,實際上我們通過用熟悉的十進位進行中轉,雖然看起來繞了個彎路,但是,算起來卻更快。

事實上,計算機內部都是二進位,計算機做進位轉換的時候,都是有通過二進位中轉的。


當然如果說n和m存在冪次關係,那麼整除,取余就會具有一些性質,例如3進位轉81進位,因為81是3的4次方。所以整除的時候直接捨去末尾4位就完事了,取余的話直接最後4位就是餘數了。

而81進位轉3進位,就只需要沒一位直接變成4位就完事了。

當然,實際上生活中(程序猿)通常遇到的例子是二進位與八進位互轉,二進位與16進位互轉。如果是八進位和十六進位互轉,大多還是通過2進位來中轉的(假裝自己不是用程序員計算器的孩紙)。


想了一下還蠻有趣的,下邊分享一下我的思考。

先說結論:

不是必須的,進位轉換二者可以直接互相轉換。

那麼為什麼平時我們要用十進位做中轉,根本原因就是我們手裡的計算器,計算機,或者你自己的口算,所有的計算我們都默認在十進位下進行,數量這個概念我們也是用十進位表示的。

下邊是幾個蘋果。

你會脫口而出,12 個!

5 乘 8 等於多少?

40!

2 的 3 次冪等於多少?

8!

是的,這些計算我們也都是基於 10 進位的。同樣的,你把這些式子輸入計算器,計算器也默認我們在進行十進位計算。十進位下的各種計算,加減乘除,求余,求冪,這些基礎架構都已經實現了,我們可以直接使用。

因此,對於二進位也好,七進位也罷,我們都會從十進位的角度去理解它。

首先回憶一下,二進位和十進位之間的互轉,之前我總結過一篇文章,這裡的話,我直接貼過來方便大家查看。

windliang:理解進位轉換的原理?

zhuanlan.zhihu.com圖標


準備寫一篇關於浮點數存儲的,然後先寫了進位轉換,越寫越多,就單獨作為一篇文章吧。

2019.723,這個數的二進位形式是什麼樣呢?讓我們慢慢考慮。

數字的概念

首先思考一下數字是什麼?為什麼要有數字。

我有個蘋果,你腦海中會出現個蘋果。

我有個蘋果,你腦海中會出現個蘋果。

我有三十個蘋果,你腦海中會出現三十個蘋果。

我有一千個蘋果,你可能想像不出來了。

數字的作用,就是讓我們對一個東西的數量有一個更精確的認識。如果沒有數字,我可能只能說我有一桌子蘋果,我有一堆蘋果,我有一大堆蘋果,我有一大大堆蘋果,這些都是感性的認知,每個人的認知可能是不一樣的,而數字統一了我們對「量」的概念。

此外,另一個神奇的地方在於數字僅僅是「量」的概念,他沒有限制去描述什麼。

比如說五個蘋果,五隻小狗,五頭牛,五粒大米,數量都是五,但他們的體積、質量、形狀都和他們本身有關了。

數字的記錄

後來人類發明了紙筆,如果把這個「量」的概念寫下來該怎麼辦呢?

最簡單的想法就是畫豎線,每增加一個,就加一條豎線。

1 -&> I

2 -&> II

3 -&> III

4 -&> IIII

5 -&> IIIII

20 -&> ???

如果數字大了,一直畫豎線顯然是不現實的,在出土的甲骨文中發現了當時人們對數字的認識。

一些大的數字用一些特定的符號來表示,這樣如果表示 108 的話,我們只需要畫出這兩個圖形就可以了。

順序無所謂,另一個人可能畫出的是下邊的

即使順序不一樣,但他們表示的數字都是 108

進位的概念

上邊的依舊不是很方便,比如我要是想表示 1000,如果我們只有 100 對應的符號,我還是需要畫 10 個才行。接下來,人們用了一個更偉大的發明,進位制,也許因為人有 10 根手指,所以採用了 10 進位,其實就是我們現在的計數法。用0 - 9 十個符號就可以表示任意數字了。

這裡邊最偉大的發明就是0的概念了,它除了表示數量上的 0,也可以用來佔位。從而數字在不同的位置有了不同的含義。拿十進位來舉例,就是逢十進一。

得到個蘋果,寫個 1,得到個蘋果,寫個6,得到個蘋果了,怎麼表達這個數量呢,低位用0佔位,高位寫1就可以了。也就是10。此時的1不再是一,而是一個十。同理100,就表明有十個十的數量。此外再發明一個小數點,0.1,小數點右邊的一表明是十分之一。

每個位置就有了不同的含義,可以看作下邊的公式。e 是小數點後開始的數位。

[公式]

a,b,c ... 代表0 - 9中的任意一個符號,現在的數量是a1000,b 個 100,c 個 10……

比如2019.723就可以看成下邊的樣子

[公式]

上邊是十進位,讓我們想一下 2 進位。

2 進位只有兩個符號可以用,那就是01,規則是滿2 就要進 1

如果有一個蘋果那麼就記做1,如果有兩個蘋果要利用0來佔位,高位寫1,也就是10,如果有四個蘋果,那就用100來表示。

因為我們對十進位太熟悉了,如果我說「我考了100分」。大家第一反應就是我考的不錯,但如果是在二進位的世界,100其實是一個蠻小的數字。

如果把二進位換兩個符號,比如&>來表示個蘋果,&<來表示零的概念,用來佔位。我如果說我考了&>&<&<分,這樣大家就沒有條件反射覺得我考的很高了。

所以我們要明確一個概念,不同進位下,可能用了同一個符號,但對於同一個數量,那麼表示法是不一樣的。

看到這麼多蘋果,

商朝人可能會寫下,我有| 二個蘋果。

十進位的人們會寫下,我有 12 個蘋果。

二進位的人們會寫下,我有 1100 個蘋果。

可以看到對於同一個數量,大家的表示是不一樣的,即使十進位和二進位的人們用了相同的符號01,但由於進位不同,他們寫出來是不一樣的。

有一天二進位的人和十進位的人相遇了,二進位的人說,我買了1100個蘋果,然後對於十進位的人第一反應,哇,一千多個蘋果,也太多了吧。

二進位的人們,看到1100立馬腦海中聯想到了上圖的數量,但是對於十進位人們必須把這個數量轉換成自己熟悉的十進位表示,才可以在腦海里想像1100個蘋果的數量是多少。那麼怎麼轉換呢?

二進位轉十進位

讓我們從十進位的角度去看一下二進位,二進位是滿二進一,所以他的每一位的權重其實是以 2 為權重的,就是下邊的樣子。

[公式]

a,b,c ... 代表01中的一個,現在的數量是 a 個 8,b 個 4,c 個 2 ……

所以1100如果用十進位表示,就是 1 個 8 加上 1 個 4,也就是12了。這樣的話,我們就可以理解他買了多少蘋果了。

十進位轉二進位

那麼十進位怎麼轉換成二進位呢?最開始的問題,怎麼用二進位去表示 2019.723

整數的轉換

其實這就是數學上的問題了,首先我們考慮整數部分2019。根據前邊二進位轉十進位的公式,我們知道可以有下邊的等式。

[公式]

兩邊如果同時除以 2 會發生什麼呢,

[公式]

可以看到係數是 a,b,c,d的部分都整除了,只剩下 e/2

右邊的話,把2019分成兩部分20181。然後就是 [公式] 和下 [公式] 兩部分。

左邊也看做 [公式][公式] 兩部分。

左右兩部分對應相等。

右邊的部分。

[公式]

所以算出了 [公式]

再看左邊的部分。

[公式]

我們可以兩邊繼續同時除以2,就可以求出d。同理就可以求出a,b,c以及更多的係數。

再來回想一下我們的方法,兩邊同時除以2,然後被分成 [公式][公式] 兩部分,其實左邊就是商,右邊是餘數。2018/2=1009······1,對應等式左邊的部分,e 其實就等於餘數。

所以轉換的方法就是用2019除以2,餘數作為二進位的低位。商作為新的除數,繼續除以2,餘數作為二進位的低位...直到商為 0。

因為我們每次求出的都是二進位對應的低位,書寫的話習慣於先寫高位,所以倒著寫過來 11111100011就是2019的二進位形式了。

寫成代碼的形式。

public static List& integerTrans(int n) {
ArrayList& list = new ArrayList&<&>();
while (n != 0) {
list.add(n % 2);
n /= 2;
}
for (int i = list.size() - 1; i &>= 0; i--) {
System.out.print(list.get(i));
}
return list;
}

小數的轉換

考慮完整數,再來想一下小數0.723怎麼處理。同樣的,利用二進位轉十進位的公式,寫出下邊的等式。

[公式]

之前同時是利用兩邊同時除以2,對於上邊的等式肯定不行了。換一下,兩邊同時乘以2呢?看看會發生什麼。

[公式]

和之前一樣,把兩邊分別分成兩部分。

[公式]

兩部分分別對應相等,可以知道

[公式]

另外一部分的話

[公式]

我們繼續同時乘以2,變成下邊的樣子。

[公式]

這樣又可以求出 [公式] ,同樣的一直不停的繼續下去,直到得到的新的小數部分是0

所以我們的演算法就是不停的乘2取整數部分,有可能得到的新的數永遠不等於 0,這就取決於我們需要的精度了,可以隨時停止。

小數的話正著寫出來就可以了,0.1011就是十進位0.723的近似值了。

所以十進位小數轉成二進位並不會像十進位整數那麼順利。原因的話,因為二進位小數它的權重依次是0.5,0.25,0.125...,所以只有這些數的任意相加才能被精確表示。大部分的十進位小數都不能精確的用二進位表示。

甚至看起來最簡單的0.1如果轉成二進位,會是什麼樣呢?

可以看到計算過程中,出現了循環,0.4,0.8,0.6,0.2會循環出現,所以0.1的二進位表示就是0.0 0011 0011 0011...是一個無限循環小數了。它就相當於我們十進位中的1/3,寫成小數形式是0.3333333...

用代碼表示一下,我們只保留 10 位小數。

public static List& decimalTrans(double n) {
ArrayList& list = new ArrayList&<&>();
int count = 10;
while (count &> 0) {
n = n * 2;
list.add((int) (n));
n = n - (int) (n);
count--;
}
for (int i = 0; i &< list.size(); i++) { System.out.print(list.get(i)); } return list; }

歷史的發展

可以看一下現實生活中數字的發展,引自維基百科。

無位值十進位

  • 古埃及十進位:以一個豎道代表1,二並排豎道代表2,三豎道代表3,一橫道代表4,左二撇右豎道代表5,上三撇下三撇代表6,上下兩道代表8,四個(並排代表9,一個「人」字形代表10,「人」上加一橫代表20,20左加一點代表30,橫道上加一點代表40,橫道上加三豎道(如中國籌算的8)代表60,橫道上加四豎道代表80(形同中國籌算中的9)代表80,兩橫道上加三豎代表90……。
  • 古希臘十進位,1至9,10至90,100至900各有不同的單字母代表。
  • 古印度Kharosshi十進位,以一個豎道代表1,二並排豎道代表2,三豎道代表3,一個X代表4,IX代表5,||X代表6,XX代表8,10,20個有單字元代表。
  • 古印度和Brahmi十進位,和希臘十進位相似,1至9,10至90,100至900各有不同的單字母代表。符號很多。

非十進的進位制

  • 巴比倫60進位制:以一個上大下小的楔形代表1,兩個並列楔形代表2,三個並列楔形代表3,上二個楔形下二個楔形代表4,上三楔下二楔代表5,上三楔下三楔代表6,上四楔下三楔代表7,上四楔下四楔代表8,上五楔下四楔代表9;一個左小右大橫楔代10,兩個橫楔並排代表20,三個橫楔並排代表30,四個橫楔並排代表40。
  • 瑪雅20進位制:以一個點代表1,兩個點並列代表2,三點並列代表3,四點並列代表4,短橫線代表5,橫線上加一點代表6,橫線上加二點代表7,橫線上加三點代表8,橫線上加四點代表9;上下兩橫線代表10,上下兩橫線之上加一點代表11,三重疊橫線代表15,三橫線上加一,二,三點代表16,17,18;小橢圓圈上加一點代表20。

十進位制

  • 中國古代的十進位有書寫式和算籌兩種型式。
  • 印度-阿拉伯十進位制。

結束

不得不感慨下人類的聰明,將量的概念抽象出來,不同地區的獨立發展,最後又是那麼的相像。所以數字,其實就是符號對數量的映射,並且人們達成了共識。


以上是二進位和十進位的轉換,現在我們再重新思考一下進位,所謂進位無非是每一位有了不同的權重。

對於二進位權重依次是 [公式]

也就是 ... 8 4 2 1

所以對於二進位 1100 ,轉為十進位就是二進位的每一位乘以它的權重,即

1 × 8 + 1 × 4 + 0 × 2 + 0 × 1 = 12

那麼問題來了,為什麼是轉成了十進位?

這裡是關鍵了,因為我們說權重的時候,習慣性就用 10 進位去計算了權重

那麼這裡換下思路,我們不用十進位去表示權重,而是用七進位去表示權重。

讓我們熟悉一下七進位。

首先七進位用 7 個符號表示,即 0, 1, 2, 3, 4, 5, 6

再熟悉一下七進位的運算,滿 7 進 1

2 + 6 = 11

3 + 4 = 10

2 * 2 = 4

[公式]

好的,看起來有些彆扭,但事實如此,哈哈。

讓我們回到二進位的權重問題,看一下七進位下的權重。

對於二進位權重依次是 [公式]

也就是,... 11 4 2 1

所以對於二進位 1100 ,轉為七進位就是二進位的每一位乘以它的權重,即

1 × 11 + 1 × 4 + 0 × 2 + 0 × 1 = 11 + 4 = 15。

所以二進位的 1100 在七進位中就是 15

我們直接將二進位轉為了七進位!

所以,我們之所以要將其他進位先轉換為十進位,就是因為進位的權重我們默認下都是在十進位下進行的。如果在程序中,我們算某個權重,比如 [公式] ,結果一定是 8,這也決定了,我們只能將其它進位先轉為十進位。

public int twoToTen(String two) {
char[] array = two.toCharArray();
int n = array.length;
int sum = 0;
int power = 0;
for (int i = n - 1; i &>= 0; i--) {
sum = sum + (array[i] - 0) * (int) Math.pow(2, power);
power++;
}
return sum;
}

輸入二進位 1100,就會輸出十進位的 12 了。

為什麼是 10 進位的呢,因為上邊我們累加以及算權重的時候,調用的加法、乘法、冪次,都是基於 10 進位的。

那麼我們能否修改函數,直接把二進位轉為七進位呢?

我們只需要重新定義加法、乘法、以及冪次,讓運算都是基於七進位的即可。這樣算出的權重就是基於七進位的,加法、乘法也就會是基於七進位的,最終的結果當然是一個七進位的數字了。

首先我們定義一個 Seven 類,進行七進位的加法以及乘法、冪次。

思想的話,參考 Leetcode 的第二題 大數相加 。

這裡的乘法以及冪次,直接遞歸的進行了,沒有進行任何優化,只用於演示,具體優化方法參考可以參考 LeetCode 的 29 題 以及 50 題。

此外,這裡的加法只考慮了兩個正數相加,減法也只考慮了減 1。

public final class Seven {
public static int sum(int n1, int n2) {
int res = 0;
int carry = 0;
int shift = 1;
while (n1 != 0 || n2 != 0) {
int x = (n1 != 0) ? n1 % 10 : 0;
int y = (n2 != 0) ? n2 % 10 : 0;
int sum = carry + x + y;
carry = sum / 7; //保存進位
res = res + sum % 7 * shift;
n1 /= 10;
n2 /= 10;
shift *= 10;
}
if (carry != 0) {
res = res + 1 * shift;
}
return res;
}

//傳進來的數是七進位, 實現減 1
public static int subOne(int n) {
int count = 0;
//考慮需要借位的情況,比如這種 43000
while (n % 10 == 0) {
n /= 10;
count++;
}
n -= 1; //借位
//低位補 6
while (count != 0) {
n = n * 10 + 6;
count--;
}
return n;

}

public static int mul(int n1, int n2) {
if (n2 == 0) {
return 0;
}
return Seven.sum(n1, mul(n1, Seven.subOne(n2)));
}

public static int pow(int a, int b) {
if (b == 0) {
return 1;
}
return Seven.mul(a, pow(a, Seven.subOne(b)));
}
}

有了七進位運算的類,我們就可以直接把二進位直接轉換為七進位了。

public int twoToSeven(String two) {
char[] array = two.toCharArray();
int n = array.length;
int sum = 0;
int power = 0;
for (int i = n - 1; i &>= 0; i--) {
int pow = Seven.pow(2, power);
int mul = Seven.mul(array[i] - 0, pow);
sum = Seven.sum(sum, mul);
power++;
}
return sum;
}

上邊如果輸入 1100,就會輸出七進位的 15 了。

甚至,我們可以在函數增加一個參數,就可以將任意進位轉為七進位了。

public int anyToSeven(String two, int radix) {
char[] array = two.toCharArray();
int n = array.length;
int sum = 0;
int power = 0;
for (int i = n - 1; i &>= 0; i--) {
int pow = Seven.pow(radix, power);
int mul = Seven.mul(array[i] - 0, pow);
sum = Seven.sum(sum, mul);
power++;
}
return sum;
}

上邊如果輸入 1200 3,也就是三進位的 1200,就會輸出七進位的 63 了。

當然上邊的 any 只能是小於七進位的,因為我們裡邊的運算都是基於 7 進位的,允許的符號是 0 - 6,如果大於七進位上邊就會亂套了。

至於大於七進位的轉為七進位,方法的話就類似於上邊介紹的十進位轉為二進位,此時權重是固定的,然後利用除法求出每一位的值。

因此我們至少還需要實現七進位的除法,然後利用十進位轉為二進位的思想即可。這裡就不寫了,就交給大家了,哈哈。

ps: 上邊的代碼,沒有做嚴格的測試,思想應該是對的,哈哈,發現問題可以和我交流。

總結一下吧

進位轉換分為兩大類。

低進位轉到高進位,也就是我們上邊詳細討論的。我們只需要把權重用高進位的數去表示,然後在某個進位下進行相乘相加即可。

高進位轉到低進位,類比於我們熟悉的十進位轉二進位,同樣用高進位的數表示權重,此時我們在某個進位下通過除法來求出各個位即可。

其實不管高進位到低進位,還是低進位到高進位,都是基於下邊的等式。

以十進位和二進位的轉換為例。

[公式]

已知左邊,就是低進位到高進位。已知右邊,就是高進位到低進位。

左邊權重的冪次的底決定了低進位是多少。

相乘相加在多少進位下進行,決定了最終轉為了多少進位。

因此需要十進位中轉根本原因就是我們手裡的計算器,計算機,或者你自己的口算,所有的計算我們都默認在十進位下進行,數量這個概念我們也是用十進位表示的。

因此不管其他進位轉十進位,還是十進位轉其他進位都會很方便。

再補一句,如果自己去實現七進位下的加減乘除。為了減少我們的工作量,因為我們計算機已經實現了十進位的加減乘除,所以我們可以將七進位轉為十進位,然後進行相應的計算,最後再將結果轉回七進位即可。而我之前實現的七進位類,相當於在十進位的基礎上模擬了七進位的進位借位,所以更麻煩些。


人類轉的時候用十進位方便,因為我們學習除法乘法都是十進位的,比如小九九,如果是九進位要背小八八,如果是十六進位就要被小ff了。一一得一……4410,4514,……eec4,efd2,ffe1。。特殊的如二進位八進位十六進位之間直接轉或通過二進位比較方便。而計算機表示任何數都要轉二進位,一般只有輸入輸出需要轉十進位


這就好比,你英語翻譯成德語時,你會中間下意識有個漢語過渡


推薦閱讀:

TAG:數學 | 十進位 | 二進位 | 進位 | 基礎數學 |