C++的高精度减法

为什么需要高精度计算

对于 C++ 而言,最大的数据为 long long(64b,8位),对于超过 8B 的数据,C++ 没有对应的数据类型进行表示。所以我们需要知道高精度计算。更详细的解释,可以参考这个网页https://blog.csdn.net/justidle/article/details/104414459

高精度减法计算原理

在读小学时,我们做减法都采用竖式方法,如图 1 所示。 这样,我们可以写出两个整数相减的算法。

我们就可以用 C++ 语言来模拟这个竖式减法的过程。我们可以考虑利用 C++ 的数组来存储对应数据,假设用数组 A 存储被减数 856 的每一位,具体来说就是 A1 存储个位 6,A2 存储十位 5,A3存储百位 8;类似数组 A 的结构,使用数组 B 存储减数 257;类似数组 A 的结构,使用数组 C 来存储对应的差 599。两数相加的结果就如图 2 所示。这样理论上来说,我们就可以计算无限大的数据。如上图 2 所示,下表表示对应的存储方式。

 数组 A数组 B数组 C
[0]679
[1]559
[2]825

总结:利用数组存储,突破存储的限制。每个位置存储 0 ~ 9 之间的数据。

高精度减法实现

思路

1、定义存储数组。

2、被减数和减数确认。由于减法可能出现负数。

3、读入数据到数组中。注意:保证被减数大于减数;倒序存放,也就是个位放在数组下标为 0 的地方。

4、从个位开始模拟竖式减法的过程,完成整个减法。

5、删除前导 0 。所谓前导零,就是出现类似这样数据 01234,这个 0 实际是不需要的。

6、输出减法的结果。倒序输出减法的结果数组 C,因为我们的个位是存储在下标为 0 的地方。

技术细节说明

定义存储数组

根据题目的要求定义数组。这个部分代码如下:

const int MAXN = 1e5+4; //根据题目的最大值。+4为了防止A+B出现进位
char s1[MAXN] = {};//存储字符串
char s2[MAXN] = {};//存储字符串
char tmp[MAXN] = {};//交换用字符串
int a[MAXN] = {};//存储加数A
int b[MAXN] = {};//存储加数B
int c[MAXN] = {};//存储和B

被减数和减数确认

由于减法可能出现负数,如 3-5=-2,我们在计算的时候,实际是使用 5-3=2,最后在结果前面添加负号。如果出现被减数小于减数的情况,要将两者颠倒。

    scanf("%s %s", s1, s2);//读入字符串
    int lena = strlen(s1);
    int lenb = strlen(s2);
    //判断最终的结果符号
    if ((lena<lenb) || (lena==lenb && strcmp(s1,s2)<0)) {
        //被减数小于减数,结果为负数
        printf("-");
        //交换数据
        strcpy(tmp, s1);
        strcpy(s1, s2);
        strcpy(s2, tmp);
        //更新长度数据
        lena = strlen(s1);
        lenb = strlen(s2);
    }

读入数据到数组

利用读入字符串的方法读入数据,再倒序写入到对应的数组中。这个部分代码如下:

    //将字符串写入到数组A中
    for (int i=0; i<lena; i++) {
        //倒序写入
        a[i] = s1[lena-i-1] - '0';
    }

    //将字符串写入到数组B中
    for (int i=0; i<lenb; i++) {
        //倒序写入
        b[i] = s2[lenb-i-1] - '0';
    }

模拟竖式减法

有两个技术细节:如何判断发生借位。这个部分代码如下:

    //模拟竖式减法
    for (int i=0; i<lena; i++) {
        if (a[i]<b[i]) {
            //有借位
            a[i+1]--;
            a[i] += 10;
        }
        c[i] = a[i] - b[i];
    }

删除前导零

因为减法运算可能会出现最高位为零,所以我们需要判断是否需要删除前导零。这个部分代码如下:

    //删除前导零
    for (int i=lena-1; i>=0; i--) {
        //因为我们是从索引 0 开始,所以最高位是保存在 len-1
        if (0==c[i] && lena>1) {
            //注意要有 lena>1 这个条件。考虑特殊情况,加法结果为 00,我们实际要输出 0。
            lena--;
        } else {
            //第一个不是零的最高位,结束删除
            break;
        }
    }

输出计算结果

采用倒序的方式输出,因为我们数据保存是倒序结构,也就是低位在前。

    //逆序打印输出
    for (int i=lena-1; i>=0; i--) {
        printf("%d", c[i]);
    }
    printf("\n");    

例题和 AC 代码

题目

题目链接

一本通 OJ:http://ybt.ssoier.cn:8088/problem_show.php?pid=1169

我自己 OJ:http://47.110.135.197/problem.php?id=1216

题目描述

求两个大的正整数相减的差。

输入

