C语言实现超级大数的加法、乘法、阶乘

C语言实现超级大数的加法、乘法、阶乘


说明

超级大数的范围在 0~1e9223372036854775807,也就是最大是 99999........99999(中间9223372036854775807个9)

实现了:

  • 大数 + 大数 => 大数
  • 大数 x 个位数 => 大数
  • 大数 x 大数 => 大数
  • int阶乘 =>大数

所有参与运算的大数都要大于0,还没有做小于0的处理。

主要实现原理和算法思路

用数组表示大数,每一位表示一个数字。

加法就是将两个大数按位累加,处理进位就用一个额外的变量保存。

乘法 a x b 就是将b的每一位数字和a相乘(每一位都处理和判断进位问题),对应还要将它扩大10的n次方倍,再将所有的相乘的结果相加构成一个新的大数。就是结果。(和小学手算高位数乘法,列竖式,原理一样)

阶乘:不停的调用大数乘法。

缺点

用数组实现的大数,但是数组里面的每一位都用的一个int来表示一个个位数字,空间浪费严重。

时间复杂度可能依然待优化,计算6000的阶乘需要6秒左右。

使用了数组动态扩容的方法来存储大数,没有考虑到链表这种节点式存储结构

还没有实现负数,减法操作,因此只能表示正数

潜在的bug和漏洞

将字符串转成大数的过程中没有处理非数字字符,如果将含有非数字字符的字符串转化成大数,会出现错误。

大数销毁方法的好像还不干净彻底,经过测试发现循环打印1~1000的阶乘,内存占用会持续增长,累计占用达到了8G左右。

代码使用方法

把下面的代码全部复制到你的项目中去,具体的使用方法可以看代码中最下面的main函数里面的用法。以及每个函数的用法可以看具体的注释

源代码

main.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/**
 * 大数类型
 * 最大值理论上可以是
 * 1e9223372036854775807
 * 99999........99999(中间9223372036854775807个9)
 */
struct bigNum {
    int *arr;  // 位数数组
    long long _arrLen;  // 数组位数的当前最大长度
    long long wei;  // 有多少位 初始是1 表示现在这个数是个位数
} typedef BigNum;

/**
 * 获取一个初始化的大数字对象,值为0
 * @return 大数对象,值为零
 */
BigNum getZero() {
    BigNum res;
    res._arrLen = 2;  // 设定一开始的位数
    res.arr = (int *) malloc(sizeof(int) * res._arrLen);
    res.wei = 1;
    res.arr[0] = 0;
    return res;
}

/**
 * 销毁一个大数对象
 * @param bigNum 大数对象地址
 */
void destroyBigNum(BigNum *bigNum) {
    free(bigNum->arr);
}

/**
 * 给大数对象中的数组扩容,扩容策略是
 * 2, 4, 8, 16 每次空间不够时,都扩充成原来的两倍
 * @param bigNum 需要扩容的大数对象地址
 */
void extendBigNum(BigNum *bigNum) {
    bigNum->_arrLen *= 2;
    bigNum->arr = (int *) realloc(bigNum->arr, sizeof(int) * bigNum->_arrLen);
}

// 整形乘方计算
int myPow(int n, int m) {
    if (m == 0) {
        return 1;
    }
    int res = n;
    for (int i = 1; i < m; i++) {
        res *= n;
    }
    return res;
}

/**
 * 判断m是多少位数
 * @param m
 * @return 如果m是 169 则返回 3
 */
int getIntWei(int m) {
    int n = 0;
    do {
        n++;
        m /= 10;
    } while (m > 0);
    return n;
}

/**
 * 将一个 int 数字转化成 BigNum类型
 * @param int 输入的int类型数
 * @return bigNum 大数对象
 */
BigNum intToBigNum(int integer) {
    int n = getIntWei(integer);
    BigNum res = getZero();
    while (res._arrLen < n) {
        extendBigNum(&res);
    }
    for (int i = 1; i <= n; i++) {
        int dig = (integer % (myPow(10, i))) / myPow(10, (i - 1));
        res.arr[i - 1] = dig;
    }
    res.wei = n;
    return res;
}

