另外两种证明素数无穷多的方法

    我们已经知道,素数有无穷多个。当时我们用的是最普遍的证明方法:

假设存在最大的素数P,那么我们可以构造一个新的数2 * 3 * 5 * 7 * … * P + 1(所有的素数乘起来加1)。显然这个数不能被任一素数整除(所有素数除它都余1),这说明我们找到了一个更大的素数。

    这里,我们将再提供两种新的证明方法,来自cut-the-knot两篇新文。

用Fermat数证明素数无穷多
    Fermat数是指形为2^(2^n)+1的数,我们把2^(2^n)+1记作F(n),其中n可以取所有自然数。显然所有的Fermat数都是奇数。一会儿我们将看到任两个Fermat数都是互素的,也就是说,每一个Fermat数的每一个素因子都与其它Fermat数的素因子不同。这也就说明,素数个数有无穷多。
    引理1:F(0) * F(1) * F(2) * … * F(n-1) = F(n) – 2, n>=1
    证明:数学归纳法。F(0)=3且F(1)=5,那么k=1时显然成立。假设k=n成立,则当k=n+1时:
    F(0) * F(1) * F(2) * … * F(n)
  = ( F(0) * F(1) * F(2) * … * F(n-1) ) * F(n)
  = ( F(n)-2 ) * F(n)
  = ( 2^(2^n)-1 ) * ( 2^(2^n)+1 )
  = 2^(2^(n+1))-1
  = F(n+1)-2

    引理2:对任意两个不相等的自然数n和m,有F(n)和F(m)互素。
    证明:假设t同时整除F(n)和F(m),m<n。根据引理1,有:
    F(n)=F(0) * F(1) * F(2) * … * F(m) * … * F(n-1) – 2
    这说明t可以整除
    F(0) * F(1) * F(2) * … * F(m) * … * F(n-1) – F(n) = 2
    注意到2只有两个因数1和2。前面说过Fermat数都是奇数,因此不可能被2整除。这样,t只能为1,这就证明了两个数互素。

用*-集合证明素数无穷多
    *-集合是一个正整数集合{a1, a2, … an},使得对所有不相等的i和j都有ai-aj整除ai。
    引理1:对所有n>=2,都存在一个大小为n的*-集合。
    证明:数学归纳法。{1,2}显然是一个大小为2的*-集合。假设{a1, a2, … an}是一个*-集合。定义b0为a1*a2*…*an(即所有ai的乘积)。对所有不超过n的正整数k,令bk=b0+ak,那么{b0, b1, b2, …, bn}就是一个大小为n+1的*-集。

    引理2:假设{a1, a2, … an}是一个*-集合。对所有不超过n的正整数i,定义fi=2^ai+1,那么f1, f2, …, fn两两互素。
    证明:显然fi都是奇数。假设fk和fm(fk>fm)可以被同一个素数p整除,那么p也只能是奇数。p可以整除fk-fm即2^am * ( 2^(ak-am)-1 )。由于p是奇数,那么它只可能是整除2^(ak-am)-1。
    如果有s整除t,那么2^s-1整除2^t-1。于是,根据*-集合的定义,2^(ak-am)-1整除2^ak-1。那么p就可以整除2^ak-1。但p也能整除2^ak+1,于是我们得出p整除2,这与p为奇数矛盾。

    定理:素数有无穷多个
    证明:根据引理1和2,对任意大的n,都存在大小为n的集合,里面的数两两互素,即至少存在n个不同的素因子。这就说明了素数的个数可以任意多。

