C++高精度整数类 iBigInteger的开发[1]

本文介绍了一位作者开发C++高精度整数类iBigInteger的动机和过程,包括数的存储方式、构造函数的实现,以及开发环境和项目搭建。作者计划在后续章节中重载运算符,项目源码已上传至github。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 创作前

1.1 关于C++

C++本身有内建的整数类型,但这些类型能表示的数的范围非常有限,比如 unsigned long long 只能表示0~18446744073709551615的数;

1.2 关于网络上的高精度整数类

CSDN上有很多创作者开发的高精度整数类,但是大多数都是用一个char型数组存储每一位数字(甚至有的用整型数组存储每一位数字),但这种存储方式浪费空间,因为一个char型的变量有256种状态,而你用了能表示256种状态的char只表示了10种状态(

1.3 著名的库

1.3.1 Java BigInteger

BigInteger是JDK里自带的高精度整数类,相信写过Java的小伙伴们都不陌生吧~

1.3.2 libgmp

GMP(The GNU Multiple Precision Arithmetic Library)又叫GNU多精度算术库,是一个提供了很多操作高精度的大整数,浮点数的运算的算术库,主要应用领域是密码学的应用和研究、互联网安全应用、代数系统、计算代数研究等。
GMP是个非常强大的库,提供了很多高效的算法,并且进行了目标机器优化!
所以一般来说我们写的程序运行速度是远远不如GMP的。。。

1.4 创作目的

  1. 提供一个小巧到可以随意嵌入你的课设的较完整的高精度整数实现
  2. 锻炼写项目能力和debug能力
  3. 了解并运用与高精度整数相关的算法

2. 开发环境和项目搭建

2.1 开发环境

操作系统:Windows 11
IDE:CLion 2023.1
代码编写辅助工具:Code Whisperer

2.2 项目搭建

  1. 打开CLion,选择文件-新建-项目,如图所示
    在这里插入图片描述C++标准选择C++23,因为可能会用到C++新标准的东西
  2. 项目面板上任意位置右键,新建 - C++类,类的名称填iBigInteger,别的保持默认值

3. 正式编写代码

3.1 数的存储方式

Java BigInteger中,数以二进制的形式存储在int型数组中,每个int存储大整数的32个二进制位,这样可以保证内存空间被充分利用;
在我们的iBigInteger中,沿用这种做法,但是我们用了更方便功能更全的std::vector代替数组(不用担心std::vector的运行时开销比数组大,因为C++有个零开销抽象原则
另外,用一个bool型变量存储数的符号,即这个数是正数还是负数;
因为有了单独的“符号位”,所以存储在vector中的数都是正的,所以vector的类型参数为unsigned int
综上所述,class iBigInteger中所有的数据成员有:

  1. std::vector ,命名为v
  2. bool,命名为_flag
#ifndef IBIGINTEGER_DEMO_IBIGINTEGER_H
#define IBIGINTEGER_DEMO_IBIGINTEGER_H

#include <vector>

class iBigInteger {
    std::vector<unsigned> v;
    bool _flag;
};

#endif //IBIGINTEGER_DEMO_IBIGINTEGER_H

3.2 构造函数

3.2.1 iBigInteger()

符号为truev中构造一个元素0

iBigInteger::iBigInteger() {
    v.emplace_back(0);
    _flag = true;
}

3.2.2 iBigInteger(int)iBigInteger(unsigned int)

  • unsigned int:因为vector中的元素恰好是unsigned int,所以_flagtruev中构造一个元素,它的值等于参数;
    iBigInteger::iBigInteger(unsigned i) {
        v.emplace_back(i);
        _flag = true;
    }
    
  • int:如果参数大于0,则v中构造一个元素,它的值等于参数,同时_flagtrue;如果参数小于0,则v中构造一个元素,它的值等于参数的相反数,同时_flag=false
    iBigInteger::iBigInteger(int i) {
        v.emplace_back(i > 0 ? i : -i);
        if (i > 0) _flag = true;
        else _flag = false;
    }
    

3.2.3 iBigInteger(long long)iBigInteger(unsigned long long)

  • unsigned long long(设形参名为i):
    1. _flag为true;
    2. 如果参数=0,则v中构造0;
    3. 如果参数不等于0:
    4. v中构造一个数,值等于i&4294967295(即i的最后32位)
    5. 如果i>>32(i的前32位)不等于0,则v中构造第二个数,值等于i>>32
    iBigInteger::iBigInteger(unsigned long long i) {
    	if (i == 0) {
        	v.emplace_back(0);
    	} else {
        	v.emplace_back(i & (unsigned) 0xffffffff);
        	if ((i >> 32) > 0)v.emplace_back(i >> 32);
    	}
    	_flag = true;
    }
    
  • long long:(设形参名为i):
    1. 如果参数=0,_flagtruev中构造0;
    2. 如果参数>0,_flagtrue
      1. v中构造一个数,值等于i&4294967295(即i的最后32位)
      2. 如果i>>32(i的前32位)不等于0,则v中构造第二个数,值等于i>>32
    3. 如果参数<0,_flagfalse
      1. 建立一个临时变量temp,等于i的相反数;
      2. v中构造一个数,值等于temp&4294967295(即i的最后32位)
      3. 如果temp>>32(i的前32位)不等于0,则v中构造第二个数,值等于temp>>32
    iBigInteger::iBigInteger(long long i) {
    	if (i == 0) {
        	v.emplace_back(0);
    	}
    	if (i > 0) {
        	v.emplace_back(i & (unsigned) 0xffffffff);
        	int n = (int) (i >> 32);
        	if (n > 0)v.emplace_back(n);
        	_flag = true;
    	} else {
        	long long temp = -i;
        	v.emplace_back(temp & (unsigned) 0xffffffff);
        	int n = (int) (temp >> 32);
        	if (n > 0)v.emplace_back(n);
        	_flag = false;
    	}
    }
    

3.2.4 iBigInteger(const iBigInteger&)

没啥好说的,v复制一份,_flag复制一份……

iBigInteger::iBigInteger(const iBigInteger &i) {
    v = i.v;
    _flag = i._flag;
}

3.2.5 iBigInteger::iBigInteger(const std::string &s, int radix)

算是挺复杂的一个东西吧……

3.2.5.1 处理字符串

我们的字符串里面可能有一些其他字符,所以要对它进行一些处理,把符合条件的字符转移到新的字符数组内,方便后续的构造;

  1. 先获取字符串长度:
    const char *basicString = s.c_str();
    size_t stringLength = std::strlen(basicString);
    
  2. 判断数的正负:
    判断原字符串的第一个字符:如果为'-',则符号为false;否则符号为true
    char firstChar = basicString[0];
    if (firstChar == '-') {
    	_flag = false;
    } else {
    	_flag = true;
    }
    
  3. 构建新字符数组dealtString
    新字符数组的长度等于原字符串的长度;
    char *dealtString = new char[stringLength];
    for (int i = 0; i < stringLength; i++)
    	dealtString[i] = 0;
    
  4. 辅助方法 accordWithRadix(int c, int radix)
    根据习惯,我们平常使用2~16进制数,所以如果radix不在2和16之间直接构造0返回:
    对于在2~10之间的进制radix,我们用0 ~ radix-1radix个数字来表示数,如用’0’和’1’两个数字表示二进制数;
    而对于11~16之间的进制,我们a表示10b表示11c表示12d表示13e表示14d表示15
    所以对于小等于10的radix,字符在’0’和’0+radix-1范围内(c >= '0' && c < ('0' + radix))即为合法;
    对于大于10小等于16的radix,字符在’0’和’9’范围内或’a’到’a+radix-11范围内(c >= '0' && c <= '9' || c >= 'a' && c < ('a' + radix - 10))合法;
    bool iBigInteger::accordWithRadix(int c, int radix) {
    	if (radix <= 10) {
        	return c >= '0' && c < ('0' + radix);
    	} else if (radix <= 16) {
        	return c >= '0' && c <= '9' || c >= 'a' && c < ('a' + radix - 10);
    	}
    	return false;
    }
    
  5. 转移字符:
    为了方便后续转二进制,我们将字符串内的合法字符倒序塞入dealtString中:
    新建变量dealtLength表示已经塞进dealtString的字符个数;
    当合法字符为’0’~‘9’时,塞入原字符就好;
    当合法字符为’a’~'f’时,做一下特殊处理:
    因为0~9的ASCII为48~57,所以我们希望表示10的’a‘被处理后的字符所对应的ASCII为58,同理’b‘~‘f’分别为59~63,这样的话每个存储在dealtString里面的字符减去’0’就是数字所代表的真实值,便于运算;
    而’a’的ASCII为97,所以当字符为‘a’~‘f’时,把字符减去97再加58(冒号的ASCII为58),[97,102]就被映射到了[58,63]
    最后在dealtString末尾填’\0’;
    int dealtLength = 0;
    for (int i = (int) stringLength - 1; i >= 0; i--) {
        if (accordWithRadix(basicString[i], radix)) {
            if (basicString[i] >= '0' && basicString[i] <= '9') {
                dealtString[dealtLength++] = basicString[i];
            }
            if (basicString[i] >= 'a' && basicString[i] <= 'f') {
                dealtString[dealtLength++] = (char) (basicString[i] - 'a' + ':');
            }
        }
    }
    dealtString[dealtLength] = '\0';
    
3.2.5.2 构建
  1. n进制转二进制的方法:
    点我跳转

  2. 辅助方法check0(const char *str, int length)
    检验在依次除以2后,dealtString里面的所有字符是否均为0
    因为dealtLength是一个字符数组,在传参时会隐式转换为指向首字符的指针,所以还要传一个长度;

    bool iBigInteger::check0(const char *str, int length) {
    	for (int i = length - 1; i >= 0; i--) {
        	if (str[i] != '0') {
            	return false;
        	}
    	}
    	return true;
    }
    
  3. 辅助方法getEvenOrOdd()
    检验除以2的余数;
    没什么好说的,因为字符串是倒置的,所以判断第一个字符所表示的数字除以2的余数就好;

    bool iBigInteger::getEvenOrOdd(const char *str) {
    	return (str[0] - '0') % 2 == 0;
    }
    

    因为 ‘0’=48是偶数,所以可以改为

    bool iBigInteger::getEvenOrOdd(const char *str) {
    	return str[0] % 2 == 0;
    }
    
  4. 辅助方法divideBy2()
    dealtString所表示的数除以2;

    void iBigInteger::divideBy2(char *str, int radix) {
    	bool carry = false;
    	for (int i = (int) strlen(str) - 1; i >= 0; --i) {
        	int temp = str[i] - '0';
        	str[i] = (char) ((str[i] - '0' + (carry ? radix : 0)) / 2 + '0');
        	carry = false;
        	if (temp % 2 == 1) carry = true;
    	}
    }
    
  5. 将二进制数位填充到v中:
    创建变量alreadyInputDigit,表示已经存储在v中的二进制位个数;

    v.emplace_back(0);
    
    long long alreadyInputDigit = 0;
    while (!check0(dealtString, dealtLength)) {
        v[alreadyInputDigit / 32] += (getEvenOrOdd(dealtString) ? 0 : 1) << (alreadyInputDigit % 32);
        alreadyInputDigit++;
        divideBy2(dealtString, radix);
        if (alreadyInputDigit % 32 == 0 && !check0(dealtString, dealtLength)) {
            v.emplace_back(0);
        }
    }
    

下面给出全部的构造函数代码:

iBigInteger::iBigInteger(const std::string &s, int radix) {
    if (radix < 2 || radix > 16) {
        v.emplace_back(0);
        _flag = true;
        return;
    }
    const char *basicString = s.c_str();
    size_t stringLength = std::strlen(basicString);
    char firstChar = basicString[0];
    if (firstChar == '-') {
        _flag = false;
    } else {
        _flag = true;
    }
    char *dealtString = new char[stringLength];
    for (int i = 0; i < stringLength; i++)
        dealtString[i] = 0;
    int dealtLength = 0;
        for (int i = (int) stringLength - 1; i >= 0; i--) {
        if (accordWithRadix(basicString[i], radix)) {
            if (basicString[i] >= '0' && basicString[i] <= '9') {
                dealtString[dealtLength++] = basicString[i];
            }
            if (basicString[i] >= 'a' && basicString[i] <= 'f') {
                dealtString[dealtLength++] = (char) (basicString[i] - 'a' + ':');
            }
        }
    }
    dealtString[dealtLength] = '\0';
    v.emplace_back(0);

    int alreadyInputDigit = 0;
    while (!check0(dealtString, dealtLength)) {
        v[alreadyInputDigit / 32] += (getEvenOrOdd(dealtString) ? 0 : 1) << (alreadyInputDigit % 32);
        alreadyInputDigit++;
        divideBy2(dealtString, radix);
        if (alreadyInputDigit % 32 == 0 && !check0(dealtString, dealtLength)) {
            v.emplace_back(0);
        }
    }
    delete[] dealtString;
}

4. 下期预告

重载运算符:operator+ operator- and so on…

5. github存储库

点击这里
求star!!!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Minsecrus_dreamers

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

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

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

打赏作者

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

抵扣说明:

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

余额充值