一、说三道四
用代码实现简单的加减乘除运算会不会?只要你是个coder,我想这个答案都是肯定的吧!
但是,今天我想说的是,当我们的运算遇到了大数,用原本的int、long、float、double数值类型
都无法表示出来的时候,你们想过该如果解决这一类型的问题了吗?
在这,你们可以先不用看卤煮撸的代码,想想如果自己遇到这个问题,该如果解决,也许你的想法
很新颖,思路以及在算法的实现上更清晰。希望能够在广大的博友的集思广益下,大数的运算能够有
一套更好的解决方案。不说了,咱们先撸代码吧。毕竟这才是主题!!!
二、大数运算之加法运算
public static String add(String n1, String n2) {
if (n1 == null || n1.isEmpty()) {
return n2;
}
if (n2 == null || n2.isEmpty()) {
return n1;
}
StringBuilder result = new StringBuilder();
int overflow = 0;
for (int i = n1.length() - 1, j = n2.length() - 1; i >= 0 || j >= 0; i--, j--) {
int x = i < 0 ? 0 : n1.charAt(i) - '0';
int y = j < 0 ? 0 : n2.charAt(j) - '0';
result.append((x + y + overflow) % 10);
overflow = (x + y + overflow) / 10;
}
if (overflow > 0) {
result.append(overflow);
}
return result.reverse().toString();
}
思路分析:
大数的相加主要是通过字符串的相加来实现的。两个大数相加,找出位数较大的那个大数获取对应的长度,
然后对较小的那个数进行左补0直至长度和较大的那个数的位数一样,最后循环累加两个大数的每一位的数值,
遇到需要进位的需要设一个标识位标识。
两个栈进行处理也可以
三、大数运算之减法运算
package com.linjm.work;
public class Sub {
public String forSub(String p, String q) {
String x = p;
String y = q;
int len = 0;
String res = "";
int sign = 0; //0: x >= y、1: x < y
sign = Tools.forMax(x, y) ? 0 : 1;
if (sign == 1) {
x = q;
y = p;
}
len = x.length();
y = Tools.fillZero(y, x.length() - y.length());
int m, n, flag = 0, num = 0; //num标识出现0的次数,防止结果中高位出现多个0的情况
for (int i = len; i > 0; i--) {
int dif;
m = Integer.parseInt(x.substring(i - 1, i));
n = Integer.parseInt(y.substring(i - 1, i));
dif = m - n;
//标志位如果等于1,说明存在借位
if (flag == 1) {
dif = dif - 1;
flag = 0; //重置标志位
}
//判断是否需要借位
if (dif < 0) {
flag = 1; //标记
dif = dif + 10;
}
if (dif == 0) {
num ++;
} else {
num = 0;
}
res += dif;
}
if (Tools.isZero(res))
return "0";
if (num > 0) {
res = res.substring(0, res.length() - num); //截取掉高位出现的连续num个0
}
return (sign == 1) ? "-" + Tools.reverse(res) : Tools.reverse(res);
}
public static void main(String[] args) {
Sub sub = new Sub();
System.out.println(sub.forSub("100000000", "1"));
}
}
思路分析:
大数相减在实现上和大数相加异曲同工,也是通过循环遍历大数的每一位,对其进行数值相减,在遇到需要借位的数值,
设立一个标识位进行标识。当遇到被减数比减数小的时候,先用减数减去被减数,用标识位标识被减数比减数小,最后结果
根据标识变量判断是否需要在结果上加上"-"。
四、大数运算之乘法运算
package com.linjm.work;
public class Mul {
public String forMul(String p, String q) {
String x = Tools.maxNum(p, q);
String y = Tools.minNum(p, q);
if (y.length() > 15) {
return "ERROR:两位数中必须有一个数的长度要小于15";
}
String sum = "0";
Add add = new Add();
Sub sub = new Sub();
while (Long.parseLong(y) > 0) {
sum = add.forAdd(sum, x);
y = sub.forSub(y, "1");
}
return sum;
}
public static void main(String[] args) {
Mul mul = new Mul();
System.out.println(mul.forMul("1000", "20000"));
}
}
思路分析:
大数相乘在算法的实现上个人表示有点不太满意,虽然实现了功能,但还是觉得代码的实现上和思路都很挫。
乘法的最终思路就是多个数值的加法运算。所以大数的乘法就是多个大数的加法运算。但是遇到两个数值都是大数的情况下,
运行速度会非常的慢。
五、大数运算之除法运算
package com.linjm.work;
public class Div {
public String forDiv (String p, String q) {
String x = p;
String y = q;
String res = "0";
String remain = "0"; //余数
if (!Tools.forMax(x, y)) {
return x + "的值要大等于" + y;
}
Add add = new Add();
Sub sub = new Sub();
while (true) {
x = sub.forSub(x, y);
res = add.forAdd(res, "1");
if (!Tools.forMax(x, y)) {
remain = x;
break;
}
}
if (!Tools.isZero(remain))
res = res + "……" +remain;
return res;
}
public static void main(String[] args) {
Div div = new Div();
System.out.println(div.forDiv("222222222222222222222", "222222222222222222221"));
}
}
思路分析:
除法运算的实质也就是减法,让被除数一直减去除数,直到减不动了为止,统计下减了多少次除数,这个值就是商咯。
但是存在的问题还是和大数的乘法是一样的,有待优化。
六、大数运算之工具类
package com.linjm.work;
public class Tools {
/**
* @param s num
* @Desc 字符串左补num个0
* @return String
* */
public static String fillZero(String s, int num) {
String res = "";
for (int i = 0; i < num; i++) {
res += "0";
}
return res + s;
}
/**
* @param s
* @Desc 反转字符串
* @return String
* */
public static String reverse(String s) {
String res = "";
for (int i = s.length(); i > 0 ; i--) {
res += s.substring(i - 1, i);
}
return res;
}
/**
* @param x n(n最小值为1)
* @Desc 获取字符串的n个字符
* @return int
* */
public static int numAt(String x, int n) {
return Integer.parseInt(x.substring(n - 1, n));
}
/**
* @param x y
* @Desc 获取两个大数中较大的数
* 注:此方法不对两个数是否是数字进行校验
* @return String
* */
public static String maxNum(String x, String y) {
String max = "";
if (x.length() > y.length()) {
max = x;
} else if (x.length() == y.length()) {
for (int i = 1; i <= x.length(); i++) {
if (numAt(x, i) != numAt(y, i)) {
max = (numAt(x, i) > numAt(y, i)) ? x : y;
} else if (i == x.length()) {
max = x;
}
}
} else if (x.length() < y.length()) {
max = y;
} else {
return "ERROR";
}
return max;
}
/**
* @param x y
* @Desc 获取两个大数中较小的数
* 注:此方法不对两个数是否是数字进行校验
* @return String
* */
public static String minNum(String x, String y) {
String min = "";
if (x.length() > y.length()) {
min = y;
} else if (x.length() == y.length()) {
for (int i = 1; i <= x.length(); i++) {
if (numAt(x, i) != numAt(y, i)) {
min = (numAt(x, i) > numAt(y, i)) ? y : x;
} else if (i == x.length()) {
min = x;
}
}
} else if (x.length() < y.length()) {
min = x;
} else {
return "ERROR";
}
return min;
}
/**
* @param x y
* @Desc 大数x是否大于大数y
* @return true/false
* */
public static boolean forMax(String x, String y) {
if (x.length() > y.length()) {
return true;
} else if (x.length() == y.length()) {
for (int i = 1; i <= x.length(); i++) {
if (numAt(x, i) != numAt(y, i)) {
return (numAt(x, i) > numAt(y, i)) ? true : false;
} else if (i == x.length()) {
return true;
}
}
} else if (x.length() < y.length()) {
return false;
} else {
System.out.println("ERROR!!!");
}
return false;
}
/**
* @param x
* @Desc 判断x是否是0或000……
* @return true/false
* */
public static boolean isZero(String x) {
boolean flag = true;
for (int i = 1; i <= x.length(); i++) {
if (Tools.numAt(x, i) != 0) {
return false;
}
}
return flag;
}
}
七、卤煮有话说
也许撸主的代码算法的实现上可能会多多少少存在点瑕疵,第一遍写出来的代码不是最好的,但我们要尽我们所能去
优化和改善我们的代码。大家有什么想法都可以说出来,我们一起来探讨大数的加减乘除,让这么有意义的事可以得到
最优质的解决方案。在求知的路上,我们不怕批评,不怕失败,更不要在意别人的嘲笑,一步一步攀岩,我们终将登顶。
想是一回事,做又是另一回事。让我们脑洞大开,集思广益一起来探讨吧!!!