精华内容
下载资源
问答
  • 一道google面试题--自然数e出现的连续的第一个10个数字组成的质数
    万次阅读
    2014-09-09 14:12:26


    博客内容移到 http://www.linuxyu.com/

    此CSDN博客将不再更新,欢迎大家访问新的网站~~


    Google早几年在美国很多地铁的出站口都有大幅招聘广告,它的第一题如下了:{first 10-digit prime found in consecutive digits e}.com,也就是自然数e中出现的连续的第一个10个数字组成的质数。据说当时只要正确解答了这道题,在浏览器的地址栏中输入这个答案,就可以进入下一轮的测试,整个测试过程如同一个数学迷宫,直到你成为google的一员。

    对这题比较感兴趣,就暂时开题,这两天动笔完成这篇博客。

    难点分析

    初步分析,主要两个难点:

    (1)e的小数点后n位有效数字,提供素数判断的数据源;

    (2)素数的高效判断,尤其时针对10位数的指数判断。

    自然数e计算

    计算e可使用泰勒公式推出来的

    e^x1 + x + x^2/2! + x^3/3! +……+ x^n/n!  1
    

    当x=1时,

    e1 + 1 + 1/2! + 1/3! +……+ 1/n!   2
    

    取n=10,即可算出近似值e≈2.7182818。 当n取较大值时,可获得高精度的e。

    版本1

    最初步的想法,直接利用泰勒公式计算,printf出来。

    //compute e 
    //program 1
    
    #include <stdio.h>
    #define N 20
    
    int main()
    {
        double e = 1.0, t = 1.0;
        int i = 0;
    
        for(i = 1; i <= N; i++){
            t = t / i;
            e = e + t;
        }
    
        printf ("e=%.20lf\n",e );
        return 0;
    }
    

    版本2

    对(2)进行叠乘合并起来,改写成如下(3):

    e=2+1/2(1+1/3(1+1/4(1+1/(n-1)(1+1/n))))   3
    

    从最里面的括号往外算,共做n次除法和加法得一段结果,运算效率也是O(N*M),但是由于收敛速度快些。

    //compute e 
    //program 2
    
    #include <stdio.h>
    #define N 20
    
    int main()
    {
        double e = 0.0;
        int i = 0;
    
        for(i = N; i > 0; i--){
            e = (e + 1.0) / i;
        }
    
        printf ("e=%.20lf\n",e );
        return 0;
    }
    

    版本3

    版本1和2只能达到小数点后15位精度,假设我们需要的数据源是e达到e后面1000位数,那么就必须要考虑到高精度的问题。自定义数据结构时必须的,但(3)中主要计算加法、减法和乘法不会丢失精度,而除法会丢失精度,因为它始终存在余数,余数是丢失精度的原因,所以要保存精度就是保存余数,在这里的除法将产生两个整数,一个是商,一个是余数,形如:

    a / b = (d,r)
    

    如果以10为基,那高精度就是对这样的除法继续做下去,将r*10恢复精度,再除,直到满足精度为止。

    //compute e 
    //program 3
    
    #include <stdio.h>
    
    //umber of significant digit=(DN-1)*4
    #define DN 2504
    
    int euler[DN], data[DN];
    int step,n;
    
    void precise_division()
    {
        int i=0;
        long y=0,r=0;
    
        for(i=step;i<DN;i++)
        {
            y = 10000*r + data[i];
            data[i] = y/n;
            euler[i] += data[i];
            r = y%n;
        }
    }
    
    void revise()
    {
        int c=0,i=0;
    
        for(i=DN-1;i>=0;i--)
        {
            euler[i] += c;
            if(euler[i]>9999)
            {
                c = euler[i]/10000;
                euler[i] = euler[i]%10000;
            }
            else
                c = 0;
        }
    }
    
    void euler_print()
    {
        int i=0;
    
        printf("\n\nE=%d.\n",euler[0]);
        for(i=1;i<DN;i++)
        {
            printf("%.4d ",euler[i]);
            if((i%250)==0)
                printf("\n\tnow it has %d bit\n",i*4);
        }
        printf("\n\neulur number is ok\n");
    }
    
    void main()
    {
        int i=0;
        step=0;
        n=1;
    
        for(i=0;i<DN;i++)
        {
            euler[i]=0;
            data[i]=0;
        }
        euler[0]=1;
        data[0]=1;
    
        while(1)
        {
            i=step;
            while(data[i]==0)
            {
                i++;
                if(i>=DN)
                goto _EXT;
            }
    
            step=i;
            precise_division();
            revise();
            n++;
        }
    
    _EXT:
        euler_print();
    }
    

    版本4

    参考文献2,实现本版本,唯一没有想通的部分在于GetLength函数实现的数学原理,这个后续再补充。

    //compute e 
    //program 4
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <assert.h>
    
    //umber of significant digit
    #define N 10000
    
    typedef unsigned int UINT;
    
    int GetLength(int m)
    {
        int n=1;
        double x=2.0/2.72;
        while(m)
        {
            while(x<1.0)
            {
                n++;
                x*=(double)(n+1);
            }
            while(x>=1.0 && m)
            {
                x/=10.0;
                --m;
            }
        }
        while(x<1.0)
        {
            n++;
            x*=(double)(n+1);
        }
        return n;
    }
    
    void main()
    {
        const UINT base=1000000;
        int i,j,k,m;
        UINT *r=NULL;
        UINT *euler=NULL;
        UINT y=0;
    
        m=GetLength(N);
    
        r=(UINT *)calloc(m+1,sizeof(UINT));
        euler=(UINT *)calloc(N,sizeof(UINT));
    
        assert(r!=NULL);
        assert(euler!=NULL);
    
        for(i=0;i<=m;++i)
        {
            r[i]=1;
            euler[i]=0;
        }
    
        j=1;
        euler[0] = 2;
    
        for(k=N;k>0;k-=6)
        {
            y=0;
            for(i=m;i>=2;--i)
            {
                y = y + r[i]*base;
                r[i] = y%i;
                y /= i;
            }
    
            if(k<6)
            {
                euler[j++] = y%base;
            }
            else
            {
                if(y<base)
                    euler[j++] = y;
                else
                {
                    if(r)
                        free(r);
                    return;
                }
            }
        }
        if(r)
            free(r);
    
        printf("\n\nE=%d.\n",euler[0]);
        for(i=1;i<j;i++)
        {
            printf("%.6d ",euler[i]);
            if((i%100)==0)
                printf("\n\tnow it has %d bit\n",i*6);
        }
        printf("\n\neulur number is ok\n");
    
        if(euler)
            free(euler);
    
        return;
    }

    素数判断

    版本1

    素数,即除1以外,只能被1和其本身整除的数。因此根据定义,最初的判断素数的方法为版本1。

    //judge prime 
    //program 1
    bool prime_1(int n)
    {
        int i;
        for(i=2;i<n;i++)
        {
            if(n%i==0)
            {
                return false;
            }
        }
        return true;
    }
    

    版本2

    由于一个数的因式分解可表示为x = a * b, a<=b,因此在判断整数n是不是素数时并不需要整数从2 ~ n,只需要整除2 ~ sqrt(n) 即可。

    //judge prime 
    //program 2
    bool prime_2(int n)
    {
        int i,n2;
    
        n2=sqrt(n);
        for(i=2;i<=n2;i++)
        {
            if(n%i==0)
            {
                return false;
            }
        }
        return true;
    }
    

    但是需要注意的是,int类型只能表示2^-31 ~ 2^31 范围,并不能完整表示10位大小的整数n,因此需要使用能够long long int类型。其次,求平方根的函数原型为double sqrt(double),进行sqrt(n)计算时并不确定能保证其精度。最后,对e小数点后,每个10位数字大小的整数n进行判断,需要对每个2 ~ sqrt(n)的整数进行判断,单个判断耗时间太久,所有判断重复运算太多。因此次方法必然不行。

    版本3

    任何一个合数都可以表现为适当个素数的乘积的形式,所以我们只用素数去除要判断的数即可,比如要判断100以内的素数,只用10以内的2,3,5,7就够了。那么首先就要构建一个素数表,以空间换时间。

    可以使用一种被称为“埃拉托色尼筛”的算法。该算法一开始初始化一个2~n的连续整数序列。从2开始,2的倍数肯定不是素数,去除,剩下的数中下一个是3。再把3的倍数去除,再下一个是5(4已经去除了),以此类推,最后剩下的数必然是素数,因为它没有被筛,不是任何一个数的倍数(除了1和自己)。这样只需要很多次加法就可以了。

    //create prime less then n 
    //program 3
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <math.h>  //Linux, gcc -lm
    #include <stdbool.h>
    
    
    #define Long64 long long
    #define MAX_N 8000000
    
    Long64 prime_table[MAX_N/2]={0};
    
    void create_prime_table(Long64 n)
    {
        Long64 i,j,n2;
        bool prime[MAX_N+1]={0};
    
        n2=sqrt(n);
    
        i=2;
        for(i=2;i<=n2;i++)
        {
            if(prime[i]==0)
            {
                for(j=i+i;j<=n;j+=i)
                {
                    prime[j]=1;
                }
            }
        }
    
        j=0;
        for(i=2;i<=n;i++)
        {
            if(prime[i]==0)
            {
                prime_table[j]=i;
                j++;
                printf("%lld : %lld \n",j,i);
            }
        }
    }
    
    void main()
    {
        create_prime_table(MAX_N);
    }
    

    这算是基本的筛法了,但它还有很多优化的空间。首先内存中存的表不只是素数,还有合数的空间,合数比素数多得多。 其次对于合数可能会被筛选多次,比如6,在2的时候筛掉一次。在3的时候又筛掉一次。

    版本4

    采用线性筛选方式,即对一个合数,只进行一次筛选,极大程度的减少重复计算。 同时,采用堆结构存放素数表。

    //create prime less then n
    //program 4
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <math.h>  //Linux, gcc -lm
    #include <stdbool.h>
    
    
    #define Long64 long long
    #define MAX_N 1000000000
    
    void create_prime_table2(Long64 n)
    {
        Long64 i,j,k;
        bool *prime;
        Long64 *prime_table;
    
        prime=(bool *)calloc(MAX_N+1,sizeof(bool));
        prime_table=(Long64 *)calloc(MAX_N/100,sizeof(Long64));
    
        i=2;
        k=0;
        for(i=2;i<=n;i++)
        {
            if(prime[i]==0)
            {
                prime_table[k++]=i;
                //printf("%lld : %lld \n",k,i);
            }
            for(j=0;j<k && prime_table[j]*i <= n;j++)
            {
                prime[prime_table[j]*i]=1;
                if(i%prime_table[j]==0)
                    break;
            }
        }
        printf("k = %lld\n",k);
    
        if(prime_table)
            free(prime_table);
        if(prime)
            free(prime);
    }
    
    void main()
    {
        create_prime_table2(MAX_N);
    }
    

    这样做之后,程序运行速度快多了,但是只能计算到1x10^9....再大时,运行就提示段错误了。还要再在其他地方想办法。。。 暂时先记录到这里。

    自然数e

    上上一篇博客中,虽然实现了计算n为自然数e的程序,但是GetLength 函数实现的数学原理一直没有想清楚。其实是这样的。 计算自然数e时,我们使用的时泰勒公式

    e^x = 1 + x + x^2/2! +... + x^n/n!...
    

    当考虑到泰勒公式的余项时,余项可以表示为

    R(x) = (e^esp * x^(n+1))/(n+1)!
    

    其中esp在0~x之间。再取x=1,得到

    R(1) = e^esp/(n+1)! <= e/(n+1)! < 10^(-M)
    

    10^(-M)就是我们要达到的误差限,也就是小数点后的后M位。 因此,若要计算到10000位的精度,只需计算(n+1)!/e > 10^10000即可。 程序中GetLength的实现原理就是这样。

    素数判断

    上一篇博客中,只计算到1x10^9内的素数,并没有达到要求,主要时卡在了数据结构上,再大的话在我2G内存32位系统上就已经提示段错误了。现在回过头了想想,其实还可以在算法上再改进。 我们知道a*b = c,一个M位的数乘上另外一个M位的数,得到结果位数范围在M+1~2M的范围内。我们只要求出5位数以内的素数,就可以用来判断10位数的整数是否为素数了。 那么素数判断的程序就可以修改成这样,也就是版本2和版本4的结合。

    版本5

    #include <stdio.h>
    #include <stdlib.h>
    #include <math.h>  //Linux, gcc -lm
    #include <stdbool.h>
    
    #define Long64 long long
    #define MAX_N 100000
    
    //create prime less then n 
    //program 5
    
    Long64 create_prime_table3(bool *prime, Long64 *prime_table, Long64 n)
    {
        Long64 i,j,k;
    
        i=2;
        k=0;
        for(i=2;i<=n;i++)
        {
            if(prime[i]==0)
            {
                prime_table[k++]=i;
                //printf("%lld : %lld \n",k,i);
            }
            for(j=0;j<k && prime_table[j]*i <= n;j++)
            {
                prime[prime_table[j]*i]=1;
                if(i%prime_table[j]==0)
                    break;
            }
        }
    
        return k;
    }
    
    bool prime3(Long64 prime_table[], Long64 length, Long64 n)
    {
        Long64 i;
    
        for(i=0;i<length;i++)
        {
            if(n%(prime_table[i])==0)
            {
                return false;
            }
        }
        return true;
    }
    
    void main()
    {   
        bool *prime;
        Long64 *prime_table,i=0,j=0,length=0;
    
        prime=(bool *)calloc(MAX_N+1,sizeof(bool));
        prime_table=(Long64 *)calloc(MAX_N,sizeof(Long64));
    
        length = create_prime_table3(prime,prime_table,MAX_N);
    
        for(i=1000000000; i<10000000000; i++)
            if(prime3(prime_table,length,i)!=false)
                printf("%lld : %lld\n",j++,i);
    }
    

    收尾:判断e中首个连续10位素数

    有了以上问题解决后,这道题终于可以解决了。不仅可以找到第一个10位长度的素数,还可以找到前10个!前10个结果如下,格式为“第几个:在e中的起始位置:要找的素数”

    1:99:7427466391 
    2:123:7413596629 
    3:149:6059563073 
    4:171:3490763233 
    5:182:2988075319 
    6:201:1573834187 
    7:214:7021540891 
    8:218:5408914993 
    9:254:6480016847 
    10:295:9920695517

    完整实现代码如下

    #include <stdio.h>
    #include <stdlib.h>
    #include <math.h>  //Linux, gcc -lm
    #include <stdbool.h>
    #include <assert.h>
    
    #define MAX_E_BITE_N 1000
    #define PRIME_N 1000000
    
    #define Long64 long long
    
    typedef unsigned int UINT;
    
    
    int get_length(int nbite)
    {
        int m = 1;
        double x = 2.0/2.72;
        while(nbite)
        {
            while(x<1.0)
            {
                m++;
                x *= (double)(m+1);
            }
            while(x >= 1.0 && nbite)
            {
                x /= 10.0;
                --nbite;
            }
        }
        while(x < 1.0)
        {
            m++;
            x *= (double)(m+1);
        }
        return m;
    }
    
    
    int create_e(Long64 *euler, const UINT base, int m)
    {
        UINT *r = NULL;
        UINT y = 0;
        int i = 0,j = 0,k = 0;
    
        r=(UINT *)calloc(m+1,sizeof(UINT));
    
        assert(r != NULL);
        assert(euler != NULL);
    
        for(i = 0;i <= m; ++i)
        {
            r[i] = 1;
            euler[i] = 0;
        }
    
        j = 1;
        euler[0] = 2;
    
        for(k = MAX_E_BITE_N; k > 0; k -= 5)
        {
            y = 0;
            for(i = m; i >= 2; --i)
            {
                y = y + r[i]*base;
                r[i] = y%i;
                y /= i;
            }
    
            if(k < 5)
            {
                euler[j++] = y%base;
            }
            else
            {
                if(y < base)
                    euler[j++] = y;
                else
                {
                    if(r)
                        free(r);
                    return 0;
                }
            }
        }
        if(r)
            free(r);
    
        printf("\n\nE=%lld.\n",euler[0]);
        for(i = 1; i < j; i++)
        {
            printf("%.5lld ",euler[i]);
        }
        printf("\n\neulur number is ok j=%d\n",j);
    
        return j;
    }
    
    Long64 create_prime_table(bool *prime, Long64 *prime_table, Long64 n)
    {
        Long64 i,j,k;
    
        i = 2;
        k = 0;
        for(i = 2;i <= n;i++)
        {
            if(prime[i] == 0)
            {
                prime_table[k++] = i;
    //            printf("%lld : %lld \n",k,i);
            }
            for(j = 0; j < k && (prime_table[j]*i) <= n; j++)
            {
                prime[prime_table[j] * i] = 1;
                if(i%prime_table[j] == 0)
                    break;
            }
        }
    
        return k;
    }
    
    bool is_prime(Long64 prime_table[], Long64 length, Long64 n)
    {
        Long64 i;
    
        for(i = 0; i < length; i++)
        {
            if(n%(prime_table[i]) == 0)
            {
                return false;
            }
        }
        return true;
    }
    
    
    void main()
    {
        const UINT base = 100000;
        int m = 0,elength = 0,j = 0,find_number = 10;
    
        Long64 *euler = NULL;
        bool *prime = NULL;
        Long64 *prime_table = NULL;
    
        Long64 i = 0,length = 0,data = 0;
    
        Long64 ten[10] = {1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000};
    
        m = get_length(MAX_E_BITE_N);
    
        euler = (Long64 *)calloc(MAX_E_BITE_N,sizeof(Long64));
    
        elength = create_e(euler,base,m);
    
    
        prime = (bool *)calloc(PRIME_N+1,sizeof(bool));
        prime_table = (Long64 *)calloc(PRIME_N,sizeof(Long64));
    
        length = create_prime_table(prime,prime_table,PRIME_N);
    
        if(prime)
            free(prime);
    
        m = 0;
        for(i = 1; (i+2) < elength; i++)
        {
            for(j = 0; j < 5; j++)
            {
                data = euler[i]%ten[5-j]*ten[5+j] + euler[i+1] * ten[j] + euler[i+2]/ten[5-j];
    
    //          printf("data : %lld \n",data);
    
                if(is_prime(prime_table,length,data) == true)
                {
                    printf("find it! %d:%lld:%lld\n",++m,5*(i-1)+j+1,data);
    
                    if(m == find_number)
                        break;    
                }               
            }
    
            if(m == find_number)
                break;    
        }
    
    
        if(euler)
            free(euler);
        if(prime_table)
            free(prime_table);
    
        return;
    }
    

    更多相关内容
  • 数字,表现为科学计数法,这样算单位的时候就出现了问题 这边有两个点: - 如果你只是为了计算转换单位,那你最好用第一种 - 如果是只是想输出这么长的数字,不想表现为科学计数法,只能用第二种

    自定义转换数值单位、解决js数字过大表现为科学计数法的问题

    下面用的TS,可以自己转为JS,我还是写一遍吧,最下面是JS,会了给我评论点赞,听到没有,臭弟弟


    问题:
    数字过大,表现为科学计数法,这样算单位的时候就出现了问题

    这边有两个点:

    • 如果你只是为了计算转换单位,那你最好用第一种
    • 如果是只是想输出这么长的数字,不想表现为科学计数法,只能用第二种

    解决:
    1、最优的是自己重写个Number类,来存数字的常数和指数,然后在里面定义自己的方法来操作该值与其他值运算,像什么比较,深浅克隆什么的

    2、不想用最优的方法,看代码(后面转单位你就递归,然后用String.fromCharCode,我就不写了,如果需要请评论我放上),这种会有误差,但因为数太大,这一点误差也不算什么

    //TS代码:
    //这是把数字转为字符串
    private getFNum(numStr:string) {	
    		//这边看下你那边有没有指定格式,可能没有+,所以后面改成('E')就好了
            let temp: string[] = numStr.toUpperCase().split('E+')
            if (!temp[1]) {
                return numStr;
            }
            //这边我之前有试过乘法,但是返回之后,它又给转回科学计数法了
            let tempNumStr: string = "1";	
            for (let i = 0; i < parseInt(temp[1]); i++) {
                tempNumStr += "0";
            }
            return tempNumStr;
        }
    
    //调用上面的方法
    private getXXXXX() {	
    		let num: number = 1000000000000000000000;	
    		//这边最好判断一下是否溢出
    		 if (!isFinite(num)) {
                    return "MAX";
             }
    		console.log(num);	//这边输出num他就会转为1e+21,蛋疼
          	 let nStr: string = this.getFNum("" + num);	//先把num转为字符串,这种效率比num.toString()要高
          	 console.log(nStr);	//这边会输出字符串1000000000000000000000,而不是1e+21
        }
    
    //JS代码就是简单转下嘛,不能运行自己再检查一下:
    //这是把数字转为字符串
    function getFNum(numStr) {	
    		//这边看下你那边有没有指定格式,可能没有+,所以后面改成('E')就好了
            let temp = numStr.toUpperCase().split('E+')
            if (!temp[1]) {
                return numStr;
            }
            //这边我之前有试过乘法,但是返回之后,它又给转回科学计数法了
            let tempNumStr = "1";	
            for (let i = 0; i < parseInt(temp[1]); i++) {
                tempNumStr += "0";
            }
            return tempNumStr;
        }
    
    //调用上面的方法
    function getXXXXX() {	
    		let num = 1000000000000000000000;	
    		//这边最好判断一下是否溢出
    		 if (!isFinite(num)) {
                    return "MAX";
             }
    		console.log(num);	//这边输出num他就会转为1e+21,然后你就没办法算单位了
          	 let nStr = this.getFNum("" + num);	//先把num转为字符串,这种效率比num.toString()要高
          	 console.log(nStr);	//这边会输出字符串1000000000000000000000,而不是1e+21
        }
    

    维尼聚合工具


    展开全文
  • 全渠道零售中台与数字化转型(1)-中台的前世今身

    千次阅读 多人点赞 2019-06-25 15:37:59
    本系列博客的目标是计划使用近半年时间创造: ... 全渠道零售中台与数字化转型(2)-中台给企业业务带来什么实际的价值 全渠道零售中台与数字化转型(3)-中台给企业技术带来什么实际的价值? 全渠...

    本系列博客的目标是计划使用近半年时间创造:

    • 国内唯一一部从业务场景到技术设计,从企业战略考虑到技术细节落地的大全;
    • 全文贯穿了企业架构、SOA、微服务,纵横业务与技术之间説透“全渠道”中台;
    • 全渠道零售中台与数字化转型(1)-中台的前世今身
    • 全渠道零售中台与数字化转型(2)-中台给企业业务带来什么实际的价值
    • 全渠道零售中台与数字化转型(3)-中台给企业技术带来什么实际的价值?
    • 全渠道零售中台与数字化转型(4)-作为甲方如何选择中台-产品还是开发?数据中台还是业务中台的多重考虑
    • 全渠道零售中台与数字化转型(5)-中台的架构设计
    • 全渠道零售中台与数字化转型(6)-基于微服务的组件设计
    • 全渠道零售中台与数字化转型(7)-中台核心框架代码实现
    • 全渠道零售中台与数字化转型(8)-中台的延伸

    楔子

    零售战鼓隆,各家齐斗法

    云溪论剑后,江湖出宝典

    古有葵花经,现有“大中台”

    没有“两个亿”,别想做中台

    技术道业务道,自求条正道

    各家纷説自己好

    谁曾想,旧日零售江湖间现己变成了血海滔滔

    你也説中台,我也説中台,到底什么是中台?

    现如今随着“新零售”这三个字一再被提及,整个零售界都在提一个“神密的东西”,那就是中台。甚至中台被上升到了“推进企业数字化变形”的乃至直接促成企业数字化转型能否成功的地位了。

    那么中台它到底是个什么样的东西呢?在人们眼中中台似乎犹如月球的背面一般神密。

    在人们眼中的中台无外乎于上述类似的组件图,类似的图一再被各大零售商或者是不少知名软件商一再的提及。

    它似乎有着“华丽的外表,沉渔落雁的面容,婀娜多姿的身段”。。。but。。。

    它只是利用了2009年TOGAF设计规范从顶向下的设计方法论把业务模块进行了LEVEL3级别的一个分解的功能图而己,它只要业务架构师手绘一些功能甚至公司的一个BA用Excel做一个功能列表然后让稍微资深点的UI做一下布局在一天内即可以得出的一个picture而己

    多少甲方为了这么一张外面报价800-1,000块钱制作费的首图化了近千万、甚至上亿的代价了?甚至笔者在几个展会听到不少的开发商豪言“你要做中台?你公司干什么的?每年至少2个亿销售额有吧。。。。。。没有?那您也别做中台了”。

     

    中台的诞生

    中台这个东西我明确告诉大家:它一点不神密,它也不是近3年的什么高科技的产物,早在2012年这个东西就已经有了。同时我本人在13年也已经用“中台”的理念制作了一套类似的东西我们在当时把它称为“SMART PLATFORM”,这套东西的代码我会在后面的章节涉及到设计和实现的时候公开它的核心源码、数据库表结构与设计思路,这个是属于我个人的也是没有问题的,各位也可以放心使用。

    这种一体化全渠道平台的出现最早是在银行、金融界,在那个时候银行、金融、保险界的一些大公司面对着繁杂的legacy systems需要开始迈入“手机端、无线办公”端的时代,于是当时的人们想到把这么多的legacy systems是不是可以做成一个“大后台”。

    在这个大后台中,把所有的业务功能进行整合,所有的数据使用一个或者是一套数据库以此来打通各个业务、解决掉数据孤岛问题、提高性能、降低不同系统间交互、接口转换、以及支持不同系统间数据交互的事务一致性时带来的昂贵的开发、网络延时与开销以及不必要的开发工作量。

    但是,业界在根据这个指导思想进行开发时发觉问题来了!

    如果仅仅是把所有的东西打包在一个“大后台”并不能真正解决IT的痛点,因为必竟它是一个IT系统。IT系统要考虑的东西除了业务功能,更重要和更有价值的地方在于:

    • 性能
    • 安全
    • 可以快速响应业务的创新或者説甚至可以“加速业务创新”并以此来为业务赋能

    以上説的神乎几神,我们中国人现在讲究的是“效率、实干”,要“落地”,要“接地气”,因此下面我们就用接地气的话来把上面这一段中台出现的背景、历史上经历的痛点来着重的讲一下吧。

     

    直接使用零售场景来描述中台的诞生与过程

    一个顾客在传统的零售场所的消费体验可以用下图描述出主要的“零售体验核心环节”

    以上这个图,它出现在20-25年前的零售大卖场内,支持它的系统也是20甚至25年前的“作品”。这边需要着重説一句的是:截止作者写此稿时,现有大部分的大型商超竟然用的还是20-25年前的IT系统。这也正是近来各大厂商、业界宣了沸沸扬扬的“新零售”,“数字化转型”的原动力与由来(改造需要money, money,没有money没有利益何来原动力)。

    因为。。。这么土的东西,直到现在终于有机会推翻它了。

    言归正传,解读上图!

    当一个顾客来到了大超市内,我们知道传统的大超市还会分不同的品牌,把化妆品还放到不同的位置甚至独立的橱柜,这就导致了客户要买什么东西,他会记得去问各个“导购”或者去服务台询问。

    “哎呀,请问会员怎么办?”,导购人员会告诉他!

    “哎呀,请问会员积分哪里积怎么积?”,导购人员会告诉他!

    “哎呀,请问印花是怎么得到?”,导购人员还是会告诉他!

    客户问错了人,比如説他去问“收银员”这把刀不是説买一把送一块肥皂吗?收银员通过话务机于是叫来了导购,但是导购也不知道,就又通过商场广播叫来了“促销人员”,促销人员当然知道买什么可以送什么或者打几折这些事喽。

    于是,靠着不同的、严格的岗位、职责的区分,我们的商场尚且还可以运作。并且要知道那是20年前,国人的消费能力有小部分已经开始起色而市场上商品的供应还不如现在的“百花齐放”。因此一些国外的大型商超明显在当时是属于“朝南坐”、“躺着挣钱的”

    因此,大型商超在当时对于IT系统的定位是次要中的次要的(很悲哀),而货物、商品甚至不乏国外进口商超内的商品在那时才是真正深深吸引国人的主要因素。

    于是过了大约10年,这也是零售业黄金的10年,随着国人消费能力的越来越高,随着IPHONE4、微信、淘宝的兴起,零商开始迈向了电商时代。

    于是这些大型商超、大型零售超市想当然的认为其实电商就是把原来站在各个服务前的一个个人肉导购啦、促销啦、专柜啦的这些个人取代成一个个的手机应用APP,于是,在当时的大型零售商眼里的电商也是类似下面这样的一个图

    先有了想法

    通过“想法”有了下面的系统“架构”

    零售电商1.0模式

    转型1.0模式

    不要笑,当时一堆一堆的零售(在当时还算是比较有钱)设计出来的系统就是这样的。

    “喏,要数字化,我把人变成了一个个的APP了,这不就是数字化!”

    所以大家直到现在也能看到类似的案例:一些传统的快销、零售商用微信、用APP、用微信小程序哪怕只是做出了一个会员登记系统也会把它当成“公司内部巨大的创新”,也是基于这样的想法。因为IT不重要吗,哈哈!

    可是,它依旧没有从根本上解决客户的问题。为什么呢?中国客户的电商使用习惯是什么?
     

    中国人的电商使用习惯

    中国,人多的很、市场大的很,我们説我们是世界第2电商大国,这个世界上没人敢説它是世界第1。

    那么多APP、那么多小程序、那么多微信公众号,而你只有一个企业实体却要做成“为了一个服务就放一个APP”的模式,比如説:我为了来一次“某干发”大超市、“某得福”网上超市购物你要我去下不止一个APP才能完成“会员、认证、购物、积分”本就应该集中在一个APP中的“功能”,甚至客户做一些兑换还要让我打开一个不知道什么地方的网页去登录一个网址才能完成兑换?你是不是觉得我们客户的时间太“无用了”?

    张小龙説过:哪个APP可以每天占用客户30分钟,这个APP就是巨大的成功

    在百花齐放、百家争鸣的数字化时代况且在当时淘宝连续使用4次双11打折活动打造了中国客户的使用电商APP的习惯后,你这边突然来了一个,有几个功能就要有几个网址、几个APP或者就算你是APP混合微信好来,你觉得中国的顾客会买你的帐?

    下载APP的时间是很宝贵的!

    在当时,APP与微信间还没做到数据共享,因为背后的legacy systems还是孤立的那么客户一些登记、购买行为、数据、历史消费记录都要我们的中国客户重复的操作2遍、操作3遍。。。。。。

    对不起,中国顾客对于这种重复操作2次以上而做的事是在完成同一件事的APP的使用不会超过1次,1次就删掉你!甚至拉黑你!并且还会去朋友圈把你数落一顿。这就是中国人的电商使用习惯。

    中国人喜欢 “一键式”,喜欢 “快速定位”,喜欢“3步操作内就完成一件事”。

    所以,大型零售商们错失了第一次电商黄金发展阶段即培养顾客消费习惯的这个阶段,那么这些大型零售商也意识到了问题:

    哦,这个问题出在后面的系统本来在打造的时候就是CS架构、本来就是一个个孤立的而导致的。

    在此时,大型零售商还是没有意识到自己的危机因为这时阿里淘宝还没有完全起势,大家都认为阿里脑子有水了,连续4次的双11。再説了,他们卖的东西不如我们的有“品牌”,对吧?

    那么现在大量的客户反馈説,你们的几个APP要变成一个APP才好用,所以大家就不约而同的想到了把后面的系统集成在一起,使得每一个系统不是孤立的对外服务了。

    同时,业内不乏I.O.E体系等造势宣称SOA,于是乎在“SOA可能是未来20年仅有的发财机会”这句口号的带领下,零售系统的改造进入了“集成1.5时代”。

     

    零售电商1.5模式-集成模式

    2007~2012年是“集成模式”概念被抛出率最高的年代,它有一个名字叫“SOA”,SOA就是那个时代的“全渠道中台”。

    以I.O.E为首尤其是IBM对SOA进行了系统化、理论化甚至到了产品化的密集布局与宣传,人们提起SOA一定会想到IBM或者是Oracle。

    嘿嘿!

    笔者突然想起2000年初时,有关于互联网的一个笑话:説人人都説这座山上有金子,于是所有人上山挖金子。结果挖金子的人没有发财,倒是山下那个“卖铲子的人”发了财

    系统集成就由如上图一样,复杂无比。

    一堆的Legacy,几十个Legacy,每个接口不同,要把它们集成光开发人员的付出就需要花费大量的时间与精力,很多企业为了不必要的自己去养开发团队为了图快于是使用了各种商业级别的、恶狠狠的集成工具(SOA开发环境)乃至付出了小型机的代价来集成一堆的Legacy。

    这些恶狠狠的工具的使用、错综复杂的系统间如蜘蛛网的连线的一切目的就是为了一个“one app can integrate all function”,一个APP所有功能。

    看似是这么一回事,可是,这次一些“巨头甲方”们却付出了惨重的代价!!!

    上面説了,集成这些Legacy本身是一件很复杂的事,因此需要使用不少在当时被称为“RAD-快速应用开发工具”来做这样的集成,这样的工具基本出自I.O.E体系,动辄几千万RMB一套,甚至还要用上百万的小型机去部署。

    钱花了,如果东西出来了倒也成了,关键是SOA还有一整套完整的“系统集成”体系化的概念。所以经历过SOA集成的都领教过所谓的“流程”。

    大家知道,所谓流程是一套best practice,它是用来帮助我们更好的更有条理的在一个如此宠大繁杂的、多达十几个几十个legacy系统集成中遵循的一条最佳途径,它并不是条条框框的死板的理论。

    至于流程是否我们真的学到了、消化了同时是否运用得当这是后话不会在本章展开,后面的章节我们会来讨论,我们就先説用SOA没有用好拿它集成完了的东西带来了什么样的噩梦吧。

    好,下面是一个运行SOA系统集成理念集成好的东西,当年国内很多大公司就是这么干的!

    这是后台用SOA理念集成好的东西,但是它在面临中国市场时又被打得体无完肤了。为什么呢?

    因为在I.O.E准备恶狠狠的、用昂贵的SOA的RAD套件进行密集推销时,我们国内的电商已经开始面临百万、千万甚至亿万级的流量了。什么东西到了中国一来,都会使用到各种高技术,国外对这点非常想不通!为什么呢?其实事情很简单,因为中国的人多,人多那么数字化流量也一定大么!中国人已经在开始思考解决大并发大流量的时候而国外还在考虑如何把“昂贵的铲子”去卖给大型零售商。于是,差距开始造成了!

    一个欧州国家的人口甚至整个欧州人口加在一起都不一定有我们的一个门户级网站的流量的人口多,势必这些国外的“高大上”会遇到水土不服,于是。。。买完了铲子,更可怕的噩梦发生了。

    频繁的CR带来的系统开发维扩成本急剧上升

    大家都知道,一个系统、一段代码它一定会经历“分析、设计、编码、测试、部署”几个阶段。如果这段代码有任何修改,它要再进行bug fix后再需要走一遍“分析、设计、编码、测试、部署”这几个阶段。

    大家知道吧,很多供应商有时为了进入一家企业做项目,它们在一开始可以跳水价、可以大甩买甚至可以0元进入,那么它挣的是什么钱呢?CR!

    对,有任何一个CR,如果再加上它是一个高大上的国外的所谓著名品牌,那么它的man day的费用会很高。比如説国内的人天单价在2,000~3,000,国外可能起板要收你4,000~6,000元的人天单价,其实人天单价6,000也已经算便宜的啦 ,你们真的没尝过8,000~1万、4万的人天单价呢!!!

    那么对于这样的公司来説,它最开心的就是甲方给他做CR,最好你依赖它,改个接口都要靠它。接口一个收8万,爽啊!!!

    好,一个复杂的系统集成完了,稍稍有任何改动,它牵连的可不只是它自己这一块代码,它会牵连到其它相关的代码,这种问题我们把它称为regression bug,为了做好regression bug的控制我们就要做regression test来保证我的这次改动不会影响到其它无关的功能。

    要知道,系统集成和"系统融和”是完全不一样的。系统集成的内部就是一团“乱麻”,业务层代码咬合在了一起,改一个功能就会引发一系列连锁反映。

    我举个例子来説,国外的系统集成或者説是很多国内软件供应商并未真正把SOA的理念吃透、甚至在瞎用,它们的手法就有点像“把一个人放在病床上,然后为了给这个病人安装一根假手指而需要把这个病人的整条手臂先卸下来,装上手指后再把这个手给病人安上”。

    它就由如下图哪怕是新增一个功能它要动到的也是一系列的“翻原代码”的行为,加上国内IT从12年后发展越来越快、整体行业较浮燥,导致国内程序员水平普遍很低。缺乏整体数据流、业务串联的能力,那么这样的改动引起的连锁反应会更大。

    拿我司曾发生过的一个案例来説,要在原有系统上做一个大闸蟹打折活动,这种设计的做法就是:

    • 设计数据库底层
    • 制作DAO
    • 制作SERVICE
    • 制作Controller
    • 制作页面

    然后有任何bug,bug的修复会把整个软件开发生命周期从头到尾再来一遍,这样的事不断的again, again, again。

    于是,一个活动做个80多人天,花掉10几万20万很正常。如果碰到“高大上”的外企来给你集成,那么把80人天剩4,000,6,000...那么做一个活动用掉个50万,80万,很合理呀。这就是我们很多国内的一 些大型零售企业在系统集成时碰到过的大血坑。

    钱,花了很多,效率又低,质量又差。

    这次的赫兹花了2亿做电商做砸了正是碰到这样的一个血坑。

    如果只是钱的问题还可以容忍,关键在这样的系统集成来到了国内碰上的最坑爹的是“系统并发”问题。

    前面説了,国内的人多,数字化流量高,这样的一种其本身后台legacy system还未经过改造只是遵照着SOA理念去做的系统集成出来的东西是根本挡不了大规模的“并发”的,国内动不动就来个十万级、百万级并发。。。。。。

    这种后台实际上充满着“单体”应用的电商应用APP,实际上是一个连千级并发都撑不住的东西,于是花了钱又做不好事,好了。。。很多企业没有死在“业务领域的竞争”中而是死在了“在国内上了电商系统”后死这个原因上了。

    成就了一上电商就死,电商领域成了一个“95%的电商项目都失败”这么一个“炼狱”了。

    于是基于“系统集成1.5”后又诞生了“系统集成2.0”模式,这次,卖铲子的又没有错过挣钱的好机会于是它提出了SOA2.0模式。

    SOA2.0模式

    这是I.O.E相关的体系们提出的SOA2.0模式,它很理论。但是它在2012~2014年间在其理论框架的指导下诞生了不少衍生技术。

    比如説它的“松耦合,高内聚,组件间无状态,外部模块间需要使用引用,强调系统整体监控、性能上的governance”,等衍生出了轻量级的Nginx、JSON API,ELK,NOSQL等一系列概念和组件甚至优化改造过了一系列之前的时代没有出现过的组件。

    可是当I.O.E体系还只停留在提出这些理念和这些组件的时侯,而我们国内的电商正在发生着巨变。历尽4次双11消费习惯培养后阿里完成了40亿到百亿规模的转变,此时它开始做一件事,那就叫去I.O.E。不要你那些动不动几百、几千万的软硬件了,我们国人一切靠自己来还比你们做了好!

    阿里去I.O.E引起了一股mySQL浪潮。而此时的I.O.E体系也已经日落西山了,IBM在惨败苏宁案例后退出了中国,很多SOA的精华其实从未被真正落地过,同时它被很多国内的开发商错误的理解和使用了,使用的目的也只是为了炒概念、卖高价。在当时,国内有超过90%的开发商认为:NGINX去代Apache,轻TOMCAT,JSON API,ELK,mySQL的组合就可以做电商了。

    OH...MY...GOD!

    首先理念错误、理解不透彻加上整体IT环境浮燥、只求实现不求精的风貌导致了又出现了一个API时代的怪胎,我们説API是一个好东西,可是它造出的怪胎更诡异!

    先从开发团队来错误的理解SOA2.0理念开始分析,下面是一个标准的在当时直到现在还有很多开发团队是这么认为的一种项目分工上的划分模式。

    我们拿JAVA项目来説,把系统划分成这么多子模块,再分别开发和打包以及分布式部署,这就是SOA!

    一切看似那么的自然。。。。。。那么的应该。。。。。。那么的。。。最后在面临国内十万、百万、千万级并发时死得那么的惨

    • 淘宝惨烈过
    • JD也惨烈过

    要不然怎么会出现“JD老刘的两把菜刀”的故事呢?以前去深圳学习JD618保卫战时还听説这个“两把菜刀”是真事呢!!!

    我们来看看工程项目上折的细又小、看似专业实际没有深入理解SOA2.0时代的精髓而只学到了表面的东西导致在当年产出的是一种什么样的怪胎吧。让我们直接从系统层面入手分析

    两个架构,先説一下其实都是“怪胎”;

    尚且不説第二个“看似专业设计架构”很多国内的供应商、软件开发团队还未达到只达到了前一种“通用设计架构”的水平,第二种架构再怎么説也比第一种要好一点,我们把它称为怪胎1.0和怪胎2.0版吧。

    怪在哪呢?下面来分析怪胎2.0版。

    场景发生在某大促的当天,在平时怪胎架构一点问题都不会发生,一切看似相当的正常和完美。而当大促这天一到,抢券、秒杀、折上折一开始:

    1. Web层汹涌压力扑面而来,这时的反映就是用户手机APP端卡死、白屏、卡顿、没反映;
    2. 于是运维一看Zabbix,哇~所有Web服务器标红,业务老板在屁股后面催的紧“快点搞定”,于是乎运维紧急增加Web服务器;
    3. 好,Web流量进来了,tomcat层吃不消了,zabbix频频告警,老板在屁股后面又开始催了“怎么还没搞定?”。于是我们增加tomcat服务器;
    4. tomcat扩了N个自以为没事了,加完后整个DB挂了,CPU飙升到100%以上,内存使用率高达95%以上,一堆的死锁,APP还是卡、白屏,这时已经距离活动开始过去了1小时了,业务老板破口大骂:“你们有没有做过电商呀,你们到底懂不懂,搞不定,滚”
    5. 这时运维傻了。。。介个问题。。。需要研发来帮忙了
    6. 好吧,活动第一天,失败。老板组织了研发、运维浩浩荡荡一大批开了个总结大会来研究第二天的方案,研发终于提出了靠谱的方案。很多内容可以走缓存,我们不该走DB的。于是大家开始了不要命的熬夜改造DAO层代码,把一些通用的都移到缓存;
    7. 此时,离第二天还剩4个半小时左右了,抓紧睡一觉吧,很多开发睡觉时还在做美梦,梦到第二天因为开发团队的给力付出我们终于顶下了流量,老板重点表扬开发;
    8. 第二天活动开始了,哇~一开始30秒时整个流量似乎比昨天大了2-3倍,这个很正常呀因为系统放开了吃流量肯定这个量超过昨天的量,然后30秒过了没多久,整个APP卡死、白屏。哈哈哈,再一看,缓存爆了,缓存爆了后流量落到DB,DB又来了一个CPU飙升到100%以上,内存使用率高达95%以上。。。。。。
    9. 再加DB,DB加完后发觉第三天量更大了,再加Web,Web加完后Tomcat中间群被压跨了,再回到以上第3点

    多少企业经历了上述的过程?我告诉大家一个值,超过90%的企业都有过上述的大血坑。

    这个大血坑会造成不少创业型公司秒死、见光死,也造成很多大企业一整批IT被干掉,也造就了那传説中的“两把菜刀”。

    这样的系统和设计它其实是由如下面的这样的一个怪胎的长相:

    脑袋小,脖子细的要命,肚子大,下盘小。吃饭吃多了他就呕,走路一快他就摔!这么样的一个怪胎!

    那么我们説系统性能没有做好?业务功能就一定做好了吗?

    嘿嘿嘿,我们回看I.O.E体系们在SOA2.0时代提出的一个概念图,再来看一遍这个图

    然后我们结合以下的一个场景再来考虑一下:

    小龙虾节活动,从数据库设计->存取层->服务层->控制层。从头到尾做了一遍,用掉了80多人天的价格。

    来了一个阳澄湖大闸蟹打折活动,从数据库设计->存取层->服务层->控制层。从头到尾做了一遍,又用掉了80多人天的价格。

    嘿嘿嘿,我们把以上深奥的理论,抽像成以下一个这样的业务场景大家看一下,是不是就可以理解为什么上述两个都同样是打折活动的业务场景分别都要用80多人天呢?

    上图已经可以很好的说明我们的程序员是如何沦落到程序猿、码农的了。

    性能达不到、加速业务、快速响应多变的由其是中国大陆市场几乎每天都在变动的业务也做不到,这是2005~2015年这10年国人特别是国内很多知名500强在电商领域经历的痛苦的10年,各种抱怨IT不给力。

    IT各种想办法找I.O.E相关体系来做企业整体解决方案,钱出了一大波,然并卵,各种继续不给力、抱怨。。。。。。again,again and again!!!

    而这10年,阿里和一些走在比较前沿或者説曾经在那10年内没有“死”的一些民营体制、特别接中国地气的企业他们已经开始深刻得总结、反省、并且依靠着自身之前学习到那些外资高大上的一些理论、知识、方法论后把它们再“本土化”并结合了中国自身特色,继而打造出来了一个新的产物,这个新的产物就是“全渠道零售中台”。

     

    回过头来看中台,什么是中台

    也有画成下面这样风格的图

    其实第2张图和第1张无非就是第一张的level3级别功能扩充了比较丰富点,第二张呢颜色鲜明一些。

    That's it,仅次而己!

    然后很多外资包括国内的一些甲方型企业拿着这样的图説“这就是中台”。。。。。。现在知道错在哪了吧。

    我上面列举的1.0,1.5,2.0时代的任何一种架构,其实都可以做成这样的“业务功能图”。

    这只是业务功能图而己,它不是代表"我“做出来的就一定是中台。

    我们看事务不能光看“外表”,我们需要看事物的“本质”,遵循着本质的那些公司都成功了,如阿里、苏宁、保洁、立白、海尔、华为。。。。。。有很多不再多叙。

    那么中台的本质到底在什么?而且是一个全渠道中台,也有人管它叫云中台它必须具备以下几样东西

    从业务功能上来分

    1. 全渠道订单中心,它必须是一个全渠道的订单中心,订单属性拥有线上、线下、O+2、第三方等各种渠道的特性;
    2. 全渠道商品管理中心,可以管理线上、线下甚至是虚拟商品;
    3. 全渠道会员中心,这个会员中心一分为2,一个合格的中台需要具备其中的CRM Foundation即会员中心基础功能,另一个叫“营销中心”,对,整个会员中心由“基础功能+营销中心”两部分构成,而很多好的中台不一定包括这个“营销中心”,因为营销中心可以诞生出另一个全渠道的产品,叫SCRM。我们不要求一个全渠道的零售中台内必须包括全渠道营销中心,必竟术业要有专精;
    4. 全渠道的促销中心,促销和营销很多人会搞起来,促销中心和营销中心在功能上是有相近的,有人把促销归为营销也有人把促销和营销进行分离,分离的条件就是“以会员为中心”和根据一个企业内的业务组织架构来决定的。这一定一定是一个全渠道的促销中心,它可以对线上线下同时促销,説白了就是你在手机APP商城内使用的券同时也可以使用在自助机、扫码购、微信小程序甚至在同一个零售企业门店POS结帐时使用,让客户无论是在线上还是在线下消费时“无缝/无差别”体验,这就叫全渠道。不管你什么活动、打折、促销,它还都是可以支持图形化界面可配置的;
    5. 内容中心,它又被称为CMS即Content Management System。它可以把手机、微信小程序、Web网站通过图形化类似于Photoshop或者説它比较接近于以前的DreamWeaver或者是FrontPage的一种“傻瓜”界面把这些活动给配置出来,它在配置的时候是可以通过结合前面的促销中心去做“协同工作”的;
    6. 财务共享中心-支付渠道啦、支付中心啦,支持各种支付,接入支付渠道时它也是可配置或者説是“半可配置”来完成一个支付渠道的接入的;
    7. 物流库存中心,支持全渠道的物流和库存,不管是自营、O+O、第三方还是自提,全部支持;
    8. 多租户管理中心,咦。。。。。。这是什么东西?唉呀,很简单!都上全渠道中台了,你这个电商不可能只是面向垂直单一名牌吧?一定是类似于“天猫店”那种多商户玩法吧?也有人管它叫B2B2C或者干脆简称成BBC功能;

    从技术上来分(月球的背面到底是什么)

    我们前面説了,业务功能它的表现出给到大众的一面很美丽、很灿烂。可是它不是本质,它不代表全渠道中台,我们需要了解月球的背后到底是什么?是不是真的有ET?喂。。。老婆,出来看上帝啊!

    从技术上来説一个全渠道必须具备如下几大功能,缺一不可,对。。。缺一不可!

    1. 微服务总线,这是必须要有的,真正的微服务讲究的是什么哈?我们先不説微服务所有的细节功能单説涉及到我们性能的那么几个功能吧:1)平峰削谷 2)服务自发现 3)服务升级降级 4)可弹性扩充,只説这4个点,有这4个点绝大多数的零售电商网站够用了,除非你能达到淘宝的量,我们后面章节会把微服务功能逐个剖析、亲自动手设计、乃至实现
    2. 各业务模块可纵向扩展,横向扩展是很简单的事,什么叫业务模块纵向扩展?比如説订单的写入和读都可以作分开的部署
    3. 可弹性的分布式的并且是多样化的缓存群
    4. 异步消息队列-MQ,必不可少
    5. 规则引擎,你当促销中心是怎么实现的?嘿
    6. HTTP请求级别缓存,这个缓存可和后台的那个分布式缓存群是不一样的东西哦,它缓存的是用户请求,相当于一个CDN功能但是和CDN又不一样,因为CDN只能缓存绝对静态的内容
    7. 分布式批处理任务-类似于网络计算,它比网格计算更轻、小
    8. 标准的安全认证登录接口,支持最常用的如:JWT,OAUTH2等协议
    9. 支持分步式数据库,此处可不只是一个数据库,你要有钱可以去烧Oracle RAC,阿里在20~40亿时为什么它要去I.O.E?那么用开源的数据库你需要怎么去实现原来的Oracle RAC的功能呢?当然你雇了一堆的架构师自己也是可以去打造这样的分布式数据库的结构应用的,只是一个产品如果它的原生就支持分布式数据库、分布式事务、可折表折库(此处指的可是纵向折哦),横向谁不会无非就是加slavers:)
    10. 成熟的性能监控
    11. 成熟的CI(持续集成)组件
    12. 配置中心,一个全渠道中台,组件少的有10多个模块,每个模块至少2-3个服务器,多的几十个模块,oh my god,全部写在properties文件里?Are you kidding me?

    所以,月球的背面长的是个什么样的呢?即什么是真正的全渠道零售中台?

    全渠道零售中台的“真容”

    我用下面的这张图来解析全渠道零售中台的技术的面长成个什么样!

    把我这篇文章的第1张图配合着全文最后一张图来看,那么你看的才是一个真正的全渠道中台!

    这两张图:

    1. 只看第1张,你会被人忽悠的体无完肤,出了钱买不到好东西;
    2. 只看第2张而不看第1张的结果是,你可能买到的不是一个产品级的解决方案而只是一个技术框架,一切业务功能需要从头开发,这是巨大的工作量和成本的付出;

    但是,不代表你把上述2张图结合起来看了就一定可以找中你“命中的中台”,还有很多、很多其它因素需要考虑。

     

    最后説一下为什么叫中台这个“中”字呢

    从业务层面解析为什么叫“中”台

    中台,我们的国人为了解决“TO C端业务的快速多变”,使用的是诸多非功能性需求如CMS+规则引擎+图形化编程,其实説白了就是把TO C端的前端的逻辑“下沉”,下沉到了这个中台系统中而不是停留在APP端 ,把APP端的功能做成了可以通过后台配出来,我之前的博客説过,所谓IT上口头説的“业务业务”,指的就是用户端功能,而不是让你去考上岗证。

    中国人做的这种高度一体化方案是基于可以彻底抛弃ERP的思想来做的,做什么legacy system的改造呢?这些功能在中台里已经有了,把你原来企业那10几个legacy system的数据做一次性的迁移,然后系统一刀切掉就好了,这是中国人的思路!但是中台在推出不久后它又要兼顾着中国人自古的“包容”精神,即我又要可以支撑原有legacy system和我的集成。那么,把原有后台legacy system的功能也放到这个中台系统中,因此它是后台业务功能的“上浮”。

    一个TO C端业务的下沉;

    一个后台业务功能的上浮;

    而中台它处于当中这一块地位,因此它就叫“中”台!

    而不是很多人认为,它处于后台和APP手机端应用的当中因此才叫中台的,不是的。这个理解太表面了没有真正理解中台的中到底为什么要叫中的背后的原理,中台的“中”是我上述这一段总结,这是业界真正公认的“中”。

    因此我这一系列文章才不仅仅只是写业务(解决方案)或者写技术,还要写数字化变形、写管理、写策略。。。。。。后面我们还会有更多精彩!

    先预告一些下一章内容吧:

    下一章我会对全渠道零售中台中的业务功能、技术组件一个个折开来、揉碎了和大家讲解!

    展开全文
  • 无穷数字

    千次阅读 2018-05-05 00:00:00
    一、你能数到多少?有这么一个故事,说的是两个匈牙利贵族决定做一次数数游戏——谁说出的数字大谁赢。 “好,”一个贵族说,“你先说吧!” 另一个绞尽脑汁想了好几分钟, 后说出...

    一、你能数到多少?


    有这么一个故事,说的是两个匈牙利贵族决定做一次数数游戏——谁说出的数字大谁赢。 


    “好,”一个贵族说,“你先说吧!” 


    另一个绞尽脑汁想了好几分钟, 后说出了他所想到的 大数字:“3”。


    现在轮到第一个动脑筋了。苦思冥想了一刻钟以后,他表示弃权说:“你赢啦!” 


    这两个贵族的智力当然是不很发达的。再说,这很可能只是一个挖苦人的故事而已。然而,如果上述对话是发生在原始部族中,这个故事大概就完全可信了。有不少非洲探险家证实,在某些原始部族里,不存在比3 大的数词。如果问他们当中的一个人有几个儿子,或杀死过多少敌人,那么,要是这个数字大于3,他就会回答说“许多个。”因此,就计数这项技术来说,这些部族的勇士们可要败在我们幼儿园里的娃娃们的手下了,因为这些娃娃们竟有一直数到十的本领呢!


    现在,我们都习惯地认为,我们想把某个数字写成多大,就能写得多大——战争经费以分为单位来表示啦,天体间的距离用英寸来表示啦,等等——只要在某个数字的后面接上一串零就是了。你可以一直这样写下去,直到手腕发酸为止。这样,尽管目前已知的宇宙①中所有原子的数目已经很大,等于300 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 

    000 000 000,但是,你还可以写出比这更大的数目来。


    上面这个数可以改写得短一些,即写成

    640?wx_fmt=png

    在这里,10 的右上角的小号数字74 表示应该写出多少个零。换句话说,这个数字意味着3 要用10 乘上74 次。


    但是在古代,人们并不知道这种简单的“算术简示法”。这种方法是距今不到两千年的某个佚名的印度数学家发明的。在这个伟大发明——这确实是一项伟大的发明,尽管我们一般意识不到这一点——出现之前,人们对每个数位上的数字,是用专门的符号反复书写一定次数的办法来表示的。例如,数字8732 在古埃及人写来是这样的:

    640?wx_fmt=png

    而在凯撒(Julius Caeser)②的衙门里,他的办事员会把这个数字写成

    MMMMMMMMDCCXXXII

    这后一种表示法你一定比较熟悉,因为这种罗马数字直到现在还有些用场——表示书籍的卷数或章数啦,各种表格的栏次啦,等等。不过,古代的计数很难得超过几千,因此,也就没有发明比一千更高的数位表示符号。一个古罗马人,无论他在数学上是何等训练有素,如果让他写一下“一百万”,他也一定会不知所措。他所能用的 好的办法,只不过是接连不断地写上一千个M,这可要花费几个钟点的艰苦劳动啊(图1)。


    在古代人的心目中,那些很大的数目字,如天上星星的颗数、海里游鱼的条数、岸边沙子的粒数等等,都是“不计其数”,就像“5”这个数字对原始部族来说也是“不计其数”,只能说成 

    640?wx_fmt=png

    图1 凯撒时代的一个古罗马人试图用罗马数字来写“一百万”,墙上挂的那块板恐怕连“十万”也写不下


    “许多”一样。

    阿基米德(Archimedes),公元前3 世纪大名鼎鼎的大科学家,曾经开动他那出色的大脑,想出了书写巨大数字的方法。在他的论文《计沙法》中这样写着:


    有人认为,无论是在叙拉古③,还是在整个西西里岛,或者在世界所有有人烟和无人迹之处,沙子的数目是无穷大的。也有人认为,这个数目不是无穷大的,然而想要表达出比地球上沙粒数目还要大的数字是做不到的。很明显,持有这种观点的人会更加肯定地说,如果把地球想像成一个大沙堆,并在所有的海洋和洞穴里装满沙子,一直装到与最高的山峰相平,那么,这样堆起来的沙子的总数是无法表示出来的。但是,我要告诉大家,用我的方法,不但能表示出占地球那么大地方的沙子的数目,甚至还能表示出占据整个宇宙空间的沙子的总数。


    阿基米德在这篇著名的论文中所提出的方法,同现代科学中


    表达大数目字的方法相类似。他从当时古希腊算术中最大的数“万”开始,然后引进一个新数“万万”(亿)作为第二阶单位,然后是“亿亿”(第三阶单位)、“亿亿亿”(第四阶单位),等等。


    写个大数字,看来似乎不足挂齿,没有必要专门用几页的篇幅来谈论。但在阿基米德那个时代,能够找到写出大数字的办法,确实是一项伟大的发现,使数学向前迈出了一大步。


    为了计算填满整个宇宙空间所需的沙子总数,阿基米德首先得知道宇宙的大小。按照当时的天文学观点,宇宙是一个嵌有星星的水晶球。阿基米德的同时代人,著名的天文学家,萨摩斯④的阿里斯塔克斯(Aristarchus)⑤求得从地球到天球面的距离为10 000 000 000 斯塔迪姆⑥,即约为1 000 000 000 英里。


    阿基米德把天球和沙粒的大小相比,进行了一系列足以把小学生吓出梦魇症来的运算, 后他得出结论说:


    很明显,在阿里斯塔克斯所确定的天球内所能装填的沙子粒数,不会超过一千万个第八阶单位⑦。


    这里要注意,阿基米德心目中的宇宙的半径要比现代科学家们所观察到的小得多。十亿英里,这只不过刚刚超过从太阳到土星的距离。以后我们将看到,在望远镜里,宇宙的边缘是在5 000 000 000 000 000 000 000 英里的地方,要填满这样一个已被观测到的宇宙,所需要的沙子数超过640?wx_fmt=png粒(即1 的后面有100 个零)。


    这个数字显然比前面提到的宇宙间的原子总数3×1074 大多了,这是因为宇宙间并非塞满了原子。实际上,在一立方米的空间内,平均才只有一个原子。


    要想得到大数目字,并不一定要把整个宇宙倒满沙子,或进行诸如此类的剧烈活动。事实上,在很多乍一看来似乎很简单的问题中,也常会遇到极大的数字,尽管你原先决不会想到,其中会出现大于几千的数字。


    有一个人曾经在大数目字上吃了亏,那就是印度的舍罕王(Shirham)。根据古老的传说,舍罕王打算重赏象棋⑧的发明人和进贡者,宰相西萨·班·达依尔(Sissa Ben Dahir)。这位聪明大臣的胃口看来并不大,他跪在国王面前说:“陛下,请您在这张棋盘的第一个小格内,赏给我一粒麦子;在第二个小格内给两粒,第三格内给四粒,照这样下去,每一小格内都比前一小格加一倍。陛下啊,把这样摆满棋盘上所有64 格的麦粒,都赏给您的仆人罢!” 


    “爱卿。你所求的并不多啊。”国王说道,心里为自己对这样一件奇妙的发明所许下的慷慨赏诺不致破费太多而暗喜。“你当然会如愿以偿的。”说着,他令人把一袋麦子拿到宝座前。


    计数麦粒的工作开始了。第一格内放一粒,第二格内放两粒,第三格内放四粒,……还没到第二十格,袋子已经空了。一袋又一袋的麦子被扛到国王面前来。但是,麦粒数一格接一格地增长得那样迅速,很快就可以看出,即便拿来全印度的粮食,国王也兑现不了他对西萨·班·达依尔许下的诺言了,因为这需要有18 446 744 073 709 551 615 颗麦粒⑨呀! 


    这个数字不像宇宙间的原子总数那样大,不过也已经够可观了。1 蒲式耳⑩小麦约有5 000 000 颗,照这个数,那就得给西萨· 班·达依尔拿来4 万亿蒲式耳才行。这位宰相所要求的,竟是全世界在2000 年内所生产的全部小麦!


    这么一来,舍罕王发觉自己欠了宰相好大一笔债。什么办?要么是忍受西萨·班·达依尔没完没了的讨债,要么是干脆砍掉他的脑袋。据我猜想,国王大概选择了后面这个办法。


    另一个由大数目字当主角的故事也出自印度,它是和“世界末日”的问题有关的。偏爱数学的历史学家鲍尔(Ball)是这样讲述这段故事的⑴:


    在世界中心贝拿勒斯⑵的圣庙里。安放着一个黄铜板,板上插着三根宝石针。每根针高约1 腕尺(1 腕尺大约合20 英寸)。像韭菜叶那样粗细。梵天⑶在创造世界的时候,在其中的一根针上从下到上放下了由大到小的64 片金片。这就是所谓梵塔。不论白天黑夜,都有一个值班的僧侣按照梵天不渝的法则,把这些金片在三根针上移来移去:一次只能移一片,并且要求不管在哪一根针上,小片永远在大片的上面。当所有64 片都从梵天创造世界时所放的那根针上移到另外一根针上时,世界就将在一声霹雳中消灭,梵塔、庙宇和众生都将同归于尽。 


    图3 是按故事的情节所作的画,只是金片少画了一些。你不妨用纸板代表金片,拿长钉代替宝石针,自已搞这么一个玩具。不难

    640?wx_fmt=png

    图3 一个僧侣在大佛像前解决“世界未日”的问题。为了省事起见,这里没有画出64 片金片来


    发现,按上述规则移动金片的规律是:不管把哪一片移到另一根针上,移动的次数总要比移动上面一片增加一倍。第一片只需一次,下一片就按几何级数加倍。这样,当把第64 片也移走后,总的移动次数便和西萨·班·达依尔所要求的麦粒数一样多了⑷! 

    把这座梵塔全部64 片金片都移到另一根针上,需要多长时间呢?一年有31 558 000 秒。假如僧侣们每一秒钟移动一次,日夜不停,节假日照常干,也需要将近5800 亿年才能完成。


    把这个纯属传说的寓言和按现代科学得出的推测对比一下倒是很有意思的。按照现代的宇宙进化论,恒星、太阳、行星(包括地球)是在大约30 亿年前由不定形物质形成的。我们还知道,给恒星,特别是给太阳提供能量的“原子燃料”还能维持100 亿~150 亿年(见“创世的年代”一章)。因此,我们太阳系的整个寿命无疑要短于200 亿年,而不像这个印度传说中所宣扬的那样长!不过,传说毕竟只是传说啊! 


    在文学作品中所提及的 大数字,大概就是那个有名的“印刷行数问题”了。 


    假设有一台印刷机器可以连续印出一行行文字,并且每一行都能自动换一个字母或其他印刷符号,从而变成与其他行不同的字母组合。这样一架机器包括一组圆盘,盘与盘之间像汽车里程表那祥装配,盘缘刻有全部字母和符号。这样,每一片轮盘转动一周,就会带动下一个轮盘转动一个符号。纸张通过滚筒自动送入盘下。这样的机器制造起来没有太大的困难,图4 是这种机器的示意图。 


    现在,让我们开动这架印刷机,并检查印出的那些没完没了的东西吧。在印出的一行行字母组合当中,大多数根本没有什么意思,如:

    aaaaaaaaaaaa…

    或者

    boobooboobooboo…

    或者

    zawkpopkossscilm…

    但是,既然这台机器能印出所有可能的字母及符号的组合,我们就能从这堆玩艺儿中找出有点意思的句子。当然,其中又有许多是胡说八道,如:

    640?wx_fmt=png

    图4 一台刚刚印出一行莎士比亚诗句的自动印刷机


    horse has six legs and…(马有六条腿,并且……)

    或者

    I like apples cooked in terpentin…

    (我喜欢吃松节油煎苹果……)。

    不过, 只要找下去, 一定会发现莎士比亚(William Shake- spare)⑸的每一行著作,甚至包括被他扔进废纸篓里去的句子!实际上,这台机器会印出人类自从能够写字以来所写出的一切句子:每一句散文,每一行诗歌,每一篇社论,每一则广告,每一卷厚厚的学术论文,每一封书信,每一份订奶单……


    不仅如此,这架机器还将印出今后各个世纪所要印出的东西。从滚筒下的纸卷中,我们可以读到30 世纪的诗章,未来的科学发现,2344 年星际交通事故的统计,还有一篇篇尚未被作家们创作出来的长、短篇小说。出版商们只要搞出这么一台机器,把它安装在地下室里,然后从印出的纸卷里寻找好句子来出版就是了—— 他们现在所干的不也差不多就是这样嘛!为什么人们没有这样干呢?


    来,让我们算算看,为了得到所有字母和印刷符号的组合,该印出多少行来。 


    英语中有26 个字母、十个数码(0,1,2,…,9)、还有14 个常用符号(空白、句号、逗号、冒号、分号、问号、惊叹号、破折号、连字符、引号、省略号、小括号、中括号、大括号),共50 个字符。再假设这台机器有65 个轮盘,以对应每一印刷行的平均字数。印出的每一行中,排头的那个字符可以是50 个字符当中的任何一个,因此有50 种可能性。对这50 种可能性当中的每一种,第二个字符又有50 种可能性,因此共有50×50=2 500 种。对于这前两个字符的每一种可能性,第三个字符仍有50 种选择。这样下去,整行进行安排的可能性的总数等于

    (65 个)

    50×50×50×…×50

    或者640?wx_fmt=png,即等于640?wx_fmt=png

    要想知道这个数字有多么巨大,你可以设想宇宙间的每个原子都变成一台独立的印刷机,这样就有3×1074 部机器同时工作。再假定所有这些机器从地球诞生以来就一直在工作,即它们已经工作了30 亿年或1017 秒。你还可以假定这些机器都以原子振动的频率进行工作,也就是说,一秒钟可以印出1015 行。那么,到目前为止,这些机器印出的总行数大约是

    640?wx_fmt=png

    这只不过是上述可能性总数的三千分之一左右而已。


    看来,想要在这些自动印出的东西里面挑选点什么,那确实得花费非常非常长的时间了! 


    ①这是指目前用最大的望远镜所能探测到的那部分宇宙。

    ②凯撒(公元前100~前44 年)是古罗马帝国的统治者。——译者

    ③叙拉古是古代的城市国家,位于意大利西西里岛东南部。——译者

    ④萨摩斯是希腊的一个岛。——译者

    ⑤阿里斯塔克斯是公元前3 世纪的希腊天文学家。——译者

    ⑥斯塔迪姆是古希腊的长度单位,1 斯塔迪姆为606 英尺6 英寸,或188 米。

    ⑦用我们现在的数学表示法,这个数字是:


    一千万       第二阶         第三阶         第四阶

    (10 000 000)×(100 000 000)×(100 000 000)×(100 000 000)× 

    第五阶       第六阶         第七阶         第八阶

    (100 000 000)×(100 000 000)×(100 000 000)×(100 000 000) 也可以简写成640?wx_fmt=png(即在1 的后面有63 个零)。


    ⑧这里的象棋指的是国际象棋。整个棋盘是由64 个小方格组成的正方形。双方的棋子(每方16 个,包括王一枚,王后一枚、相两枚、马两枚,车两枚、兵八枚)在格内移动,以消灭对方的王为胜。棋盘的形状可参见插图2。——译者


    ⑨这位聪明的宰相所要求的麦子粒数可写为

    640?wx_fmt=png

    在数学上,这类每一个数都是前一个数的固定倍数的数列叫做几何级数(在我们这个例子里,这个倍数为2)。可以证明,这种级数所有各项之和,等于固定倍数(在本例中为2)的项数次方幂(在本例中为64)减去第一项(此例中为1)所得到的差除以固定倍数与1 之差。这就是:

    640?wx_fmt=png

    直接写出结果来就是

    18 446 744 073 709 551 615

    ⑩蒲式耳是欧美的容量单位(计算谷物专用)。1 蒲式耳约合35.2 升。——译者

    ⑴引自W.W.R.Ball, Mathmatical Recreations and Essays(《数学拾零》)。

    ⑵贝拿勒斯是佛教的圣地,位于印度北部。——译者

    ⑶梵天是印度教的主神。——译者

     

    ⑷如果只有7 片,则需要移动的次数为

    640?wx_fmt=png

    当金片为64 片时,需要移动的次数则为

    640?wx_fmt=png

    这就和西萨•班•达依尔所要求的麦粒数相同了。

    ⑸莎士比亚(1564~1616 年),文艺复兴时代的著名英国剧作家及诗人。——译者


    二、怎样计数无穷大的数字


    上一节我们谈了一些数字,其中有不少是毫不含糊的大数。但是这些巨大的数字,例如西萨·班·达依尔所要求的麦子粒数,虽然大得难以令人置信,但毕竟还是有限的,也就是说,只要有足够的时间,人们总能把它们从头到尾写出来。


    然而,确实存在着一些无穷大的数,它们比我们所能写出的无论多长的数都还要大。例如,“所有整数的个数”和“一条线上所有几何点的个数”显然都是无穷大的。关于这类数字,除了说它们是无穷大之外,我们还能说什么呢?难道我们能够比较一下上面那两个无穷大的数,看看哪个“更大些”吗?


    “所有整数的个数和一条线上所有几何点的个数,究竟哪个大些?”——这个问题有意义吗?乍一看,提这个问题可真是头脑发昏,但是著名数学家康托尔(Georg Cantor)首先思考了这个问题。因此,他确实可被称为“无穷大数算术”的奠基人。 


    当我们要比较几个无穷大的数的大小时,就会面临这样一个问题:这些数既不能读出来,也无法写出来,该怎样比较呢?这下子,我们自己可有点像一个想要弄清自己的财物中,究竟是玻璃珠子多,还是铜币多的原始部族人了。你大概还记得,那些人只能数到3。难道他会因为数不清大数而放弃比较珠子和铜币数目的打算?根本不会如此。如果他足够聪明,他一定会通过把珠子和铜币逐个相比的办法来得出答案。他可以把一粒珠子和一枚铜币放在一起,另一粒珠子和另一枚铜币放在一起,并且一直这样做下去。如果珠子用光了,而还剩下些铜币,他就知道,铜币多于珠子;如果铜币先用光了,珠子却还有多余,他就明白,珠子多于铜币;如果两者同时用光,他就晓得,珠子和铜币数目相等。


    康托尔所提出的比较两个无穷大数的方法正好与此相同:我们可以给两组无穷大数列中的各个数一一配对。如果 后这两组都一个不剩,这两组无穷大就是相等的;如果有一组还有些数没有配出去,这一组就比另一组大些,或者说强些。


    这显然是合理的、并且实际上也是唯一可行的比较两个无穷大数的方法。但是,当你把这个方法付诸实施时,你还得准备再吃一惊。举例来说,所有偶数和所有奇数这两个无穷大数列,你当然会直觉地感到它们的数目相等。应用上述法则,也完全合理,因为这两组数间可建立如下的一一对应关系:

    640?wx_fmt=png

    在这个表中,每一个偶数都与一个奇数相对应。看,这确实再简单、再自然不过了! 

    但是,且慢。你再想一想:所有整数(奇偶数都在内)的数目和单单偶数的数目,哪个大呢?当然,你会说前者大一些,因为所有的整数不但包含了所有的偶数,还要加上所有的奇数啊。但这只不过是你的印象而已。只有应用上述比较两个无穷大数的法则,才能得出正确的结果。如果你应用了这个法则,你就会吃惊地发现,你的印象是错误的。事实上,下面就是所有整数和偶数的一一对应表:

    640?wx_fmt=png

    按照上述比较无穷大数的规则,我们得承认,偶数的数目正好和所有整数的数目一样大。当然,这个结论看来是十分荒谬的,因为偶数只是所有整数的一部分。但是不要忘了,我们是在与无穷大数打交道,因而就必须做好遇到异常的性质的思想准备。


    在无穷大的世界里,部分可能等于全部!关于这一点,著名德国数学家希尔伯特(David Hilbert)有一则故事说明的再好不过了。据说在他的一篇讨论无穷大的演讲中,他曾用下面的话来叙述无穷大的似非而是的性质:①


    我们设想有一家旅店,内设有限个房间,而所有的房间都已客满。这时来了位新客,想订个房间。旅店主说:“对不起,所有的房间都住满了。”现在再设想另一家旅店,内设无限多个房间,所有房问也都客满了。这时也有一位新客来临,想订个房间。


    “不成问题!”旅店主说。接着,他就把一号房间里的旅客移至二号房间,二号房间的旅客移到三号房间,三号房间的旅客移到四号房间,等等,这一来,新客就住进了已被腾空的一号房间。


    我们再设想一座有无限个房间的旅店,各个房间也都住满了。这时,又来了无穷多位要求订房间的客人。


    “好的,先生们请等一会儿。”旅店主说。


    他把一号房间的旅客移到二号房间,二号房间的旅客移到四号房间,三号房间的旅客移到六号房间,如此,如此。


    现在,所有的单号房间都腾出来了。新来的无穷多位客人可以住进去了。


    由于希尔伯特讲这段故事时正值世界大战期间,所以,即使在华盛顿,这段话也不容易被人们所理解②。但这个例子却确实举到了点子上,它使我们明白了:无穷大数的性质与我们在普通算术中所遇到的一般数字大不一样。

    按照比较两个无穷大数的康托尔法则,我们还能证明,所有的普通分数(如640?wx_fmt=png等)的数目和所有的整数相同。把所有的分数按照下述规则排列起来:先写下分子与分母之和为2 的分数,这样的分数只有一个,即640?wx_fmt=png;然后写下两者之和为3 的分数,即640?wx_fmt=png640?wx_fmt=png;再往下是两者之和为4的,即640?wx_fmt=png640?wx_fmt=png640?wx_fmt=png。这样做下去,我们可以得到一个无穷的分数数列,它包括了所有的分数(图5)。现在,在这个数列旁边写上整数数列,就得到了无穷分数与无穷整数的一一对应。可见,它们的数目又是相等的! 

    640?wx_fmt=png

    图5 原始人和康托尔教授都在比较他们数不出来的数目的大小


    你可能会说“是啊,这一切都很妙,不过,这是不是就意味着,所有的无穷大数都是相等的呢?如果是这样,那还有什么可比的呢?”


    不。事情并不是这样。人们可以很容易地找出比所有整数或所有分数所构成的无穷大数还要大的无穷大数来。 


    如果研究一下前面出现过的那个比较一条线段上的点数和整数的个数的多少的问题,我们就会发现,这两个数目是不一样大的。线段上的点数要比整数的个数多得多。为了证明这一点,我们先来建立一段线段(比如说1 寸长)和整数数列的一一对应关系。 


    这条线段上的每一点都可用这一点到这条线的一端的距离来表示,而这个距离可以写成无穷小数的形式,如

                   0.735 062 478 005 6…… 

    或者

                   0.382 503 756 32……③

    现在我们所要做的,就是比较一下所有整数的数目和所有可能存在的无穷小数的数目。那么,上面写出的无穷小数和640?wx_fmt=png640?wx_fmt=png这类分数有什么不同呢?大家一定还记得在算术……课上学过的这样一条规则:每一个普通分数都可以化成无穷循环小数。如640?wx_fmt=png= 0.6666……640?wx_fmt=png= 0. 66,=0.428571⋰428571⋰428571⋰4……=0.428571。我们已经证明过,所有分数的数目和所有整数的数目相等,所以,所有循环小数的数目必定与所有整数的数目相等。但是,一条线段上的点不可能完全由循环小数表示出来。绝大多数的点是由不循环的小数表示的。


    因此就很容易证明,在这种情况下,一一对应关系是无法建立的。

    假定有人声称他已经建立了这种对应关系,并且,对应关系具

    有如下形式:

     N 

    1   0. 3 8 6 0 2 5 6 3 0 7 8……     

    2   0. 5 7 3 5 0 7 6 2 0 5 0……     

    3   0. 9 9 3 5 6 7 5 3 2 0 7……     

    4   0. 2 5 7 6 3 2 0 0 4 5 6……     

    5   0. 0 0 0 0 5 3 2 0 5 6 2……     

    6   0. 9 9 0 3 5 6 3 8 5 6 7……     

    7   0. 5 5 5 2 2 7 3 0 5 6 7…… 

    8   0. 0 5 2 7 7 3 6 5 6 4 2…… 

    ·    ……………………………     

    ·    …………………………… 

    ·    …………………………… 

    当然,由于不可能把无穷多个整数和无穷多个小数一个不漏地写光,因此,上述声称只不过意味着此人发现了某种普遍规律(类似于我们用来排列分数的规律),在这种规律的指导下,他制定了上表,而且任何一个小数或迟或早都会在这张表上出现。


    不过,我们很容易证明,任何一个这类的声称都是站不住脚的,因为我们一定还能写出没有包括在这张无穷表格之中的无穷多个小数。怎么写呢?再简单不过了。让这个小数的第一小数位(十分位)不同于表中第一号小数的第一小数位,第二小数位(百分位)不同于表中第二号小数的第二小数位,等等。这个数可能就是这个样子(还可能是别的样子):


    640?wx_fmt=png


    这个数无论如何在上表中是找不到的。如果此表的作者对你说,你的这个数在他那个表上排在第137 号(或其他任何一号),你就可以立即回答说:“不,我这个数不是你那个数,因为这个数的第137 小数位和你那个数的第137 小数位不同。” 


    这么一来,线上的点和整数之间的一一对应就建立不起来了。也就是说,线上的点数所构成的无穷大数大于(或强于)所有整数或分数所构成的无穷大数。


    刚才所讨论的线段是“1 寸长”。不过很容易证明,按照“无穷大数算术”的规则,不管多长的线段都是一样。事实上,1 寸长的线段也好,1 尺长的线段也好,1里长的线段也好,上面的点数都是相同的。只要看看图6 即可明了,AB 和AC 为不同长度的两条线段,现在要比较它们的点数。过AB 的每一个点作BC 的平行线,都会与AC 相交,这样就形成了一组点。如D 与D′,E 与E′,F 与F′等。对AB 上的任意一点,AC 上都有一个点和它相对应,反之亦然。这样,就建立了一一对应的关系。可见,按照我们的规则,这两个无穷大数是相等的。 


    通过这种对无穷大数的分析,还能得到一个更加令人惊异的结论:平面上所有的点数和线段上所有的点数相等。为了证明这一点,我们来考虑一条长1 寸的线段AB 上的点数和边长1寸的正方形CDEF 上的点数(图7)。

    640?wx_fmt=png


    假定线段上某点的位置是0.75120386…。我们可以把这个数按奇分位和偶分位分开,组成两个不同的小数:

                     0 . 7 1 0 8… 

                     0 . 5 2 3 6… 

    以这两个数分别量度正方形的水平方向和垂直方向的距离,便得出一个点,这个点就叫做原来线段上那个点的“对偶点”。反过来,对于正方形内的任意一点,比如说由0.4835…和0.9907…这两个数描述的点,我们把这两个数掺到一起,就得到了线段上的相应的“对偶点”0.49893057…。


    很清楚,这种做法可以建立那两组点的一一对应关系。线段上的每一个点在平面上都有一个对应的点,平面上的每一个点在线段上也有一个对应点,没有剩下来的点。因此,按照康托尔的标准,正方形内所有点数所构成的无穷大数与线段上点数的无穷大数相等。


    用同样的方法,我们也容易证明,立方体内所有的点数和正方形或线段上的所有点数相等,只要把代表线段上一个点的无穷小数分作三部分④,并用这三个新小数在立方体内找“对偶点”就行了。和两条不同长度线段的情况一样,正方形和立方体内点数的多少与它们的大小无关。


    尽管几何点的个数要比整数和分数的数目大,但数学家们还知道比它更大的数。事实上,人们已经发现,各种曲线,包括任何一种奇形怪状的样式在内,它们的样式的数目比所有几何点的数目还要大。因此,应该把它看作是第三级无穷数列。

    按照“无穷大数算术”的奠基者康托尔的意见,无穷大数是用希伯来字母ℵ(读作阿莱夫)表示的,在字母的右下角,再用一个小号数字表示这个无穷大数的级别。这样一来,数目字(包括无穷大数)的数列就成为

              1,2,3,4,5,…ℵ1,ℵ2,ℵ3…

    我们说“一条线段上有ℵ1 个点”或“曲线的样式有ℵ2 种”,就和我们平常说“世界有7 大洲”或“一副扑克牌有54 张”一样简单了。

    640?wx_fmt=png

    图8  无穷大数的头三级


    在结束关于无穷大数的讨论时,我们要指出,无穷大数的级只要有几个,就足够把人们所能想像出的任何无穷大数都包括进去了。大家知道,ℵ0 表示所有整数的数目,ℵ1 表示所有几何点的数目,ℵ2 表示所有曲线的数目,但到目前为止,还没有人想得出一种能用ℵ3 来表示的无穷大数来。看来,头三级无穷大数就足以包括我们所能想到的一切无穷大数了。因此,我们现在的处境,正好跟我们前面的原始部族人相反:他有许多个儿子,可却数不过3;我们什么都数得清,却又没有那么多东西让我们来数! 


    ①这段文字从未印行过,甚至希尔伯特本人也未写成文字,但是广泛流传着。本书引自R. Courant, The Complete Collection of Helbert Stories。

    ②作者这句话说得比较含蓄,意思大概是说:本来这些概念就不好懂,再加上希尔伯特的国籍是德国——美国在世界大战中的敌国,因此,这段话当时就更不易为美国人所接受了。——译者

    ③我们已经假定线段长1 寸,因此这些小数都小于1。

    ④例如,我们可把数字

    0 . 7 3 5 1 0 6 8 2 2 5 4 8 3 1 2……

    分成下列三个新的小数:

    0 . 7 1 8 5 3…,

    0 . 3 0 2 4 1…,

    0 . 5 6 2 8 2…。

    ∑编辑 | Gemini

    (本文节选自《从一到无穷大:科学中的事实和臆测》)

    640?wx_fmt=jpeg

    算法数学之美微信公众号欢迎赐稿

    稿件涉及数学、物理、算法、计算机、编程等相关领域

    经采用我们将奉上稿酬。

    投稿邮箱:math_alg@163.com


    展开全文
  • 作业,但这种混合表示法易混淆,16进制字母还是数字大。D,1个字节Byte可表示2个连续的16进制数字,16进制数。因为将4个位元Bit化成单独的16进制数字困难,十进制是什么,第1位的权值表示16进制前面的是...
  • tensorflow使用GAN生成手写数字(代码介绍)

    千次阅读 热门讨论 2018-12-26 20:51:53
    本篇文章主要介绍如何来设置一个GAN网络利用MNIST手写数字图片进行训练来生成手写数字图片,代码主要参考github的实现,在原来的基础上做了一些修改和新增了一些功能。本篇文章主要介绍代码的功能和实现,对于GAN...
  • DID 去中心化数字身份

    千次阅读 2021-08-31 14:37:22
    随着互联网的出现和普及,传统的身份有了另外一种表现形式,即数字身份。一般认为,数字身份的演进经历了四个阶段,分别是:中心化身份、联盟身份、以用户为中心的身份以及自 我主权身份。 中心化身份是由单一的权威...
  • 在选购电脑配件的时候,小白们对于cpu...注:如果你嫌文章长的话,可以直接看最后的总结部分(想要详细掌握还是建议全文阅读) 一、intelcpu后边数字及字母的意思 1、intel的CPU按系列可以划分为Core(酷睿)、...
  • 数字能量学

    万次阅读 2019-08-22 14:43:18
    星性:代表贵人的出现,社会的名望,地位。主聪明、智慧,困境中遇到生气星会有新转机。性格:生性善良,人缘好,性格开朗、活泼,喜欢开玩笑。看问题客观,办事冷静,易知足,缺乏斗争精神。在人际关系方面善于化解...
  • 是不是必须在原字符中没出现过?请思考 小结: 三、Manacher原理 假设遍历到位置i,如何操作呢 四、代码及复杂度分析 前缀树 后缀树/后缀数组 后缀树:后缀树,就是把一串字符的所有后缀保存并且压缩的字典树。 相...
  • import numpy import matplotlib.pyplot #画图 %matplotlib inline #使图像出现在这个页面,而不是弹出新的页面 all_values=data_list[0].split(',') #接收datat_list[0],这是第一条记录,根据逗号,将这一长串...
  • 数字电路笔记

    千次阅读 多人点赞 2019-10-11 15:08:17
    本学期在学数字电路,这里是我的笔记。希望能帮助到有需要的人,同时也让自己养成记笔记的习惯。欢迎各位大佬批评指正。 数字电路 教师:** tel:********* 第一章 数字电路概论 1.二进制 LSB和MSB   通常,MSB...
  • 概述:本道作业题是柯汾汲同学的课后练习,分享的...】-1e-物理楼上的说得都不是正确,这里的e应该不是通常说的自然底数我觉得楼主遇到的只是科学计数法中的数1e+10实际上是指1乘以10的10次方例如科学计数法中:234...
  • 价格差异太大了!教你选择CPU型号及常见CPU后缀字母详解2020-05-26 10:30:4012点赞20收藏1评论很多想要买电脑或笔记本的朋友,经常会看到配置介绍信息中,CPU处理器经常都有U、K、H等等之类的后缀字母,很多不太熟悉...
  • OpenCV模板匹配识别图片中的数字

    千次阅读 多人点赞 2021-03-29 00:29:08
    OpenCV模板匹配识别图片中的数字 前言 本博客主要实现利用OpenCV的模板匹配识别图像中的数字,然后把识别出来的数字输出到txt文件中,如果识别失败则输出“读取失败”。 操作环境: OpenCV - 4.1.0 Python 3.8.1 ...
  • 数字证书及CA详解

    万次阅读 多人点赞 2019-08-30 16:11:00
    一般来说,一个数字证书内容可能包括基本数据(版本、序列号) 、所签名对象信息( 签名算法类型、签发者信息、有效期、被签发人、签发的公开密钥)、CA的数字签名,等等。 1.2.1 证书规范 前使用最广泛的...
  • 玩转python的正则表达式|提取字符串中的所有数字

    万次阅读 多人点赞 2019-04-25 11:32:35
    :\w+|\s+)', 'alal ,b6 th al 56\nfPython\t4Ac\65') # 找到所有含'th'的单词(\w包括数字小写字母) >>>[' th ', 'fPython'] 8)如何在正则表达式中包含变量: 2019-4-23 15:24:10 aa='th' ss=re.compile(r'...
  • 超硬核!数据结构学霸笔记,考试面试吹牛就靠它

    万次阅读 多人点赞 2021-03-26 11:11:21
    主要内容: 1、函数传数组就是传了个指针,这个大家都知道,所以传的时候你写arr[],里面写多少,或者不写,都是没关系的,那你后面一定要放一个变量来把数组长度传进来。 2、还有就是,定义:int arr[5],你访问...
  • 在懂得了基础的数字电路原理和常识之后,你需要将该数字电路转换成实际的硬件,即需要用硬件描述语言去把这个电路给实现出来,市面上用的最多的还是Verilog,此时需要学习如何用Verilog硬件描述语言与一个具体的数字...
  • 一、利用clear清除内存时,要用pack函数进行内存整理Matlab在运行大数据时,会出现Outof Memory,在程序中加入clear不需要的变量,能否解决Outof Memory问题。答案是或许可以,或许不可以,原因清除变量或者给该变量...
  • 有人可能觉得,这些题简单了吧,别慌,小白先入门,这些属于 medium 级别的,后面在给几道 hard 级别的。 步骤一、定义数组元素的含义 由于我们的目的是从左上角到右下角,最小路径和是多少,那我们就定义 dp[i] ...
  • Java判断字符串是否为数字的多种方式,你用对了吗

    万次阅读 多人点赞 2021-01-28 23:24:31
    判断一个字符串是否为数字是Java开发中很常见的业务需求,实现这个判断有很多种方式,大体上分为异常处理,正则表达式,数字字符,NumberFormat工具类,外部工具类这四大类,不同类型下的实现方式其实略有不同,那么...
  • 神经网络——实现MNIST数据集的手写数字识别

    千次阅读 多人点赞 2019-01-03 21:49:07
    由于官网下载手写数字的数据集较慢,因此提供便捷下载地址如下 手写数字的数据集MNIST下载:https://download.csdn.net/download/gaoyu1253401563/10891997 数据集包含如下: 一、使用小规模数据集进行神经网络...
  • 数字电路信号逻辑电平标准详解

    万次阅读 2019-05-03 21:00:34
    最基本的单端信号逻辑电平为CMOS、TTL,在此基础上随着电压摆幅的降低,出现LVCMOS、LVTTL等逻辑电平,随着信号速率的提升又出现ECL、PECL、LVPECL、LVDS、CML等差分信号逻辑电平。 而什么是逻辑电平呢,个人的...
  • 牛逼!Java 从入门到精通,超全汇总版

    万次阅读 多人点赞 2021-05-06 19:40:33
    Java 枚举 Enum ,全面理解 Enum,就这一篇文章 反射 ,这篇文章简直好了 学会反射后,我被录取了!(干货) Java 注解 上面所有的内容和思维导图,都可以在这篇文中获取 Java技术核心总结 PDF 下载 上面都是以...
  • TYPEC-CC逻辑芯片-E-MARK数据线-浅析

    千次阅读 多人点赞 2020-07-10 00:18:36
    从USB接口出现以来,相信很多人都遇到这一个问题——不论怎么熟悉你的电脑、USB接口位置,一般都没有一次就能找对方向插上,很概率都要换各边重复插一次!对于个人来讲,特别是有点强迫症的工程师来说,真的难以...
  • Python数据分析系列博客,包括网络爬虫、可视化分析、GIS地图显示、情感分析、舆情分析、主题挖掘、威胁情报溯源、知识图谱、预测预警及AI和NLP应用等。前文分享了疫情相关新闻数据爬取,并进行中文分词处理及文本...
  • 第 3 章 灰度变换与空间滤波 ...  由于处理的是数字量,变换函数的值通常存储在一个一维阵列中,并且从 r 到 s 的映射通过查表得到。对于 8 比特环境,一个包含 T 值的可查阅的表需要有 256 个记录。 ...
  • BP神经网络识别手写数字项目解析及matlab实现

    万次阅读 多人点赞 2018-08-08 10:15:20
    BP神经网络指传统的人工神经网络,相比于卷积...目前, 它日益受到重视, 同时其他学科的发展, 为其提供了更的机会。1986 年, Romelhart 和Mcclelland提出了误差反向传播算法(Error Back Propagation Algorithm) ...
  • 神经网络实现手写数字识别(MNIST)

    万次阅读 多人点赞 2017-05-10 18:20:42
    神经网络实现迷宫游戏的思路,在本篇当中也写如何使用神经网络实现迷宫的,但是研究了一下, 感觉有些麻烦不好弄,所以就选择了比较常见的方式,实现手写数字识别(所谓的MNIST)。 二、人工神经网络简介 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 138,617
精华内容 55,446
关键字:

数字太大后面出现e