首先,需要指出的是,java内的BigInteger类很好用,功能相当全面。像加减乘这样的基础操作都是可以胜任的。所以,在java中其实没有手动实现高精度的必要。
所以写下这篇博客的初衷并不是放在实际情况下使用,而是当做高精度算法的练习,也同时练习java编程能力。
克隆接口Cloneable,重写clone方法。便于复制
可比较接口Comparable,实现CompareTo方法,便于比较排序。
(可搭配各种数据结构使用)
数组number,用于记录每一位,需要指出的是,该类仅支持十进制的运算。
数组长度len,用于表示数组中数字长度,便于统计和计算
符号变量sign,用于作为符号项参与运算。取值为1或-1.
提供两种构造方法,用于初始化
传入整数,按位倒序转化进数组
传入字符串,检索符号,倒序存入数组
打印方法void print,以带符号字符串的形式打印。
高精度加法void add,传入大整数,加到对象上
高精度减法void minus,传入大整数,计算减法
高精度乘法void multiply,传入大整数,计算乘法
克隆方法BigNumber clone,深克隆对象,返回克隆对象
比较方法int compareTo,与传入大整数进行比较,大1,小-1,相等0
整理数组void sort,传入计算后的长度,计算符号并整理数组
public class BigNumber implements Cloneable,Comparable<BigNumber>
{
short[] number = new short[50000];
int len = 0;
int sign;
BigNumber(int num)
{
if(num < 0)
{
num = -num;
this.sign = -1;
}
else
{
this.sign = 1;
}
while(num != 0)
{
this.number[++len] = (short)(num % 10);
num /= 10;
}
}
BigNumber(String num)
{
this.len = num.length();
this.sign = 1;
for(int i = this.len - 1;i > 0;i--)
{
number[this.len - i] = (short)(num.charAt(i) - '0');
}
if(num.charAt(0) == '-')
{
this.len -= 1;
this.sign = -1;
}
else
{
this.number[this.len] = (short)(num.charAt(0) - '0');
}
}
public BigNumber clone()
{
BigNumber num = null;
try {
num = (BigNumber) super.clone();
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
num.number = number.clone();
return num;
}
public void print()
{
if(sign == -1)
{
System.out.print("-");
}
for(int i = len;i >= 1;i--)
{
System.out.print(this.number[i]);
}
System.out.println();
}
private void sort(int len)
{
while(this.number[len + 1] != 0)
{
len++;
}
if(this.number[len] < 0)
{
this.sign = -this.sign;
for(int i = 1;i < len;i++)
{
if(this.number[i] == 0)
{
continue;
}
this.number[i] = (short)(10 - this.number[i]);
this.number[i + 1]++;
}
this.number[len] *= -1;
}
while(len > 1 &&this.number[len] == 0)
{
len--;
}
this.len = len;
}
public void add(BigNumber num)
{
int len = Math.max(this.len, num.len);
for(int i = 1;i <= len ;i++)
{
this.number[i] = (short)((this.number[i] * this.sign) + (num.number[i] * num.sign));
this.number[i + 1] += this.number[i] / 10;
this.number[i] %= 10;
if(this.number[i] < 0)
{
this.number[i + 1] --;
this.number[i] += 10;
}
}
sort(len);
}
public void minus(BigNumber num)
{
int len = Math.max(this.len, num.len);
for(int i = 1;i <= len ;i++)
{
this.number[i] = (short)((this.number[i] * this.sign) - (num.number[i] * num.sign));
this.number[i + 1] += this.number[i] / 10;
this.number[i] %= 10;
if(this.number[i] < 0)
{
this.number[i + 1] --;
this.number[i] += 10;
}
}
sort(len);
}
public void multiply(BigNumber num)
{
BigNumber ans = new BigNumber(0);
for(int i = 1;i <= this.len;i++)
{
for(int j = 1;j <= num.len;j++)
{
ans.number[i + j - 1] += (short)(this.number[i] * num.number[j] * this.sign * num.sign * ans.sign);
ans.number[i + j] += ans.number[i + j - 1] / 10;
ans.number[i + j - 1] %= 10;
if(ans.number[i + j - 1] < 0)
{
ans.number[i + j]--;
ans.number[i + j - 1] += 10;
}
}
ans.sort(i + num.len);
}
number = ans.number.clone();
sign = ans.sign;
len = ans.len;
}
@Override
public int compareTo(BigNumber o) {
o.minus(this);
if(o.len == 0)
{
return 0;
}
return o.sign == 1 ? -1 : 1;
}
}
首先,为了处理负数的相加减。允许数组在计算时出现负数,并且边计算边整理的。
基本思路是尾对齐并按位直接相加减。由于在数组中数据逆序存放,所以尾对齐是默认设置。所以计算时仅需要从第一位开始至两者长度中较大者循环即可。
对每一位,取每一位的绝对值乘上该数符号作为真实值进行加减。算完后,进行整理,主要是进位和借位,保证每一位的值落在0-9上。
对于首位,允许出现负数,当首位为负数是,表示该数符号与当前符号相反,交于整理方法进行整理,传递数组长度为两者长度较长者。
对计算完成的数进行整理,具体分为三步
第一步,从当前位开始找到最高位。方法就是从向后寻找第一个0.(当然这样的处理当当前位与最高位之间存在零的时候会出现bug,但其作为内部方法,通过在调用前对长度的准确把握,还是可以避开这个bug的。)
第二步,根据最高位整理数组并更新符号。 主要针对的,是当数组中的值为负的情况。由于在计算处理时,采用的是边算边进位。若出现负数,最终一定会出现一个负的最高位,其余位为正。当检测到最高位为负数时,翻转符号,最高位取相反数,从最低位至次高位开始翻转符号,并借位归正。例如 -1 2 9 符号为正, 整理后变为 7 1 ,符号为负。
第三步,从最高位起,计算真实长度。由于负数情况可能导致位数减少,该操作即为解决这种情况。实现方法为从最高位起,找到首个不为零的位。并处理答案为0的情况。并修正长度和符号成员变量。
乘法的高精度不能直接在原数组上进行,故定义答案变量ans。
模拟乘法竖式,每一次,取以为与另一因数相乘,取该位下标为偏移量,统计在答案数组中,并进行整理。
需要注意的是,在向答案数组中累加时,需要额外乘上答案变量的符号以确保符号统一。