(C++)大数计算问题

发布时间:2024年01月18日

一、实验目的、内容

大数是超过整数表示范围的整数,针对正整数运算,定义一个大数类,并编写两个大数类对象的加法和减法函数。

二、实验程序设计及结构

1.需求分析

bigu,内含p(unsigned char*)、n(size_t)数据成员及构造函数、析构函数等成员函数。

变量

bigu类型数据ab,用于测试数据。

函数

主函数、运算符重载函数及bigu类的成员函数。

2.设计结构或流程图

  1. 进行ab的输入。(调用构造函数及istream& operator>>(istream& c, bigu& x)
    1. 从输入的第一个数字字符开始,定义unsigned short临时数组(看作256进制数)。
    2. 每输入一个数字字符,每个数组元素乘10。
    3. 遍历临时数组,将超过255的元素执行进位操作;若数组长度不够,重新定义数组并拷贝。
    4. 不断输入直到遇到非数字字符为止。
    5. 将临时数组的数据拷贝进unsigned char数组并存储。
  2. 进行ab的加法并输出。(调用bigu operator+(const bigu& x, const bigu& y)ostream& operator<<(ostream& c, const bigu& x)
    1. 加法运算
      1. 根据整数运算进位的性质,和的元素个数为max(x.n,y.n)max(x.n,y.n)+1,故定义max(x.n,y.n)+1长度的临时数组。
      2. x.ny.n的大小分类讨论。
      3. 将临时数组初始化为xy各位元素之和。
      4. 遍历临时数组,对值超过255的执行进位操作。
      5. 将临时数组的数据拷贝进unsigned char数组并存储。
    2. 输出
      1. 定义临时数组。
      2. 从最高位开始,将超过9的数据进行十进制展开。
      3. 每次加新数之前将原来的所有元素乘256并对超过9的数进行进位。
      4. 输出并释放临时数组。
  3. 比较ab的大小,作差后输出。(调用bool operator>(const bigu& x, const bigu& y)bigu operator-(const bigu& x, const bigu& y)ostream& operator<<(ostream& c, const bigu& x)
    1. 大于运算
      1. 若两者元素个数不同,直接返回结果。
      2. 逐位比较,直到两者不相等为止。
    2. 减法运算(与加法类似)

三、设计过程

#include <iostream>
using namespace std;
class bigu
{
public:
    unsigned char *p;                                  // 指向正整数数组
    size_t n;                                          // 数组长度
    bigu() { *(p = new unsigned char[n = 1]) = '\0'; } // 默认构造函数:初始化为0
    // 带参数构造(无符号整型)
    bigu(unsigned long long x)
    {
        unsigned char a[8], *q(a), *t(q);
        *q = x;
        while (x >>= 8)
            *++q = x; // 逐字节读取
        *(p = new unsigned char[n = ++q - t]) = *t;
        while (++t < q)
            *++p = *t;
        ++(p -= n);
    }
    // 为了防止浅拷贝的问题,重写拷贝构造函数
    bigu(const bigu &x)
    {
        delete[] p; // 防止内存泄漏
        const unsigned char *q(x.p);
        *(p = new unsigned char[n = x.n]) = *q;
        while (--n)
            *++p = *++q;
        ++(p -= n = x.n);
    }
    ~bigu() { delete[] p; } // 析构函数
    // 为了加快效率、避免拷贝的有参构造函数
    bigu(unsigned char *a, size_t b)
    {
        p = a;
        n = b;
    }
};
// 重载关系运算符
bool operator>(const bigu &x, const bigu &y)
{
    if (x.n > y.n)
        return true;
    if (y.n > x.n)
        return false;
    const unsigned char *p(x.p + x.n), *q(y.p + y.n);
    do
        if (*--p > *--q)
            return true;
        else if (*p < *q)
            return false;
    while (p > x.p);
    return false;
}
// 重载加法运算符
bigu operator+(const bigu &x, const bigu &y)
{
    const unsigned char *p(x.p), *q(y.p); // 读取数据
    // 考虑新数组大小将会是x,y中较大的那个或是较大的加1
    // 故分类讨论
    if (y.n > x.n)
    {
        size_t n(x.n);
        unsigned short *a(new unsigned short[y.n + 1]), *t(a + y.n); // 定义临时数组
        // 向临时数组输入数据
        *a = *p + *q;
        while (--n)
            *++a = *++p + *++q;
        while (++a < t)
            *a = *++q;
        // 处理首位
        if (*(a -= (n = y.n)--) > 255)
        {
            *(a + 1) += *a / 256;
            *a %= 256;
        }
        // 处理头尾之间
        while (--n)
            if (*++a > 255)
            {
                *(a + 1) += *a / 256;
                *a %= 256;
            }
        // 处理最高位
        if (*++a > 255) // 超过了unsigned char表示的范围
        {
            // 拷贝、释放并返回
            unsigned char *u(new unsigned char[y.n + 1] + (n = y.n));
            *u = *a / 256;
            *a %= 256;
            do
                *--u = *--t;
            while (--n);
            delete[] t;
            return bigu(u, y.n + 1);
        }
        // 未超过unsigned char表示的范围
        unsigned char *u(new unsigned char[n = y.n] + y.n);
        *--u = *a;
        while (--n)
            *--u = *--a;
        delete[] a;
        return bigu(u, y.n);
    }
    // 此时x的位数大于等于y的位数
    // 操作过程同理
    if (x.n == 1)
        return bigu(*p + *q);
    size_t n(y.n);
    unsigned short *a(new unsigned short[x.n + 1]), *t(a + x.n);
    *a = *p + *q;
    while (--n)
        *++a = *++p + *++q;
    while (++a < t)
        *a = *++p;
    if (*(a -= (n = x.n)--) > 255)
    {
        *(a + 1) += *a / 256;
        *a %= 256;
    }
    while (--n)
        if (*++a > 255)
        {
            *(a + 1) += *a / 256;
            *a %= 256;
        }
    if (*++a > 255)
    {
        unsigned char *u(new unsigned char[x.n + 1] + (n = x.n));
        *u = *a / 256;
        *a %= 256;
        do
            *--u = *--t;
        while (--n);
        delete[] t;
        return bigu(u, x.n + 1);
    }
    unsigned char *u(new unsigned char[n = x.n] + x.n);
    *--u = *a;
    while (--n)
        *--u = *--a;
    delete[] a;
    return bigu(u, x.n);
}
// 重载减法运算符
// 默认x>=y
bigu operator-(const bigu &x, const bigu &y)
{
    if (x.n == 1) // 由于y<=x,只需要1字节内存空间
    {
        unsigned char *a(new unsigned char[1]);
        *a = *x.p - *y.p;
        return bigu(a, 1);
    }
    const unsigned char *p(x.p), *q(y.p);             // 读取
    short *a(new short[x.n]), *t(a), *r(a + x.n - 1); // 定义临时数组
    *t = *p - *q;
    size_t n(y.n);
    while (--n)
        *++t = short(*++p) - short(*++q); // 由于p,q为无符号指针,需要进行强制类型转换
    while (++t <= r)
        *t = *++p;
    if (*(t = a) < 0)
    {
        --*(t + 1);
        *t += 256;
    } // 对首位进行退位操作
    while (++t < r)
        if (*t < 0)
        {
            *(t + 1) += *t / 256 - 1;
            (*t %= 256) += 256;
        } // 对其余各位进行退位操作
    ++t;
    while (!*--t)
        ; // 找到首个大于0的高位
    // 拷贝数据进新的合适大小的数组
    unsigned char *u(new unsigned char[n = ++t - a] + n);
    do
        *--u = *--t;
    while (t > a);
    delete[] a; // 防止内存泄漏
    return bigu(u, n);
}
// 重载右移运算符,用以输入
istream &operator>>(istream &c, bigu &x)
{
    delete[] x.p; // 释放原来的数组,防止内存泄漏
    char s;       // 由于不知道大数的上限,采用逐字节读取方式
    do
        c.get(s);
    while (s == ' ' || s == '\n' || s == '\t'); // 排除空格、水平制表符和换行
    if (s < '0' || s > '9')                     // 如果输入的不是数字,按0处理
    {
        *(x.p = new unsigned char[x.n = 1]) = 0;
        return c;
    }
    unsigned short *a(new unsigned short[x.n = 10]), *b(a), *p; // 定义临时数组,用范围比unsigned char稍大的类型进行临时存储,a指向最低位,b指向目前的最高位
    *a = s - '0';                                               // 存储第一位
    c.get(s);
    while (s >= '0' && s <= '9')
    {
        (*(p = a) *= 10) += s - '0'; // 加上新数并乘10(位权)
        while (++p <= b)
            *p *= 10;       // 每一位乘10
        if (*(p = a) > 255) // 最低位超过了unsigned char的范围
            if (p == b)     // 只有1位的情况
            {
                *++b = *p / 256; // 改变尾指针
                *p %= 256;
                goto F; // 直接进行下一位的读取
            }
            else
            {
                *(p + 1) += *p / 256;
                *p %= 256;
            }
        else if (p == b)
            goto F;     // 只有1位而且不需要处理
        while (++p < b) // 处理头尾之间的数据
            if (*p > 255)
            {
                *(p + 1) += *p / 256;
                *p %= 256;
            }
        if (*p > 255)           // 最高位需要进位
            if (++b - a == x.n) // 事先的上限不够用,重新申请更大的存储空间并拷贝
            {
                unsigned short *u(a), *v(new unsigned short[x.n += 10]);
                *(p = v) = *a;
                while (++a < b)
                    *++p = *a;
                delete[] u;
                a = v;
                *(b = p + 1) = *p / 256;
                *p %= 256;
            }
            else // 够用,直接处理
            {
                *b = *p / 256;
                *p %= 256;
            }
    F:
        c.get(s);
    }
    // 将处理结果拷贝进unsigned char数组并释放临时数组
    unsigned char *q(x.p = new unsigned char[x.n = ++b - (p = a)]);
    *q = *p;
    while (++p < b)
        *++q = *p;
    delete[] a;
    return c;
}
// 重载左移运算符,用以输出
ostream &operator<<(ostream &c, const bigu &x)
{
    size_t m(10);                                           // 先假定一个上限
    unsigned short *t(new unsigned short[m]), *a(t), *b(t); // 用范围比unsigned char稍大的类型进行临时存储,a指向最低位,b指向目前的最高位
    const unsigned char *q(x.p + x.n);                      // 用于读取数据的指针
    if ((*t = *--q) > 9)
        do
        {
            *++b = *t / 10;
            *t %= 10;
        } while (*++t > 9); // 如果最高位大于等于10
    while (--q >= x.p)
    {
        (*(t = a) *= 256) += *q; // 新位加上原来的乘上位权(256)
        while (++t <= b)
            *t *= 256; // 各高位乘位权(256)
        t = a;
        while (t != b) // 处理头尾之间的位,使它们都小于10
        {
            *(t + 1) += *t / 10;
            *t %= 10;
            ++t;
        }
        if (*t > 9) // 如果最高位需要处理
        {
            if (false)
            F:
            { // 预先的上限不够用,重新申请更大的存储空间
                unsigned short *u(t = new unsigned short[m += 30]), *v(a);
                *u = *a;
                while (++a < b)
                    *++u = *a;
                delete[] v;
                a = t;
                t = b = u;
            }
                // 处理最高位
                do
                {
                    if (++b - a == m)
                        goto F;
                    *b = *t / 10;
                    *t++ %= 10;
                } while (*b > 9);
        }
    }
    c << *b; // 输出最高位
    while (--b >= a)
        c << *b; // 输出其余各位
    delete[] a;  // 释放临时数组
    return c;
}
// 主函数
// 用以测试数据
int main()
{
    bigu a, b;
    cout << "请输入大数a:\n";
    cin >> a;
    cout << "请输入大数b:\n";
    cin >> b;
    cout << "a+b=" << a + b << endl;
    if (a > b)
        cout << "a-b=" << a - b << endl;
    else
        cout << "b-a=" << b - a << endl;
    system("pause");
    return 0;
}

四、测试分析

第一组

在这里插入图片描述

第二组

在这里插入图片描述

实验中出现的bug及解决方案

实验中出现的bug:忽略了大数的进位、退位问题。

解决方案:定义更大的临时数据数组并逐位处理。

五、设计的特点和结果

设计采用的数据结构为数组。为了表示大数,把unsigned char数组看作256进制的数,而不是十进制数,节约了内存空间,并利用动态内存分配技术,解决了大数问题。在实验过程中,我原本是打算逐个扩充数组(每次数组长度加1),后来发现运行的效率很低,就采用了和标准库类型vector类似的内存分配方法,每次多扩充29位,解决了效率低的问题。

文章来源:https://blog.csdn.net/m0_61219098/article/details/135494036
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。