/**
 * 将数字字符串转化为大数对象
 * @param string 字符串,如 "1234535543"
 * 字符串只能是纯数字,如果出现其他字符会导致程序bug
 * @return 大数对象
 */
BigNum stringToBigNum(char *string) {
    BigNum res = getZero();
    while (res._arrLen < strlen(string)) {
        extendBigNum(&res);
    }
    for (long long i = 0; i < strlen(string); i++) {
        res.arr[strlen(string) - i - 1] = (int) (string[i]) - 48;
    }
    res.wei = (long long) strlen(string);
    return res;
}

/**
 * 将大数打印成一行打印出来
 * @param bigNum 大数对象地址
 */
void printBigNumLn(BigNum *bigNum) {
    for (long long i = bigNum->wei - 1; i >= 0; i--) {
        printf("%d", bigNum->arr[i]);
    }
    printf("\n");
}

/**
 * 打印这个大数具体的信息
 * 包含了直观表示,内部数组存储结构,位数和当前数组长度。
 * @param bigNum 大数对象地址
 */
void printBigNumObj(BigNum *bigNum) {
    for (long long i = bigNum->wei - 1; i >= 0; i--) {
        printf("%d", bigNum->arr[i]);
    }
    printf(" [");
    for (int i = 0; i < bigNum->wei; i++) {
        printf("%d ", bigNum->arr[i]);
    }
    printf("]");
    printf("arrLen:%lld;wei:%lld\n", bigNum->_arrLen, bigNum->wei);
}

/**
 * 将这个大数对象增加它自己,也就是乘以2倍
 * 输入 231 => 462
 * @param bigN 大数对象地址
 * 无返回值,函数调用直接修改大数对象自身的值
 */
void bigNumAddSelf(BigNum *bigN) {
    // 扩容检测
    if (bigN->arr[bigN->wei - 1] >= 5 && bigN->wei == bigN->_arrLen) {
        extendBigNum(bigN);
    }
    int next = 0;  // 下一位进位现象 0 无进位,1有进位
    for (long long i = 0; i < bigN->wei; i++) {
        int res = bigN->arr[i] * 2 + next;
        if (res < 10) {
            bigN->arr[i] = res;
            next = 0;
        } else {
            bigN->arr[i] = res - 10;
            next = 1;
        }
        // 判断最后是不是多出来一位
    }
    if (next == 1) {
        // printf("up wei\n");
        bigN->arr[bigN->wei] = 1;
        bigN->wei++;
    }
}

/**
 * 把b2里面的内容拷贝到b1里面去
 * 深拷贝
 * copy:  b1 <=== b2
 * @param b1 大数对象地址 接收
 * @param b2 大数对象地址 提供复制
 * 无返回值,函数调用直接修改大数对象 b1 自身的值
 */
void bigNumCopy(BigNum *b1, BigNum *b2) {
    if (b1->_arrLen < b2->_arrLen) {
        // 扩容1
        b1->_arrLen = b2->_arrLen;
        b1->arr = (int *) realloc(b1->arr, sizeof(int) * b1->_arrLen);
    }
    // 先将b1内的数据清零
    for (long long i = 0; i < b1->_arrLen; i++) {
        b1->arr[i] = 0;
    }
    // 一位一位复制
    for (long long i = 0; i < b2->wei; i++) {
        b1->arr[i] = b2->arr[i];
    }
    b1->wei = b2->wei;
}

/**
 * 大数比较大小,
 * @param b1 大数对象地址
 * @param b2 大数对象地址
 * @return 如果 b1 > b2 返回 1 ; 如果 b1 < b2 返回 -1;相等返回 0
 */
