只是呼吸 发表于 2024-1-5 12:32:13

最优除法步骤试验(附 源代码)

   
       在写大整数的除法过程中,包括本人在内的很多人,因为没有掌握除法的内在联系,走了弯路。
       本人用的除法采用的是\(10^8\)进制,最近在扩展到\(10^9\)进制的过程中,就遇到了困难。心想:难道一个进制就要单独写一个除法程序吗?
重新思考后,发现大整数的加法、减法、乘法(硬乘)都有一个共同的特征规律:

1)        输入数据能采用任意\(10^k\)进制,运算结果不受影响(因为计算机字长限制以及编译器不支持的进制除外)。
2)        运算过程中不需要改变输入数据的结构和大小
       结合加法、减法、乘法(硬乘)的共同特征规律,我们有理由认为,除法的运算规律也应当如此。
       当我们按照上面两个特征规律,结合“笔算除法的最优估商”,写出笔算除法程序时,我们不但得到了商,还同时得到了余数,这个余数是笔算除法的必然结果。
各位爱好者可以查看一下自己写的除法程序,如果余数不是自然得到,而是要添加一些步骤才得到,那么一定是运算过程中“改变了输入数据的结构和大小”。
下面是测试源代码。
说明:源代码做在一个文件中,在debug 模式下编译通过。

只是呼吸 发表于 2024-1-5 12:35:22


#include <stdio.h>
#include <time.h>
#include <string.h>
#include <wtypes.h>

/* 变量长度 */
typedef unsigned int UINT32;                                        /*定义三十二位无符号变量 */
typedef unsigned __int64 UINT64;                                /*定义六十四位无符号变量 */

/* 一些宏定义 */
#defineBASE1000000                        /*大数的基数(10^k进制,可修改)*/
#defineBASE_EXP   6                                                        /*基数的指数 k,与基数同时修改 */

#defineDIVID 3000                                                                /*被除数的数字个数(可修改)*/
#define       DIVIS 2000                                                                /*除数的数字个数(可修改)*/

/* 函数声明,除法函数 */
int DivAbs( UINT32 *dividend, UINT32 *divisor,
                       UINT32 *quotient,UINT32 *remainder,int *CtrL);

int DivHelp(UINT32*Wdiv, UINT32*divisor, UINT32 *qut, int *CtrL);
int MulSubAdd(UINT32 *Wdiv, UINT32 *divisor, int *CtrL);

/* 函数声明,辅助函数 */
int _Output(UINT32 *quotient, UINT32 *remainder, int *CtrL);
int _Input(UINT32 *dividend, UINT32 *divisor, int *CtrL);
void _RandomNumbers(char*a, int lenA);
int _StringToDigit(UINT32 *result, char *str);

/* 函数声明,结果检测(非必须) */
int _resultTest(UINT32 *dividend,UINT32 *divisor,UINT32 *quotient,UINT32 *remainder,int *CtrL);

/* 全局变量 */
static char a={0};                                                        /*被除数a */
static char b={0};                                                        /*除数b */

/////////////////////////////////////////////////////////////////////////////////////////
int        main()
{
                UINT32dividend={0};                                        //dividend        被除数
                UINT32divisor={0};                                        //divisor        除数
                UINT32quotient={0};        //quotient        商
                UINT32remainder={0};                                //remainder        余数
                int CtrL={0};
               
                //CtrL ,        控制数组,                代替结构体传送必须的各个参数
                //CtrL=dividendLEN                传送被除数(数组)的长度       
                //CtrL=divisorLEN                传送除数(数组)的长度
                //CtrL=quotientLEN                传送商(数组)的长度
                //CtrL=remainderLEN                传送余数(数组)的长度
       
                _Input(dividend,divisor,CtrL);

                DivAbs(dividend,divisor,quotient,remainder,CtrL);//// 绝对值除法,求商和余数

                _Output(quotient,remainder,CtrL);

                _resultTest(dividend,divisor,quotient,remainder,CtrL);
      
                return 0;
}