同余运算及其基本性质

    100除以7的余数是2,意思就是说把100个东西七个七个分成一组的话最后还剩2个。余数有一个严格的定义:假如被除数是a,除数是b(假设它们均为正整数),那么我们总能够找到一个小于b的自然数r和一个整数m,使得a=bm+r。这个r就是a除以b的余数,m被称作商。我们经常用mod来表示取余,a除以b余r就写成a mod b = r。
    如果两个数a和b之差能被m整除,那么我们就说a和b对模数m同余(关于m同余)。比如,100-60除以8正好除尽,我们就说100和60对于模数8同余。它的另一层含义就是说,100和60除以8的余数相同。a和b对m同余,我们记作a≡b(mod m)。比如,刚才的例子可以写成100≡60(mod 8)。你会发现这种记号到处都在用,比如和数论相关的书中就经常把a mod 3 = 1写作a≡1(mod 3)。
    之所以把同余当作一种运算,是因为同余满足运算的诸多性质。比如,同余满足等价关系。具体地说,它满足自反性(一个数永远和自己同余)、对称性(a和b同余,b和a也就同余)和传递性(a和b同余,b和c同余可以推出a和c同余)。这三个性质都是显然的。
    同余运算里还有稍微复杂一些的性质。比如,同余运算和整数加减法一样满足“等量加等量,其和不变”。小学我们就知道,等式两边可以同时加上一个相等的数。例如,a=b可以推出a+100=b+100。这样的性质在同余运算中也有:对于同一个模数m,如果a和b同余,x和y同余,那么a+x和b+y也同余。在我看来,这个结论几乎是显然的。当然,我们也可以严格证明这个定理。这个定理对减法同样有效。

    性质:如果a≡b(mod m),x≡y(mod m),则a+x≡b+y(mod m)。
    证明:条件告诉我们,可以找到p和q使得a-mp = b-mq,也存在r和s使得x-mr = y-ms。于是a-mp + x-mr = b-mq + y-ms,即a+x-m(p+r) = b+y-m(q+s),这就告诉我们a+x和b+y除以m的余数相同。

    容易想到,两个同余式对应相乘,同余式两边仍然相等:
    如果a≡b(mod m),x≡y(mod m),则ax≡by(mod m)。
    证明:条件告诉我们,a-mp = b-mq,x-mr = y-ms。于是(a-mp)(x-mr) = (b-mq)(y-ms),等式两边分别展开后必然是ax-m(…) = by-m(…)的形式,这就说明ax≡by(mod m)。

    现在你知道为什么有的题要叫你“输出答案mod xxxxx的结果”了吧,那是为了避免高精度运算,因为这里的结论告诉我们在运算过程中边算边mod和算完后再mod的结果一样。假如a是一个很大的数,令b=a mod m,那么(a * 100) mod m和(b * 100) mod m的结果是完全一样的,这相当于是在a≡b (mod m)的两边同时乘以100。这些结论其实都很显然,因为同余运算只关心余数(不关心“整的部分”),完全可以每一次运算后都只保留余数。因此,整个运算过程中参与运算的数都不超过m,避免了高精度的出现。

    在证明Fermat小定理时,我们用到了这样一个定理:
    如果ac≡bc(mod m),且c和m互质,则a≡b(mod m) (就是说同余式两边可以同时除以一个和模数互质的数)。
    证明:条件告诉我们,ac-mp = bc-mq,移项可得ac-bc = mp-mq,也就是说(a-b)c = m(p-q)。这表明,(a-b)c里需要含有因子m,但c和m互质,因此只有可能是a-b被m整除,也即a≡b(mod m)。

    可能以后还要用到更多的定理,到时候在这里更新。

Matrix67原创
转贴请注明出处

数论部分第一节:素数与素性测试

    一个数是素数(也叫质数),当且仅当它的约数只有两个——1和它本身。规定这两个约数不能相同,因此1不是素数。对素数的研究属于数论范畴,你可以看到许多数学家没事就想出一些符合某种性质的素数并称它为某某某素数。整个数论几乎就围绕着整除和素数之类的词转过去转过来。对于写代码的人来说,素数比想像中的更重要,Google一下BigPrime或者big_prime你总会发现大堆大堆用到了素数常量的程序代码。平时没事时可以记一些素数下来以备急用。我会选一些好记的素数,比如4567, 124567, 3214567, 23456789, 55566677, 1234567894987654321, 11111111111111111111111 (23个1)。我的手机号前10位是个素数。我的网站域名的ASCII码连起来(77 97 116 114 105 120 54 55 46 99 111 109)也是个素数。还有,我的某个MM的八位生日也是一个素数。每次写Hash函数之类的东西需要一个BigPrime常量时我就取她的生日,希望她能给我带来好运。偶尔我叫她素MM,没人知道是啥意思,她自己也不知道。
    素数有很多神奇的性质。我写5个在下面供大家欣赏。

1. 素数的个数无限多(不存在最大的素数)
  证明:反证法,假设存在最大的素数P,那么我们可以构造一个新的数2 * 3 * 5 * 7 * … * P + 1(所有的素数乘起来加1)。显然这个数不能被任一素数整除(所有素数除它都余1),这说明我们找到了一个更大的素数。

2. 存在任意长的一段连续数,其中的所有数都是合数(相邻素数之间的间隔任意大)
  证明:当0<a<=n时,n!+a能被a整除。长度为n-1的数列n!+2, n!+3, n!+4, …, n!+n中,所有的数都是合数。这个结论对所有大于1的整数n都成立,而n可以取到任意大。

3. 所有大于2的素数都可以唯一地表示成两个平方数之差。
  证明:大于2的素数都是奇数。假设这个数是2n+1。由于(n+1)^2=n^2+2n+1,(n+1)^2和n^2就是我们要找的两个平方数。下面证明这个方案是唯一的。如果素数p能表示成a^2-b^2,则p=a^2-b^2=(a+b)(a-b)。由于p是素数,那么只可能a+b=p且a-b=1,这给出了a和b的唯一解。