int bigNumCmp(BigNum *b1, BigNum *b2) {
    if (b1->wei > b2->wei) {
        return 1;
    } else if (b1->wei < b2->wei) {
        return -1;
    } else {
        // 从高位向低位比较
        for (long long i = b1->wei - 1; i >= 0; i--) {
            if (b1->arr[i] > b2->arr[i]) {
                return 1;
            } else if (b1->arr[i] < b2->arr[i]) {
                return -1;
            }
        }
        return 0;
    }
}

/**
 * 大数相加 有返回值版
 * @param b1 大数对象地址
 * @param b2 大数对象地址
 * @return 返回一个新的大数对象
 */
BigNum bigNumPlus(BigNum *b1, BigNum *b2) {
    // 注意左边的参数的值比右边的要大
    if (bigNumCmp(b1, b2) == -1) {
        BigNum *temp = b1;
        b1 = b2;
        b2 = temp;
    }
    BigNum res = getZero();
    if (b1->wei == b1->_arrLen) {
        extendBigNum(b1); // 最高位刚好达到数组了就直接扩容,不判断了
    }
    bigNumCopy(&res, b1);
    // b2 要扩容到和res一样的水平
    if (b2->_arrLen < res._arrLen) {
        b2->_arrLen = res._arrLen;
        b2->arr = (int *) realloc(b2->arr, sizeof(int) * b2->_arrLen);
        // 多扩容出来的数据要清零
        for (long long i = b2->wei; i < b2->_arrLen; i++) {
            b2->arr[i] = 0;
        }
    }
    while (b2->_arrLen <= res._arrLen) {
        extendBigNum(b2);
    }
    int next = 0;  // 下一位进位现象 0 无进位,1有进位
    for (long long i = 0; i < res.wei; i++) {
        int resNum = b2->arr[i] + res.arr[i] + next;
        if (resNum < 10) {
            res.arr[i] = resNum;
            next = 0;
        } else {
            res.arr[i] = resNum - 10;
            next = 1;
        }
    }
    // 判断最后是不是多出来一位
    if (next == 1) {
        res.arr[res.wei] = 1;
        res.wei++;
    }
    return res;
}

/**
 * 大数相加 无返回值版
 * 把b2加到b1上
 * @param b1 大数对象地址
 * @param b2 大数对象地址
 * 无返回值,直接通过地址修改了b1的值。
 */
void bigNumAdd(BigNum *b1, BigNum *b2) {
    if (b1->wei == b1->_arrLen) {
        extendBigNum(b1); // 最高位刚好达到数组了就直接扩容,不判断了
    }
    int flag = bigNumCmp(b1, b2);
    if (flag == -1) {
        // 后边大,前面小
        while (b1->_arrLen < b2->_arrLen) {
            extendBigNum(b1);
        }
        // 前面0化
        for (long long i = b1->wei; i < b1->_arrLen; i++) {
            b1->arr[i] = 0;
        }
    }
    int next = 0;  // 下一位进位现象 0 无进位,1有进位
    for (long long i = 0; i < b2->wei; i++) {
        int resNum = b1->arr[i] + b2->arr[i] + next;
        if (resNum < 10) {
            b1->arr[i] = resNum;
            next = 0;
        } else {
            b1->arr[i] = resNum - 10;
            next = 1;
        }
    }
    if (b2->wei > b1->wei) {
        b1->wei = b2->wei;
    }
    // 判断最后是不是多出来一位
    if (next == 1) {
        b1->arr[b1->wei]++;
        b1->wei++;
    }
}

/**
 * 将一个大数乘以多少倍数,倍数是个位数(<10)
 * @param bgn 大数对象地址
 * @param a 自觉保证是个位数
 * 无返回值,函数调用直接修改 bgn 的值
 */