int DivAbs( UINT32 *dividend, UINT32 *divisor,UINT32 *quotient,UINT32 *remainder,int *CtrL)////除法开始。
{
        //数组采用10^k进制,数组的高位对应较大的下标,数组的低位对应较小的下标
       
        //CtrL=dividendLEN                传送被除数(数组)的长度       
        //CtrL=divisorLEN                传送除数(数组)的长度
        //CtrL=quotientLEN                传送商(数组)的长度
        //CtrL=remainderLEN                传送余数(数组)的长度
        int dividendLEN=CtrL;
        int divisorLEN=CtrL;
        int remainderLEN=0;
        int i;

        i=BASE_EXP;
        remainderLEN=1;////临时借用变量remainderLEN
        do
        {
                remainderLEN*=10;
                i--;
        }while(i>0);

        if (remainderLEN !=BASE)
        {
                printf("您输入的 BASE 的零的个数和指数 BASE_EXP 不相等\n");
                exit(1);
        }
       
        if ( BASE>1000000)
        {
                printf("大整数的基数超过10^6,超过计算机的字长,本程序不涉及,请输入的大整数基数<=10^6");
                exit(1);
        }
       
        if ( divisorLEN==1)
        {
                printf("除数为一个limb,属于多精度除以单精度的情况,本程序不涉及。");
                exit(1);
        }

        if (divisorLEN == 0)///若除数为零,
        {
                printf("err:divisor is zeor\n");
                exit(1);
        }
       
        if ((dividendLEN == 0) || (dividendLEN < divisorLEN))///若被除数为零,或被除数小于除数,
        {
                printf("%d",0);
                exit(1);
        }
       
        for(i=0; i<dividendLEN; i++)//拷贝
        {
                remainder=dividend;//////        remainder[]是被除数的拷贝,既是工作数组,又得到余数
        }
        remainderLEN=dividendLEN;
       
        DivHelp(remainder,divisor,quotient,CtrL);//除法

        i = DIVIS/BASE_EXP+6;
        while ((remainder == 0) && (i >= 0))       /// 计算余数长度
        {
                i--;
        }
        CtrL=i+1;
       
        i = dividendLEN-divisorLEN;
        while ((quotient == 0) && (i >= 0))       /// 计算商 的长度
        {
                i--;
        }
        CtrL=i+1;

        return 0;
}

int DivHelp(UINT32*Wdiv, UINT32*divisor, UINT32 *qut, int *CtrL)
{       
        //    Wdiv[];                                        被除数的工作数组,同时也是余数数组
        //   qut[];                                        商
       
        //CtrL                                                传送被除数(数组)的长度       
        //CtrL                                                传送除数(数组)的长度
        //CtrL                                                试商由一确定为零时的判断依据       
        //CtrL                                                随时刷新的被除数最高位下标(用于除法对位),
        //CtrL                                                传送试商的值                                                       
        //CtrL                                                对位计算
       
//        UINT32        TempArray = { 0 };///存储试商与除数相乘所得的结果
        UINT64D = 0;
        UINT64C = 0;
        int divisorMaxLable=CtrL-1;        // 除数数组的最大下标
        int        changeLable=CtrL-1;                // 随时刷新的被除数起始下标,初值 CtrL-1      
        int i;
       
        CtrL = 0;
        CtrL=CtrL-1;                                //随时刷新的被除数起始下标,初值 CtrL-1 做首次计算   
    CtrL = 0;
       
        i = changeLable - divisorMaxLable;                //除法总的循环次数的初始值(从ctrl-ctrl 开始计数)       
        D = UInt32x32To64(divisor, BASE) +(UINT64)divisor;
       
        while (i >= 0)//-----//除法开始
        {
                C = UInt32x32To64(Wdiv, BASE) + (UINT64)Wdiv;////-----C=Wdiv*10^8+Wdiv.
                if ((C < D) || (CtrL == 1))
                {
                        i--;
                        if(i<0)
                        {
                                continue;
                        }
                       
                        C *= BASE;
                        C += (UINT64)Wdiv;//C=Wdiv*10^2k+Wdiv*10^k+Wdiv
                        //three_div_two(Wdiv, divisor,CtrL);
                        CtrL = 1;
                }
                CtrL = (int)(C / D);                                                /// 求试商

                MulSubAdd(Wdiv,divisor,CtrL);                /// 被除数-商*除数
               
                if(CtrL==1)continue;
                qut = CtrL;                                                        ////已定商,赋值给数组

                while ((Wdiv == 0) && (changeLable >= 0))       /// 刷新被除数最高位下标changeLable,用于下一次计算
                {
                        changeLable--;
                }
                i = changeLable - divisorMaxLable;
                CtrL=changeLable;
                CtrL = 0;
        }
}///*/////