共 2 行,第 1 行是被减数 a,第 2 行是减数 b,不保证 a > b。每个大整数不超过 10005 位。

输出

一行,即所求的差。

样例输入

9999999999999999999999999999999999999
9999999999999

样例输出

9999999999999999999999990000000000000

分析

题目告诉我们不超过 200 位,也就是 MAXN = 10005+4。

AC 代码

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 1e4+4; //根据题目的最大值。+4为了防止A+B出现进位
char s1[MAXN] = {};//存储字符串
char s2[MAXN] = {};//存储字符串
char tmp[MAXN] = {};//交换用字符串
int a[MAXN] = {};//存储加数A
int b[MAXN] = {};//存储加数B
int c[MAXN] = {};//存储和B

int main() {
    scanf("%s %s", s1, s2);//读入字符串
    
    int lena = strlen(s1);
    int lenb = strlen(s2);
    //判断最终的结果符号
    if ((lena<lenb) || (lena==lenb && strcmp(s1,s2)<0)) {
        //被减数小于减数,结果为负数
        printf("-");
        //交换数据
        strcpy(tmp, s1);
        strcpy(s1, s2);
        strcpy(s2, tmp);
        //更新长度数据
        lena = strlen(s1);
        lenb = strlen(s2);
    }
    
    //将字符串写入到数组A中
    for (int i=0; i<lena; i++) {
        //倒序写入
        a[i] = s1[lena-i-1] - '0';
    }

    //将字符串写入到数组B中
    for (int i=0; i<lenb; i++) {
        //倒序写入
        b[i] = s2[lenb-i-1] - '0';
    }

    //模拟竖式减法
    for (int i=0; i<lena; i++) {
        if (a[i]<b[i]) {
            //有借位
            a[i+1]--;
            a[i] += 10;
        }
        c[i] = a[i] - b[i];
    }

    //删除前导零
    for (int i=lena-1; i>=0; i--) {
        //因为我们是从索引 0 开始,所以最高位是保存在 len-1
        if (0==c[i] && lena>1) {
            //注意要有 lena>1 这个条件。考虑特殊情况,加法结果为 00,我们实际要输出 0。
            lena--;
        } else {
            //第一个不是零的最高位,结束删除
            break;
        }
    }

    //逆序打印输出
    for (int i=lena-1; i>=0; i--) {
        printf("%d", c[i]);
    }
    printf("\n");
    
    return 0;
}
  • 26
    点赞
  • 75
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论
对于浮点数的高精度减法,可以将浮点数转化为字符串,然后按照高精度减法的思路进行计算,最后再将结果转化为浮点数。 以下是一个使用字符串实现高精度减法的例子: ```c++ #include <iostream> #include <string> #include <algorithm> // for reverse() using namespace std; string float_sub(string a, string b) { int lena = a.length(), lenb = b.length(); int dotPosa = a.find('.'), dotPosb = b.find('.'); int lenDeca = (dotPosa == -1 ? 0 : lena - dotPosa - 1); int lenDecb = (dotPosb == -1 ? 0 : lenb - dotPosb - 1); // 补足小数点后的0 if (lenDeca < lenDecb) { a.append(lenDecb - lenDeca, '0'); } else if (lenDecb < lenDeca) { b.append(lenDeca - lenDecb, '0'); } // 补足小数点前的0 if (dotPosa < dotPosb) { a.insert(0, dotPosb - dotPosa, '0'); } else if (dotPosb < dotPosa) { b.insert(0, dotPosa - dotPosb, '0'); } // 对齐后的长度 int len = max(lena, lenb); // 去掉小数点 a.erase(dotPosa, 1); b.erase(dotPosb, 1); // 翻转字符串方便计算 reverse(a.begin(), a.end()); reverse(b.begin(), b.end()); // 计算 string c(len, '0'); int carry = 0; for (int i = 0; i < len; i++) { int numa = (i < lena ? a[i] - '0' : 0); int numb = (i < lenb ? b[i] - '0' : 0); int numc = numa - numb - carry; if (numc < 0) { numc += 10; carry = 1; } else { carry = 0; } c[i] = numc + '0'; } // 去掉前导0 while (c.length() > 1 && c.back() == '0') { c.pop_back(); } // 加上小数点 int lenDec = max(lenDeca, lenDecb); if (lenDec > 0) { c.insert(len - lenDec, "."); } // 翻转回来 reverse(c.begin(), c.end()); return c; } int main() { string a = "123.456"; string b = "78.9"; string c = float_sub(a, b); cout << c << endl; return 0; } ``` 这个例子中,我们首先将小数点后的0补足,然后将小数点前的0也补足,接着去掉小数点,翻转字符串,进行高精度减法计算,再去掉前导0,加上小数点,最后翻转回来,得到最终结果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力的老周

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值