BigInt

BigInt

大整数静态库顶多算一个小项目,其目的也是很简单——拿来练手,也算给自己涨点信心。

不过既然做完了,也应按照习惯总结一下开发经验,复盘一下整个开发流程,总结一下错误什么的。也算是让该项目有一个完整的结局。

Motivation

一开始只是做了一道DP题,结果数据过大,需要用到大整数类进行运算。本来挺简单的一件事,结果一下子给我卡住了,我TM学了这么久的计算机一个大整数就给我卡住了?实在是不能忍,一气之下搞了个大整数库。

说实话,纯纯的造轮子。

Interface Design

即便是造轮子,既然决定了,咱就好好造。

在开发一个库之前,我需要明确一个目的——其他人对该库的期望是如何的,即我如果是库的使用者,我希望库应该有什么样的行为。

  • 大整数库理应是基本整数类型的延伸,大整数的引入就是解决整数类型的限制问题,所以理所应当的,大整数应该表现的应该和普通整数类型别无二致。
  • 大整数应该兼容所有整数类型,任何能用整数类型表示的地方都可以替换为大整数。
  • 我不希望大整数类包含任何的工具函数,毕竟基本整数类型不会通过.来调用一些函数。不过为大整数类适配的工具函数又是必要的。

以上三点,基本能确定我心目中的大整数类应该是什么样的——它应该是完整的(即和基本数据类型拥有一样的运算性质),简洁的(不包含任何工具函数),兼容的(任何整数类型都可替换成大整数,即隐式类型转换)

当然,在开发过程中以上三点都或多或少进行了让步,这就跟我的实现方法有关了。

Development Experience

首先就是面临着大整数数据结构的选择,一般大整数有两种表示方法——unsigned charstring,第一种则是用unsigned char数组代表任意长度整数的二进制表示法,第二种则是以字符串的形式存储任意长度的整数。这两种方法各有利弊。我选择是第二种,它对流输入输出有很好的适配性,因为它本身就是字符串,第二是第二种数据结构能够应用一些方便的算法,比如fft。

当然,它的缺点也很明显,因为失去了二进制表示法,对于截断(隐式转换到其他数据类型),移位,按位逻辑等操作就变得非常麻烦,以至于我不得不放弃以上功能。

接下来,我还是阐述一些开发中遇到的问题吧。

一般来说,都是用BigInt(long long)来兼容其他整数类型,以便于隐式转到大整数。但是对于unsigned long long来说,该方法就变得不可取。如果我们对unsigned long long类型进行重载,就会出现函数解析问题,因为别的类型比如int,char可以同时转换到long longunsigned long long,编译器不会知道你究竟想调用哪个构造函数。

所以对于long longunsigned long long来说,函数重载这条路走不通。此外,对于浮点类型,我希望大整数能够基本数据类型一样,对浮点数据进行截断,保留整数部分。

好吧,其实重载还是能走通的,只要把每一种数据类型都重载一遍就好了。

不过为了省去麻烦,我通过模板函数实现了该功能。

通过元编程技巧判断参数是有符号类型还是无符号类型或是浮点类型,然后分别执行不同的算法流程(其实差不多)。

不过使用了模板就要记得,这时用户无论以什么类型生成大整数,编译器都会尝试为其生成对应的构造函数,这与我们预期行为不一致。至少我们应该允许那些能够转换成整数的自定义类生成对应的构造函数,所以我们还需要利用类型萃取判断其是否能转换到整数,准确来说是double

解决了别的类型转换到大整数,就需要处理大整数转换到基本整数类型了。

此时,构造函数和类型转换运算符至少其一应该使用explicit关键字,因为如果同时出现基本整数类型可隐式转换到大整数和大整数可以隐式转换到基本类型的情况,编译器还会出现不知道用哪一个函数的问题——即内置的运算符和你定义的重载运算符发生冲突。

鉴于我的大整数对于截断操作不是很友好,同时将大整数转换到基本整数可能出现数据丢失问题,将类型转换运算符声明为explicit是合理的。

此外,工具函数通过一个工具类实现,将工具类声明为大整数类的友元,工具类就能够从外部实现了。

Github

https://github.com/Thetranslater/BigInt

posted @ 2022-11-12 20:22  ᴮᴱˢᵀ  阅读(91)  评论(0)    收藏  举报