int MulSubAdd(UINT32 *Wdiv,UINT32 *divisor,int *CtrL)
{
                //CtrL                                                                传送除数(数组)的长度
                //CtrL                                                                随时刷新的被除数最高位下标(用于除法对位),
                //CtrL                                                                传送试商的值       
                //CtrL                                                                对位计算
       
        UINT32        TempArray = { 0 };///临时存储试商与除数相乘所得的结果
        int divisorMaxLable=CtrL-1;                                ///除数数组的最大下标
        int temp,MM;
        int i;
        UINT64 C=0,D=0;

        temp = 0;
        TempArray = 0;
        TempArray = 0;
        for (i= 0; i <= divisorMaxLable; i++)
        {
                C = UInt32x32To64(divisor, CtrL) + temp;/// 乘法。 CtrL是函数传入的试商。C=商*除数;
                temp = (int)(C / BASE);         
                TempArray = (int)(C - UInt32x32To64(temp, BASE));
        }
        TempArray = temp;               
               
        divisorMaxLable+=1;
        if ((temp==0) && (CtrL==0))
        {
                divisorMaxLable--;
        }

        temp=0;
        MM = CtrL - divisorMaxLable;       
        for (i = 0; i <=divisorMaxLable; i++)////// 减法,被除数-商*除数
        {
                Wdiv -= (TempArray+ temp);
                temp = 0;
                if (Wdiv >=BASE)
                {
                        Wdiv += BASE;
                        temp = 1;
                }
        }

        CtrL = 0;
        if (temp == 1) /// temp==1表示余数是负的。说明试商比真实商大了一。
        {
                temp = 0;
                for (i = 0; i <=divisorMaxLable; i++)////
                {
                        Wdiv += divisor + temp;// 加法,余数为负数时做一次加法
                        temp = 0;
                        if (Wdiv >= BASE)
                        {
                                Wdiv -= BASE;
                                temp = 1;
                        }/////
                }
       
                if (CtrL == 1)///当temp1==1和CtrL==1同时成立时。说明真实的商应该是零。
                {
                        CtrL = 1; //强制让被除数增加一项。
                }
                CtrL -= 1;//余数为负时,试商减去一
        }
        return (CtrL);
}

int _Output(UINT32 *quotient,UINT32 *remainder,int *CtrL)/////输出商和余数
{
        //CtrL=quotientLEN                传送商(数组)的长度
        //CtrL=remainderLEN                传送余数(数组)的长度
        int quotientLEN = CtrL;
        int remainderLEN =CtrL;
        int i;

        printf("被除数\n");///输出被除数
        printf("%s",a);
        printf("\n");
        printf("\n");

        printf("除数\n");///输出除数
        printf("%s",b);
        printf("\n");
        printf("\n");
        printf("\n");
       
        switch (BASE_EXP)///输出商和余数
        {
        case 6:
                printf("所得商\n");
                printf("%d", quotient);
                for (i = quotientLEN - 2; i >= 0; i--)
                {
                        printf("%06d", quotient);
                }
                printf("\n");
                printf("\n");

                printf("所得余数\n");
                printf("%d", remainder);
                for (i = remainderLEN - 2; i >= 0; i--)
                {
                        printf("%06d", remainder);
                }
                break;
        case 5:
                printf("所得商\n");
                printf("%d", quotient);
                for (i = quotientLEN - 2; i >= 0; i--)
                {
                        printf("%05d", quotient);
                }
                printf("\n");
                printf("\n");

                printf("所得余数\n");
                printf("%d", remainder);
                for (i = remainderLEN - 2; i >= 0; i--)
                {
                        printf("%05d", remainder);
                }
                break;
        case 4:
                printf("所得商\n");
                printf("%d", quotient);
                for (i = quotientLEN - 2; i >= 0; i--)
                {
                        printf("%04d", quotient);
                }
                printf("\n");
                printf("\n");

                printf("所得余数\n");
                printf("%d", remainder);
                for (i = remainderLEN - 2; i >= 0; i--)
                {
                        printf("%04d", remainder);
                }
                break;
        case 3:
                printf("所得商\n");
                printf("%d", quotient);
                for (i = quotientLEN - 2; i >= 0; i--)
                {
                        printf("%03d", quotient);
                }
                printf("\n");
                printf("\n");

                printf("所得余数\n");
                printf("%d", remainder);
                for (i = remainderLEN - 2; i >= 0; i--)
                {
                        printf("%03d", remainder);
                }
                break;
        case 2:
                printf("所得商\n");
                printf("%d", quotient);
                for (i = quotientLEN - 2; i >= 0; i--)
                {
                        printf("%02d", quotient);
                }
                printf("\n");
                printf("\n");

                printf("所得余数\n");
                printf("%d", remainder);
                for (i = remainderLEN - 2; i >= 0; i--)
                {
                        printf("%02d", remainder);
                }
                break;
        case 1:
                printf("所得商\n");
                printf("%d", quotient);
                for (i = quotientLEN - 2; i >= 0; i--)
                {
                        printf("%01d", quotient);
                }
                printf("\n");
                printf("\n");

                printf("所得余数\n");
                printf("%d", remainder);
                for (i = remainderLEN - 2; i >= 0; i--)
                {
                        printf("%01d", remainder);
                }
                break;
        }
        printf("\n");
        return 0;
}