void bigNumMulDig(BigNum *bgn, int a) {
    // 乘以0要特殊处理
    if (a == 0) {
        bgn->wei = 1;
        bgn->arr[0] = 0;
        return;
    }
    // 如果这个数最高位刚好顶满了数组,那么可能就会有数组溢出的风险,直接扩容
    if (bgn->wei == bgn->_arrLen) {
        extendBigNum(bgn);
    }
    int next = 0;
    for (long long i = 0; i < bgn->wei; i++) {
        int resNum = bgn->arr[i] * a + next;
        if (resNum < 10) {
            bgn->arr[i] = resNum;
            next = 0;
        } else {
            next = resNum / 10;
            bgn->arr[i] = resNum % 10;
        }
    }
    if (next) {
        bgn->arr[bgn->wei] = next;
        bgn->wei++;
    }
}

/**
 * 将一个大数对象的结尾增加n个零
 * @param bgn 大数对象
 * @param a 增加几个0,如果是1就是扩大十倍,如果是2就是扩大100倍,如果是0就是不扩大
 * 例如:
 *  (大数=1124, a=3)  => 大数变成:1124000
 * 无返回值,函数调用直接修改 bgn 的值
 */
void bigNumAppend0(BigNum *bgn, long long a) {
    if (a <= 0) {
        return;
    }
    while (bgn->wei + a > bgn->_arrLen) {
        extendBigNum(bgn);
    }
    for (long long i = bgn->wei - 1; i >= 0; i--) {
        bgn->arr[i + a] = bgn->arr[i];
    }
    for (int i = 0; i < a; i++) {
        bgn->arr[i] = 0;
    }
    bgn->wei += a;
}

/**
 * 大数乘法 b1 * b2
 * @param b1
 * @param b2
 * @return 返回大数乘法后的结果
 */
BigNum bigNumMul(BigNum *b1, BigNum *b2) {
    BigNum res = getZero();
    // 注意左边的参数的值比右边的要大
    if (bigNumCmp(b1, b2) == -1) {
        BigNum *temp = b1;
        b1 = b2;
        b2 = temp;
    }
    for (long long i = 0; i < b2->wei; i++) {
        // 遍历b2的每一位
        int w = b2->arr[i];
        BigNum newBg = getZero();
        bigNumCopy(&newBg, b1);  // 被乘数b1复制一份
        bigNumMulDig(&newBg, w);
        bigNumAppend0(&newBg, i);
        bigNumAdd(&res, &newBg);
    }
    return res;
}

/**
 * 阶乘
 * @param n 数字
 * @return 阶乘的结果
 */
BigNum jie(int n) {
    BigNum res = getZero();
    res.arr[0] = 1;
    for (int i = 1; i <= n; i++) {
        BigNum bigI = intToBigNum(i);
        res = bigNumMul(&res, &bigI);
        destroyBigNum(&bigI);
    }
    return res;
}

int main() {
    // 大数相加
    BigNum b1 = intToBigNum(1);
    printBigNumObj(&b1);
    BigNum b2 = intToBigNum(95599);
    printBigNumObj(&b2);
    BigNum b3 = bigNumPlus(&b1, &b2);
    printBigNumObj(&b3);

    // 大数乘以个位数
    BigNum bg1 = intToBigNum(11665544);
    bigNumMulDig(&bg1, 6);
    printBigNumObj(&bg1);

    // 大数乘以大数
    BigNum bg3 = intToBigNum(166655466);
    BigNum bg4 = intToBigNum(443335544);
    BigNum bg5 = bigNumMul(&bg3, &bg4);
    printBigNumObj(&bg5);

    // 字符串版 大数乘以大数
    BigNum bgn1 = stringToBigNum("8717829120915684686898899879870");
    BigNum bgn2 = stringToBigNum("1555888888564684684684684888888");
    BigNum bgn3 = bigNumMul(&bgn1, &bgn2);
    printBigNumObj(&bgn3);

    // 遍历输出阶乘结果
    // for (int i = 1; i < 1000; i++) {
    //     BigNum result = jie(i);
    //     printf("!%d\n", i);
    //     // printBigNumObj(&result);
    //     printBigNumLn(&result);
    //     destroyBigNum(&result);
    // }

    // 计算超大阶乘
    BigNum result = jie(6000);
    printBigNumLn(&result);
}
  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值