4. 当n为大于2的整数时,2^n+1和2^n-1两个数中,如果其中一个数是素数,那么另一个数一定是合数。
  证明:2^n不能被3整除。如果它被3除余1,那么2^n-1就能被3整除;如果被3除余2,那么2^n+1就能被3整除。总之,2^n+1和2^n-1中至少有一个是合数。

5. 如果p是素数,a是小于p的正整数,那么a^(p-1) mod p = 1。
  这个证明就有点麻烦了。
    首先我们证明这样一个结论:如果p是一个素数的话,那么对任意一个小于p的正整数a,a, 2a, 3a, …, (p-1)a除以p的余数正好是一个1到p-1的排列。例如,5是素数,3, 6, 9, 12除以5的余数分别为3, 1, 4, 2,正好就是1到4这四个数。
    反证法,假如结论不成立的话,那么就是说有两个小于p的正整数m和n使得na和ma除以p的余数相同。不妨假设n>m,则p可以整除a(n-m)。但p是素数,那么a和n-m中至少有一个含有因子p。这显然是不可能的,因为a和n-m都比p小。
    用同余式表述,我们证明了:
(p-1)! ≡ a * 2a * 3a * … * (p-1)a (mod p)
    也即:
(p-1)! ≡ (p-1)! * a^(p-1) (mod p)
    两边同时除以(p-1)!,就得到了我们的最终结论:
1 ≡ a^(p-1) (mod p)

    可惜最后这个定理最初不是我证明的。这是大数学家Fermat证明的,叫做Fermat小定理(Fermat's Little Theorem)。Euler对这个定理进行了推广,叫做Euler定理。Euler一生的定理太多了,为了和其它的“Euler定理”区别开来,有些地方叫做Fermat小定理的Euler推广。Euler定理中需要用一个函数f(m),它表示小于m的正整数中有多少个数和m互素(两个数只有公约数1称为互素)。为了方便,我们通常用记号φ(m)来表示这个函数(称作Euler函数)。Euler指出,如果a和m互素,那么a^φ(m) ≡ 1 (mod m)。可以看到,当m为素数时,φ(m)就等于m-1(所有小于m的正整数都与m互素),因此它是Fermat小定理的推广。定理的证明和Fermat小定理几乎相同,只是要考虑的式子变成了所有与m互素的数的乘积:m_1 * m_2 … m_φ(m) ≡ (a * m_1)(a * m_2) … (a * m_φ(m)) (mod m)。我为什么要顺便说一下Euler定理呢?因为下面一句话可以增加我网站的PV:这个定理出现在了The Hundred Greatest Theorems里。

    谈到Fermat小定理,数学历史上有很多误解。很长一段时间里,人们都认为Fermat小定理的逆命题是正确的,并且有人亲自验证了a=2, p<300的所有情况。国外甚至流传着一种说法,认为中国在孔子时代就证明了这样的定理:如果n整除2^(n-1)-1,则n就是素数。后来某个英国学者进行考证后才发现那是因为他们翻译中国古文时出了错。1819年有人发现了Fermat小定理逆命题的第一个反例:虽然2的340次方除以341余1,但341=11*31。后来,人们又发现了561, 645, 1105等数都表明a=2时Fermat小定理的逆命题不成立。虽然这样的数不多,但不能忽视它们的存在。于是,人们把所有能整除2^(n-1)-1的合数n叫做伪素数(pseudoprime),意思就是告诉人们这个素数是假的。
    不满足2^(n-1) mod n = 1的n一定不是素数;如果满足的话则多半是素数。这样,一个比试除法效率更高的素性判断方法出现了:制作一张伪素数表,记录某个范围内的所有伪素数,那么所有满足2^(n-1) mod n = 1且不在伪素数表中的n就是素数。之所以这种方法更快,是因为我们可以使用二分法快速计算2^(n-1) mod n 的值,这在计算机的帮助下变得非常容易;在计算机中也可以用二分查找有序数列、Hash表开散列、构建Trie树等方法使得查找伪素数表效率更高。
    有人自然会关心这样一个问题:伪素数的个数到底有多少?换句话说,如果我只计算2^(n-1) mod n的值,事先不准备伪素数表,那么素性判断出错的概率有多少?研究这个问题是很有价值的,毕竟我们是OIer,不可能背一个长度上千的常量数组带上考场。统计表明,在前10亿个自然数中共有50847534个素数,而满足2^(n-1) mod n = 1的合数n有5597个。这样算下来,算法出错的可能性约为0.00011。这个概率太高了,如果想免去建立伪素数表的工作,我们需要改进素性判断的算法。

    最简单的想法就是,我们刚才只考虑了a=2的情况。对于式子a^(n-1) mod n,取不同的a可能导致不同的结果。一个合数可能在a=2时通过了测试,但a=3时的计算结果却排除了素数的可能。于是,人们扩展了伪素数的定义,称满足a^(n-1) mod n = 1的合数n叫做以a为底的伪素数(pseudoprime to base a)。前10亿个自然数中同时以2和3为底的伪素数只有1272个,这个数目不到刚才的1/4。这告诉我们如果同时验证a=2和a=3两种情况,算法出错的概率降到了0.000025。容易想到,选择用来测试的a越多,算法越准确。通常我们的做法是,随机选择若干个小于待测数的正整数作为底数a进行若干次测试,只要有一次没有通过测试就立即把
这个数扔回合数的世界。这就是Fermat素性测试。
    人们自然会想,如果考虑了所有小于n的底数a,出错的概率是否就可以降到0呢?没想到的是,居然就有这样的合数,它可以通过所有a的测试(这个说法不准确,详见我在地核楼层的回复)。Carmichael第一个发现这样极端的伪素数,他把它们称作Carmichael数。你一定会以为这样的数一定很大。错。第一个Carmichael数小得惊人,仅仅是一个三位数,561。前10亿个自然数中Carmichael数也有600个之多。Carmichael数的存在说明,我们还需要继续加强素性判断的算法。

    Miller和Rabin两个人的工作让Fermat素性测试迈出了革命性的一步,建立了传说中的Miller-Rabin素性测试算法。新的测试基于下面的定理:如果p是素数,x是小于p的正整数,且x^2 mod p = 1,那么要么x=1,要么x=p-1。这是显然的,因为x^2 mod p = 1相当于p能整除x^2-1,也即p能整除(x+1)(x-1)。由于p是素数,那么只可能是x-1能被p整除(此时x=1)或x+1能被p整除(此时x=p-1)。
    我们下面来演示一下上面的定理如何应用在Fermat素性测试上。前面说过341可以通过以2为底的Fermat测试,因为2^340 mod 341=1。如果341真是素数的话,那么2^170 mod 341只可能是1或340;当算得2^170 mod 341确实等于1时,我们可以继续查看2^85除以341的结果。我们发现,2^85 mod 341=32,这一结果摘掉了341头上的素数皇冠,面具后面真实的嘴脸显现了出来,想假扮素数和我的素MM交往的企图暴露了出来。
    这就是Miller-Rabin素性测试的方法。不断地提取指数n-1中的因子2,把n-1表示成d*2^r(其中d是一个奇数)。那么我们需要计算的东西就变成了a的d*2^r次方除以n的余数。于是,a^(d * 2^(r-1))要么等于1,要么等于n-1。如果a^(d * 2^(r-1))等于1,定理继续适用于a^(d * 2^(r-2)),这样不断开方开下去,直到对于某个i满足a^(d * 2^i) mod n = n-1或者最后指数中的2用完了得到的a^d mod n=1或n-1。这样,Fermat小定理加强为如下形式:
    尽可能提取因子2,把n-1表示成d*2^r,如果n是一个素数,那么或者a^d mod n=1,或者存在某个i使得a^(d*2^i) mod n=n-1 ( 0<=i<r ) (注意i可以等于0,这就把a^d mod n=n-1的情况统一到后面去了)
    Miller-Rabin素性测试同样是不确定算法,我们把可以通过以a为底的Miller-Rabin测试的合数称作以a为底的强伪素数(strong pseudoprime)。第一个以2为底的强伪素数为2047。第一个以2和3为底的强伪素数则大到1 373 653。
    Miller-Rabin算法的代码也非常简单:计算d和r的值(可以用位运算加速),然后二分计算a^d mod n的值,最后把它平方r次。程序的代码比想像中的更简单,我写一份放在下边。虽然我已经转C了,但我相信还有很多人看不懂C语言。我再写一次Pascal吧。函数IsPrime返回对于特定的底数a,n是否是能通过测试。如果函数返回False,那说明n不是素数;如果函数返回True,那么n极有可能是素数。注意这个代码的数据范围限制在longint,你很可能需要把它们改成int64或高精度计算。
function pow( a, d, n:longint ):longint;
begin
   if d=0 then exit(1)
   else if d=1 then exit(a)
   else if d and 1=0 then exit( pow( a*a mod n, d div 2, n) mod n)
   else exit( (pow( a*a mod n, d div 2, n) * a) mod n);
end;

function IsPrime( a,n:longint ):boolean;
var
   d,t:longint;
begin
   if n=2 then exit(true);
   if (n=1) or (n and 1=0) then exit(false);
   d:=n-1;
   while d and 1=0 do d:=d shr 1;
   t:=pow( a, d, n );
   while ( d<>n-1 ) and ( t<>1 ) and ( t<>n-1 ) do
   begin
      t:=(t * t)mod n;
      d:=d shl 1;
   end;
   exit( (t=n-1) or (d and 1=1) );
end;

    对于大数的素性判断,目前Miller-Rabin算法应用最广泛。一般底数仍然是随机选取,但当待测数不太大时,选择测试底数就有一些技巧了。比如,如果被测数小于4 759 123 141,那么只需要测试三个底数2, 7和61就足够了。当然,你测试的越多,正确的范围肯定也越大。如果你每次都用前7个素数(2, 3, 5, 7, 11, 13和17)进行测试,所有不超过341 550 071 728 320的数都是正确的。如果选用2, 3, 7, 61和24251作为底数,那么10^16内唯一的强伪素数为46 856 248 255 981。这样的一些结论使得Miller-Rabin算法在OI中非常实用。通常认为,Miller-Rabin素性测试的正确率可以令人接受,随机选取k个底数进行测试算法的失误率大概为4^(-k)。

    Miller-Rabin算法是一个RP算法。RP是时间复杂度的一种,主要针对判定性问题。一个算法是RP算法表明它可以在多项式的时间里完成,对于答案为否定的情形能够准确做出判断,但同时它也有可能把对的判成错的(错误概率不能超过1/2)。RP算法是基于随机化的,因此多次运行该算法可以降低错误率。还有其它的素性测试算法也是概率型的,比如Solovay-Strassen算法。另外一些素性测试算法则需要预先知道一些辅助信息(比如n-1的质因子),或者需要待测数满足一些条件(比如待测数必须是2^n-1的形式)。前几年AKS算法轰动世界,它是第一个多项式的、确定的、无需其它条件的素性判断算法。当时一篇论文发表出来,题目就叫PRIMES is in P,然后整个世界都疯了,我们班有几个MM那天还来了初潮。算法主要基于下面的事实:n是一个素数当且仅当(x-a)^n≡(x^n-a) (mod n)。注意这个x是多项式中的未知数,等式两边各是一个多项式。举个例子来说,当a=1时命题等价于如下结论:当n是素数时,杨辉三角的第n+1行除两头的1以外其它的数都能被n整除。

Matrix67原创
转贴请注明出处

漫话进位制

    人有十个手指,用手指的伸屈来计数非常方便。但一旦对象的数目超过10个了,手指头就不够用了。当然,有人会想到还有脚趾头。搬弄脚趾头是不现实的,数手指头只需要站着比划一下就可以了,数脚趾头还需要坐下来慢慢研究。一种好的方法是每次数完了十个指头后在什么地方做一个标记,比如在地上放一个木棒。人们可以把这根木棒想像成一个“大指头”,它相当于十个指头。这样,我有37个MM就被表示成了地上3个木棒加上我7个手指头。哈哈,你的MM数只有两根木棒加4个手指头,于是我的MM比你的多。久而久之,人们就只接受0到9这十个数字了,再大的数就用几个数字合起来表示。这种“满十进一”的数字系统就叫做十进位制。
    如果人只有八个手指头又会怎样呢?那我们现在很可能正在使用八进制,数学发展起来后我们最终只接受八个数字,而大于8的数字就用更高一级的计量单位表示。代表这八个数字的很可能是些星际之门里的怪符号,这里为了便于叙述,我们仍然使用阿拉伯数字的0到7来表示。于是,人类数数的方式将变为:0,1,2,3,4,5,6,7,10,11,12,13,14,15,16,17,20,…。这里,数字8被记作10,数字64则用100代替。在这个数学世界里,6+5=13,因为6+1得到的数已经是一位数中最大的了,再加的话只能“进一位”了。“满八进一”将成为数学运算的基本法则。
    如果人有12根手指,12进制将成为更难想像的事。在12进制中,人类会把10和11直接想成是一个“数字”。研究的进位制大于10时,大于9的数字我们习惯上用大写字母ABC来表示。这样,自然数序列里将多出两个符号A和B来,数数的方式变为…,8,9,A,B,10,11,12,…。

    我们自然会想,人类生活中究竟有没有其它的进位制呢?当然有。比如,时间和角度就是60进位制,60秒=1分。还有更怪的,计算机的储存容量单位是1024进制的,1MB=1024KB。当然,这也是有原因的。我们在研究几何时常常需要用到1/2,1/3,1/4或者1/6,我们希望这些分数在角度进制下恰好都是整数以便于运算。于是,角度的进位制就变成了60。为什么时间也是60进位制呢?因为时间和角度密切相关,你看看你的手表就知道了(别告诉我你的手表是数字型的)。为什么钟和手表又要用圆形表盘的方式来表示时间呢?其实人自古以来计算时间都是用的圆形盘面,因为地球绕太阳旋转和地球的自转使得时间具有了周期性。
    计算机使用二进制,因为计算机元件只有两种状态(开和关,或者说通电和断电),因此计算机只认0和1两个数字。1024是2的幂,又比较接近1000符合人的习惯,因此把1024当成了计算机容量的进位制。

    前段时间有人在OIBH上问,为什么纸币的面值都是1*10^n、2*10^n、5*10^n呢。有人回答说这样的货币系统可以使得某种贪心方法正确。确实有这个结论,这样的货币系统使得解决“凑钱和找零时最少使用多少张纸币”这一问题的贪心算法(不断拿最大面额的纸币)是正确。但用这一点来解释我们的问题显然是可笑的,人们首先考虑的并不是如何方便地使用最少纸币,而是如何方便地得到总面额。一个只有1元纸币和7元纸币的货币系统同样满足贪心性质,但显然傻子才会设计出这种别扭的货币系统来。因此,我的回答是“纸币的面值取10的约数,这样的话凑钱和找零最方便”。但是,有人会问,要是我们使用的进位制不是10怎么办?换句话说,如果我们使用的是23进位制,除1和本身外没有其它约数的话,又该怎样设置货币系统呢。答案非常出人意料,如果我们使用的是23进位制的话,我们很可能根本发展不出数学这门学科来。10=1+2+3+4,是前四个正整数的和;10又是2和5的积;这样的进位制非常适合数学的发展。同样地,6=1+2+3,6=2*3,因此可能正在使用六进制的昆虫们很容易发展出数学来(六足动物的数学非常强,不是有人发现了蜜蜂蜂巢的六边形样式设计是最科学的么)。大家看过《计算机中的上帝》(Calculating God)吗?那里面构造了这样一种生物:他有23根手指。这种别扭的数字最多只能让人联想到乔丹和染色体,除此之外没有任何特性。这给这种生物的数学发展带来不可逾越的困难。而事实上,这种生物恰好又没有发展数学的必要性。他就好像人类一样,对较小的物体个数具有直接感知的能力。人类可以直接感知的物体数量一般不超过6。也就是说,如果你眼前有3个,或者5个东西,你不需要数,看一眼就知道有多少个;但当你眼前出现的物体数目达到7个或者8个时,你就必须要数一数才知道个数了。而我们所说的生物面对的物体数目多达46个时仍然可以一眼分辨出多少来,数目超过46后就统称为“很多”了。直接感知数目达到50甚至60多的生物个体就扮演着该种族中的僧侣角色。46这个数字对于种族的生存已经完全足够了,他们在组建部落时总会保证部落的个体数不超过这个数字。因此,这种生物不需要数数的能力,他们也就无须发展数学了。他们不知道30加30等于多少,从某种意义上说他们甚至不知道一加一等于几,因为他们头脑中根本没有数字这个概念。作为一种补偿,他们对事物的感知能力相当敏锐。他们甚至直接凭直觉感知到了相对论,因为他们的思维不受演绎逻辑的束缚。

    下文将介绍两套进制转换的方法,然后介绍这两套方法在小数转换上的应用。更多的进位制相关应用你可以在文后的习题部分中体会到。

    在讲进位制时,大多数教材会教大家二进制和十进制如何互换。今天我就偏不这样讲,我要和那些教材讲得一样了我还不如不写今天这些东西。二进制虽然常用,但比较特殊,很可能会了二进制但仍然不会其它进制;我们今天当一回蜜蜂,看看六进制和十进制怎么互相转换。学会这个后,任意进制间的转换你就应该都会了。
    说起进位制时往往要回到最根本的一些计数方法上。这篇日志是我第237篇日志。数字“237”表示两个百,加上三个十,加上七个单位一。我们把它们分别叫百位、十位和个位,同一个数字在不同数位上表示的实际数量不同。用一个式子表示上面的意思就是,237=2*100+3*10+7。这就是进位制的实际意义。
    现在,假如我是一只勤劳的蜜蜂。我写237篇日志是肯定不可能的了,因为我的数学世界里根本没有7这个数字。那就说我写了235篇日志吧。结合前面所说的东西,“十位”上的数表示有多少个6,“百位”上的数表示有多少个36(后面提到的十位、百位打引号表明这不是十进制中的“十”和“百”)。于是,六进制下的235就应该等于2*6^2+3*6+5。这个算式你用什么进制算出答案就相当于把六进制中的235转换为了什么进制。不过你要把这个式子当成别的进制算是不大可能的,算之前你估计得重新背一遍乘法口诀表(注意我为什么不说是“九九”乘法口诀表)。这就是我们为什么一般只研究十进制与其它进制互换的原因。我们用熟悉的十进制进行计算,得出2*6^2+3*6+5=72+18+5=95。这是按定义进行进制转换的方法。六进制的235等于十进制的95,我们记作(
235)6=(95)10。那个6和10是下标,应该像H2O的2一样小小地写在下面。我就懒得排版了,反正转贴个几次就成Plain Text了。
    下面的任务是,考虑怎么把(95)10变回(235)6。使用六进制计算13*10+5可以得到235(十位上的9相当于六进制中的13),但我们说过六进制计算很麻烦。下面我们给出一种把十进制转换为六进制的方法,仔细思考你会发现这种方法显然是正确的。我们把所有6的幂从小到大写出来:1,6,36,216,…。216远远超过95了,因此95的六进制不可能是四位数。95里面有两个36,因此在最高位上写个“2”。去掉两个36,95里只剩23。23里有三个6,数字3将填写在第二位上,去掉这三个6最后所剩的5留给最末位。换句话说,我们不断寻找最大的x使得6^x不超过当前数,当前数减去6^x并在右起第x+1位上加一。这事实上是前面六进制转十进制的逆过程。
    上面的进制互换方法是一套方法,这是我们所介绍的第一套方法。这套方法的特点是正确性很显然,但是计算比较复杂,又费马达又费电。我们需要一个计算更方便的进制转换方法。下面介绍的就是进制转换的第二套方案。

    再一次回到一个很基础的问题:在十进制中,为什么乘以10相当于在数的末尾加一个0?我们同样会联想到位运算:为什么二进制左移一位(末尾加一个0)相当于乘以2?事实上,这个结论普遍存在于所有进位制中:k进制数的末尾加个0,相当于该数乘以k。证明方法非常简单,乘以一个k就相当于进位制展开式的每个指数都加一,也就相当于所有数字左移一位。六进制235=2*6^2+3*6+5,乘以6的话式子将变为2*6^3+3*6^2+5*6,也即2350。利用这个性质,六进制235可以很快转为十进制:235相当于2后面添0,加上3,再添一个0,再加上5,写为算式即(2*6+3)*6+5=95。把(2*6+3)*6+5展开来,得到的式子和前面的那种计算方法(2*6^2+3*6+5)一模一样,但这里的计算方式更简便一些。如果写成程序,六进制字符串t转为十进制数a只需要一句话就可以完成:
for i:=1 to length(t) do a:=a*6+ord(t[i])-48;
    使用这种方法将十进制变回六进制是一个彻头彻尾的逆向操作:当前数不断除以6并把余数作为新的最高位。比如,95除以6等于15余5,余数5就是个位,15除以6的余数3作为“十位”,最终的商2是“百位”。这叫做短除法,是最常见的方法,网上随处可见。

    下面说一下进位制中的小数。前面的东西如果理解了,小数进制的转换将顺理成章地进行下去。六进制中的0.1相当于十进制的1/6,因为六进制中的0.1、0.2、0.3、0.4、0.5五个数把区间0到1均分为了6分。同样地,(0.05)6=(5/36)10。你会发现,一个“十分位”代表1/6,一个“百分位”代表1/6^2,之前的很多结论仍然成立。六进制小数12.345就等于1*6^1+2*6^0+3*6^(-1)+4*6^(-2)+5*6^(-3),通过负指数把进制转换的整数部分和小数部分联系在了一起。(12.345)6转为十进制后居然变成了无限小数,其实这并不奇怪,这只是一个约数的问题:同样是三分之一,在我的六进制下正好分干净(0.2),但在你十进制下就总也分不完,总要剩一点留给下一位(0.333333…)。这里有一些小数进制转换的实例。可以看到,一个进制下的有限小数很可能是另一个进制下的无限循环小数。另一个有趣的例子在这里
    既然前面所说的第一套方法中六进制转十进制对于小数仍然成立,那么第一套方法的十进制转六进制也可以直接在小数上使用。如果你嫌无限小数很别扭,用分数进行操作是一种不错的选择。具体操作方法和前文叙述一模一样。针对纯小数的进制转换,我们把前文的描述换种方法再说一遍:不断寻找最小的正整数x使得1/6^x不超过当前数,当前数减去1/6^x并在小数点后第x位上加一。我就不再举例子了,下面主要讨论第二套方法在小数上的应用。
    我们曾说过,在k进制末尾加0相当于该数乘以k。可惜这对小数没有用,小数后你加它八百个“0”这个数仍然不变。其实,“末尾加0”只是这种性质反映在整数上的一种现象而已,我们还需要看到更本质的东西(还记得高二哲学么)。考虑到小数的乘k和除k,不难想到这种性质的实质是小数点的移动,整数的末尾加0其实是小数点向右移动一位的结果。显然小数也有类似的结论:将k进制小数的小数点左移一位,相当于该数除以k。比如,十进制中3.14除以10就变成了0.314。结论的证明和原来完全相同:除以k后展开式中的指数全部减一,相当于所有数右移一位。有了这个结论,我们的方法就出来了。来看六进制12.345如何转换为十进制。由于这种方法对整数和小数的处理方法有一些不同,转进制时我们通常对整数部分和小数部分分别进行操作。先把12转成十进制的8,然后单独考虑小数部分。0.345可以看作是数字“5”的小数点左移,加上4后小数点再次左移,再加上3并最后一次左移小数点;写成算式即((5/6+4)/6+3)/6。展开这个式子,实质与前面的方法仍然一样。小数部分十进制转六进制依然是彻头彻尾的逆向操作:当前数不断乘以6并取出整数部分写下来。
    这里有一个实例供大家参考,这个例子中的进制转换保证不涉及无限循环小数。1/4在十进制和六进制下的表示肯定都是有限小数,因为4的唯一一个因子2同样也是10和6的因子。先看0.25怎么变成六进制:0.25*6=1.5,取出1,留下0.5;0.5*6=3,没有小数部分了,因此(0.25)10就等于(0.13)6。现在我再把它变回去:(3/6+1)/6=(0.5+1)/6=1.5/6=0.25。这不是彻头彻尾的逆操作吗?

    每年NOIp前总有人问负进位制。事实上,如果你搞清了上面的问题,负进位制将非常好理解。负进位制有一个非常奇特的功能:它可以表示出负数但不需要用负号。一个负进制数可能是负数,也可能是正数。比如,负六进制下的12等于十进制下的-4,而负六进制下的123等于1*(-6)^2+2*(-6)^1+3,即十进制下的27。是正是负取决于位数的奇偶:若该数有偶数位,则该数为负数;若有奇数位,则该数为正数。原因很简单,小数点每右移一位,相当于这个数乘以-6;从一位数开始,乘奇数次后该数的位数变成偶数且值为负,乘偶数次该数仍有奇数位且值仍为正。由于末尾添0的性质(小数点移位的性质)仍然成立,负六进制与十进制的转换依然是上面的方法:(123)-6=(1*(-6)+2)*(-6)+3=(27)10。十进制转负六进制?还是那句话:彻头彻尾的逆操作。找到最小的非负整数x使得当前数减x能被6整除,这个x将作为新的最高位写到结果中,然后当前数减去x再除以-6。在这里我不说“余数”这个词,因为当除数为负数时对余数的定义很模糊。不再举例子了,例子都举烦了,自己把(123)-6=…=(27)10那一行倒过去看就是例子了。
    当然,还有更神奇的:-1+i进制可以表示出复数来,因为-1+i的幂有时含有虚数有时不含虚数。运算和转换依然和上面这些东西一样,我也就不多说了。

    进位制的问题结束了。我们这里是以六进制为例进行的说明,但是不要忘

判定被7整除的简易方法

    判断一个数的整除性对于某些除数来说是一件非常容易的事,比如2、3、4、5、6、8、9、10、11、12、15……
    但是对于7来说一直是一个难题,而判定是否被7整除在数字运算中又比较常用。我刚看到一种判定能否被7整除的方法,在这里写一下。
    比如,我们要看86415能否被7整除。首先我们把它从个位开始往左边走两个数字一组划分开来,这样,86415就划分成8 64 15;然后,从左开始“一加一减找余数”:

    6       6
    8  64  15
        1

    看上面,6+8正好被7整除,64-1被7整除,15+6被7整除。
    然后把找到的余数从右往左读出来,616,现在,如果616能被7整除,那么86415就能被7整除。
    如果你还看不出616能被7整除的话,可以继续这样做下去:

    1
    6  16
        2

    现在很明显了吧,21能被7整除。因此,86415就能被7整除。
    下面我再举一个例子:6913580247。

     1       5       2
    69  13  58  02  47
         6       2

    22561

    5       2
    2  25  61
        4

    245能被7整除,因此6913580247能被7整除。

    更加奇妙的是,这个方法对于判定被11整除、被13整除同样有效。
    至于为什么,我没仔细研究,估计和那个有关。看到7、11、13这三个数,你难道还想不起那个吗?
    最后补充:比较流行的割位法对于三位数、四位数比较简便;但位数一多,显然这种方法比较简便。6913580247我们用这种方法只做了两次,用割位法要做9次!

    做人要厚道,转帖请注明出处。