int _Input(UINT32 *dividend,UINT32 *divisor,int *CtrL)
{
                //CtrL=dividendLEN        传送被除数(数组)的长度       
                //CtrL=divisorLEN        传送除数(数组)的长度

                _RandomNumbers(a,DIVID);////产生DIVID个数字的被除数
                _RandomNumbers(b,DIVIS);////产生DIVIS个数字的除数
               
                CtrL = _StringToDigit(dividend, a);///将产生的数字字符存入数组中
                CtrL = _StringToDigit(divisor, b);
                return 0;
}

void _RandomNumbers(char*a, int lenA)////产生多位数字的十进制随机数
{
        int i;
        UINT32 seed;

        seed = (UINT32)time(0);
        srand(rand() + seed);
       
                do
                {
                        a = rand() % 10;////do循环确保随机数的第一位数字非零。
                        a += 48;
                } while (a == 48);

                for (i = 1; i < lenA; i++)
                {
                        a = 48 + rand() % 10;
                }
                a = '\0';
       
}////*////

int _StringToDigit(UINT32 *result, char *str)////辅助函数。将字符数组转换为数字数组,按10^k进制存入数组中
{
        int step=0;
        int strInteger;
        int strRemainder;
        int i,j;
       
        strInteger = strlen(str) / BASE_EXP;////将字符数组str按指数长BASE_EXP分组
        strRemainder = strlen(str) % BASE_EXP;

        if (strRemainder == 0)
        {
                for (j=strInteger-1; j>=0; j--, step += BASE_EXP)
                {
                        for (i=step; i <= step+BASE_EXP- 2; i++)
                        {
                                result += str - 48;
                                result *= 10;
                        }
                        result += str - 48;
                }
        }
        else
        {
                for (j=0; j<=strRemainder-2; j++)
                {
                        result += str - 48;
                        result *= 10;
                }
                result += str - 48;

                for (j=strInteger-1; j>=0; j--, strRemainder += BASE_EXP)
                {
                        for (i = strRemainder; i <= strRemainder+BASE_EXP-2; i++)
                        {
                                result += str - 48;
                                result *= 10;
                        }
                        result += str - 48;
                }
        }

        if (strRemainder == 0)
        {
                strInteger--;
        }

        return (strInteger + 1);
}

int _resultTest(UINT32 *dividend,UINT32 *divisor,UINT32 *quotient,UINT32 *remainder,int *CtrL)
{
        //CtrL=dividendLEN                传送被除数(数组)的长度       
        //CtrL=divisorLEN                传送除数(数组)的长度
        //CtrL=quotientLEN                传送商(数组)的长度
        //CtrL=remainderLEN                传送余数(数组)的长度

        int i, j;
        UINT64MIDDLE;
        UINT32 temp,TEMP;
        UINT32 testOne={0};
        UINT32 testTwo={0};

        for (i = 0; i < CtrL; i++)
        {
                temp = 0;
                TEMP = divisor;
                for (j = 0; j < CtrL; j++)
                {
                        MIDDLE = UInt32x32To64(TEMP , quotient) + temp + testOne;
                        temp = (UINT32)(MIDDLE / BASE);
                        testOne = (UINT32)(MIDDLE - UInt32x32To64(BASE , temp));
                }
                testOne = temp;
        }
       
        for (i = 0; i < CtrL; i++)
        {
                testTwo=        dividend-testOne-temp;
                temp=0;
                if(testTwo>=BASE)
                {
                        testTwo+=BASE;
                        temp=1;
                }
        }

        printf("\n");
        j=CtrL-1;
                while(testTwo==remainder)
                {
                        j--;
                        if(j<0)
                        {
                                printf("结果正确");
                        }
                }
          if(j>0)
                {
                        printf("结果错误");
                }
printf("\n");
return 0;
}

nyy 发表于 2024-1-5 12:41:46

只是呼吸 发表于 2024-1-5 12:35


写个屁呀,现在网上有现成的大整数开源算法(我下载后,根本看不懂),比hugecalc还牛逼,只不过你不知道,
我是亲自体验过后才知道

nyy 发表于 2024-1-5 12:48:15

https://github.com/ILW8/gwnum

好像就这个开源算法库,吊打hugecalc,所以真的没必要再浪费时间了

TobyFlenderson 发表于 2024-4-7 10:19:12

无所谓哪个牛逼,最主要的还是学习算法思想吧,理性愉悦嘛。

只是呼吸 发表于 2024-5-2 10:07:09

今天发现,除法的结果是正确的,但是检测函数(int _resultTest())考虑不周到,把正确的结果判错了。
页: [1]
查看完整版本: 最优除法步骤试验(附 源代码)