关于计算问题, 希望各位可以帮忙解答一下

S君别放弃 2019-11-20 07:26:22
本人在学习Java期间, 由老师布置了一个作业, 要求用程序输出一个计算结果, 然后用手算一遍看看答案是否相符, 代码如下:


由于当时没有多想, 便写出了以上代码. 后来发现输出结果异常后, 便在网上查找相关问题的解决. 其中大部分问题解决方案都是由于精度丢失, 换一个更高精度的BigDecimal来计算, 然后我便重新写了一遍, 代码如下:


由于不了解BigDecimal具体功能及作用, 因此出现这个情况也是完全没有预料到的. 然后便和同学讨论Bug的解决方法, 有人提议换一种数据类型, 于是我便使用long类型重新计算了一下, 代码如下:


输出结果很相近, 但是仍不是正确答案, 最终经过多次尝试, 我们决定询问老师, 老师说我们会在计算机体系结构课中详细了解牵涉到的知识......

但是我仍然对这些结果的产生一头雾水, 不知道其真正原理是什么, 只是凭空猜测, 并没有足够的知识支撑.
希望各位能够帮我解答一下上面这些结果发生的原因. 非常感谢!
...全文
128 11 打赏 收藏 转发到动态 举报
写回复
用AI写文章
11 条回复
切换为时间正序
请发表友善的回复…
发表回复
jiawenhe123 2019-11-23
  • 打赏
  • 举报
回复
一些基础的计算本身就有边界的问题,例如a expression b的数学计算;a+b=c但是0.a+0.b!=0.c的问题,这些问题不能一概而论,要看问题域选择合适的策略才能正确实现。 关于UP的问题没有问题域的上下文,所以不好说理想的解决方案,不过从问题的描述来看,是要求避免大数导致边界溢出导致运算结果出错,即使用BigInteger等天文表示法仍然不能正确处理这样的问题,这个问题很类似求出1到N的阶乘结果有多少个0的算法问题。 试想一个数的n次幂一个超大数,那么肯定会出现边界溢出的,就是NaN,可以采用如下方法,如果10的幂是一个超大的数那么必须找到一个能和他抵消的数,即10^20+(-10^20)==0这样的。如果没有则为Nan。 解法1:没有采用面向对象技术,只使用基本类型和数组实现。
/**
 * compute the result: x1y1+x2y2+x3y3+x4y4+x5y5+x6y6
 * x1=10^20,x2=1223,x3=10^18,x4=10^15,x5=3,x6=-10^12
 * y1=10^20,y2=2,y3=-10^22,y4=10^13,y5=2111,y6=10^16
 * <p>
 * note:skip all pre check
 */
public class ApplicationSolution {

	public static void main(String[] args) {
		int[][][] arrays = init();
		int[][] bigs = arrays[0];
		int[][] tinys = arrays[1];
		sortByPower(bigs);
		if (bigs.length % 2 != 0) {
			throw new IllegalStateException("The big array is not illegal,it must be even elements!");
		}
		int[] powers = sortByPower(bigs);


		if (!pair(powers)) {
			outputResult("NaN");
			System.exit(0);
		}
		int result = 0;
		for (int[] tiny : tinys) {
			result += tiny[0] * tiny[1];
		}
		outputResult(result);
	}

	/**
	 * 根据幂排序,如果为负数则*-1
	 * @param bigs 原始大数元素数组
	 * @return 所有大数的幂
	 */
	private static int[] sortByPower(int[][] bigs) {
		int[][] nums = bigs;
		int[] powers = new int[nums.length];
		for (int i = 0; i < nums.length; i++) {
			powers[i] = getCurrentPower(nums[i]);
		}
		Arrays.sort(powers);
		return powers;
	}

	/**
	 * 检查是否所有幂指数都可以被抵消
	 * @param powers 幂指数
	 * @return 抵消结果
	 */
	private static boolean pair(int[] powers) {
		int[] nums = powers;
		int leftIndex = 0, rightIndex = nums.length - 1;
		int left = nums[leftIndex];

		int right = nums[rightIndex];

		while (true) {
			if (left + right != 0) {
				return false;
			}
			leftIndex++;
			rightIndex--;
			if (leftIndex < rightIndex) {
				break;
			}

		}
		return true;
	}

	/**
	 * 获取当前数据的幂指数
	 * @param nums 原始数据
	 * @return 幂指数
	 */
	private static int getCurrentPower(int[] nums) {
		int power = 0;
		for (int j = 1; j < nums.length; j += 2) {
			power += nums[j];
		}
		if (nums[0] < 0) {
			power *= -1;
		}
		return power;
	}

	/**
	 * 使用输入数据初始化
	 * 其中分为两个部分,一个是大数二维数组,一个是基本数数组
	 * @return array[0]为大数元素,array[1]为基本数组
	 */
	private static int[][][] init() {
		int x11 = 10, x12 = 20, y11 = 10, y12 = 20;
		int[] xy1 = new int[]{x11, x12, y11, y12};
		int x2 = 1223, y2 = 2;
		int[] xy2 = new int[]{x2, y2};
		int x31 = -10, x32 = 18, y31 = 10, y32 = 22;
		int[] xy3 = new int[]{x31, x32, y31, y32};
		int x41 = 10, x42 = 15, y41 = 10, y42 = 13;
		int[] xy4 = new int[]{x41, x42, y41, y42};
		int x5 = 3, y5 = 2111;
		int[] xy5 = new int[]{x5, y5};
		int x61 = -10, x62 = 12, y61 = 10, y62 = 16;
		int[] xy6 = new int[]{x61, x62, y61, y62};

		return new int[][][]{new int[][]{xy1, xy3, xy4, xy6}, new int[][]{xy2, xy5}};
	}

	/**
	 * 输出结果
	 * @param result 结果值
	 */
	private static void outputResult(Object result) {
		System.out.println("The result is:" + result);
	}


}
S君别放弃 2019-11-23
  • 打赏
  • 举报
回复
引用 11 楼 jiawenhe123 的回复:
一些基础的计算本身就有边界的问题,例如a expression b的数学计算;a+b=c但是0.a+0.b!=0.c的问题,这些问题不能一概而论,要看问题域选择合适的策略才能正确实现。 关于UP的问题没有问题域的上下文,所以不好说理想的解决方案,不过从问题的描述来看,是要求避免大数导致边界溢出导致运算结果出错,即使用BigInteger等天文表示法仍然不能正确处理这样的问题,这个问题很类似求出1到N的阶乘结果有多少个0的算法问题。 试想一个数的n次幂一个超大数,那么肯定会出现边界溢出的,就是NaN,可以采用如下方法,如果10的幂是一个超大的数那么必须找到一个能和他抵消的数,即10^20+(-10^20)==0这样的。如果没有则为Nan。 解法1:没有采用面向对象技术,只使用基本类型和数组实现。
/**
 * compute the result: x1y1+x2y2+x3y3+x4y4+x5y5+x6y6
 * x1=10^20,x2=1223,x3=10^18,x4=10^15,x5=3,x6=-10^12
 * y1=10^20,y2=2,y3=-10^22,y4=10^13,y5=2111,y6=10^16
 * <p>
 * note:skip all pre check
 */
public class ApplicationSolution {

	public static void main(String[] args) {
		int[][][] arrays = init();
		int[][] bigs = arrays[0];
		int[][] tinys = arrays[1];
		sortByPower(bigs);
		if (bigs.length % 2 != 0) {
			throw new IllegalStateException("The big array is not illegal,it must be even elements!");
		}
		int[] powers = sortByPower(bigs);


		if (!pair(powers)) {
			outputResult("NaN");
			System.exit(0);
		}
		int result = 0;
		for (int[] tiny : tinys) {
			result += tiny[0] * tiny[1];
		}
		outputResult(result);
	}

	/**
	 * 根据幂排序,如果为负数则*-1
	 * @param bigs 原始大数元素数组
	 * @return 所有大数的幂
	 */
	private static int[] sortByPower(int[][] bigs) {
		int[][] nums = bigs;
		int[] powers = new int[nums.length];
		for (int i = 0; i < nums.length; i++) {
			powers[i] = getCurrentPower(nums[i]);
		}
		Arrays.sort(powers);
		return powers;
	}

	/**
	 * 检查是否所有幂指数都可以被抵消
	 * @param powers 幂指数
	 * @return 抵消结果
	 */
	private static boolean pair(int[] powers) {
		int[] nums = powers;
		int leftIndex = 0, rightIndex = nums.length - 1;
		int left = nums[leftIndex];

		int right = nums[rightIndex];

		while (true) {
			if (left + right != 0) {
				return false;
			}
			leftIndex++;
			rightIndex--;
			if (leftIndex < rightIndex) {
				break;
			}

		}
		return true;
	}

	/**
	 * 获取当前数据的幂指数
	 * @param nums 原始数据
	 * @return 幂指数
	 */
	private static int getCurrentPower(int[] nums) {
		int power = 0;
		for (int j = 1; j < nums.length; j += 2) {
			power += nums[j];
		}
		if (nums[0] < 0) {
			power *= -1;
		}
		return power;
	}

	/**
	 * 使用输入数据初始化
	 * 其中分为两个部分,一个是大数二维数组,一个是基本数数组
	 * @return array[0]为大数元素,array[1]为基本数组
	 */
	private static int[][][] init() {
		int x11 = 10, x12 = 20, y11 = 10, y12 = 20;
		int[] xy1 = new int[]{x11, x12, y11, y12};
		int x2 = 1223, y2 = 2;
		int[] xy2 = new int[]{x2, y2};
		int x31 = -10, x32 = 18, y31 = 10, y32 = 22;
		int[] xy3 = new int[]{x31, x32, y31, y32};
		int x41 = 10, x42 = 15, y41 = 10, y42 = 13;
		int[] xy4 = new int[]{x41, x42, y41, y42};
		int x5 = 3, y5 = 2111;
		int[] xy5 = new int[]{x5, y5};
		int x61 = -10, x62 = 12, y61 = 10, y62 = 16;
		int[] xy6 = new int[]{x61, x62, y61, y62};

		return new int[][][]{new int[][]{xy1, xy3, xy4, xy6}, new int[][]{xy2, xy5}};
	}

	/**
	 * 输出结果
	 * @param result 结果值
	 */
	private static void outputResult(Object result) {
		System.out.println("The result is:" + result);
	}


}
感谢回答, 主要题目也不是很清楚, 就写了让我们试着计算上面给出的式子, 和正确答案比较 我们也是一头雾水. 总之非常感谢
格子z 2019-11-22
  • 打赏
  • 举报
回复
引用 6 楼 S君别放弃 的回复:
[quote=引用 4 楼 格子z 的回复:] 首先,长度很长,精度要求高的计算,都得用BigDecimal来计算,不然会丢失精度(题注用的double)。 其次,BigDecimal已经是完善的计算类了。 最后,问题出在题目上,一看就知道肯定是正数并且数据很大,题目是某人心算出来的吗?默认-10的22次方和-10的12次方是负数? 运行代码,对比一下两个结果。

public class Test2 {
    public static void main(String[] args) {
        BigDecimal b1 = new BigDecimal(10).pow(20).multiply(new BigDecimal(10).pow(20));
        BigDecimal b2 = new BigDecimal(1223).multiply(new BigDecimal(2));
        BigDecimal b3 = new BigDecimal(10).pow(18).multiply(new BigDecimal(-10).pow(22));
        BigDecimal b4 = new BigDecimal(10).pow(15).multiply(new BigDecimal(10).pow(13));
        BigDecimal b5 = new BigDecimal(3).multiply(new BigDecimal(2111));
        BigDecimal b6 = new BigDecimal(-10).pow(12).multiply(new BigDecimal(10).pow(16));
        BigDecimal result1 = new BigDecimal(0);
        BigDecimal result2 = new BigDecimal(0);
        result1 = b1.add(b2).add(b3).add(b4).add(b5).add(b6);
        result2 = b1.add(b2).subtract(b3).add(b4).add(b5).subtract(b6);
        System.out.println(result1);
        System.out.println(result2);
    }
}
不是的, 是先计算次方再加负号, 这个是可以手算的, 因为其中带次幂的数都被抵消了, 最后只剩下x2y2 + x5y5 得出8779. 另外请问第二种BigDecimal是我使用错误吗 因为输出结果是一串非常诡异的数.[/quote] 是先计算次方再加负号,-10的2次方+10的2次方=0??? 负数的偶数次方也是正数。 不知道你这边是怎么运行的,简单点,我给的两种计算方式,一种是按照公式去计算,第二种是按照出题者的思路去计算,把两个假装是负数的数不用加,用减,就得到8779
S君别放弃 2019-11-22
  • 打赏
  • 举报
回复
引用 8 楼 格子z 的回复:
[quote=引用 6 楼 S君别放弃 的回复:] [quote=引用 4 楼 格子z 的回复:] 首先,长度很长,精度要求高的计算,都得用BigDecimal来计算,不然会丢失精度(题注用的double)。 其次,BigDecimal已经是完善的计算类了。 最后,问题出在题目上,一看就知道肯定是正数并且数据很大,题目是某人心算出来的吗?默认-10的22次方和-10的12次方是负数? 运行代码,对比一下两个结果。

public class Test2 {
    public static void main(String[] args) {
        BigDecimal b1 = new BigDecimal(10).pow(20).multiply(new BigDecimal(10).pow(20));
        BigDecimal b2 = new BigDecimal(1223).multiply(new BigDecimal(2));
        BigDecimal b3 = new BigDecimal(10).pow(18).multiply(new BigDecimal(-10).pow(22));
        BigDecimal b4 = new BigDecimal(10).pow(15).multiply(new BigDecimal(10).pow(13));
        BigDecimal b5 = new BigDecimal(3).multiply(new BigDecimal(2111));
        BigDecimal b6 = new BigDecimal(-10).pow(12).multiply(new BigDecimal(10).pow(16));
        BigDecimal result1 = new BigDecimal(0);
        BigDecimal result2 = new BigDecimal(0);
        result1 = b1.add(b2).add(b3).add(b4).add(b5).add(b6);
        result2 = b1.add(b2).subtract(b3).add(b4).add(b5).subtract(b6);
        System.out.println(result1);
        System.out.println(result2);
    }
}
不是的, 是先计算次方再加负号, 这个是可以手算的, 因为其中带次幂的数都被抵消了, 最后只剩下x2y2 + x5y5 得出8779. 另外请问第二种BigDecimal是我使用错误吗 因为输出结果是一串非常诡异的数.[/quote] 是先计算次方再加负号,-10的2次方+10的2次方=0??? 负数的偶数次方也是正数。 不知道你这边是怎么运行的,简单点,我给的两种计算方式,一种是按照公式去计算,第二种是按照出题者的思路去计算,把两个假装是负数的数不用加,用减,就得到8779[/quote] 嗯... 可能理解上出现误差了. 总之感谢解答
活在梦里吗 2019-11-21
  • 打赏
  • 举报
回复
引用 2 楼 S君别放弃 的回复:
[quote=引用 1 楼 活在梦里吗的回复:]浮点数问题?二进制无法准确表达小数,就跟十进制无法准备表达1/3这种数一样
可是最后使用long类型时,结果也差1是为什么呢? 而且0.0这个结果也太诡异了,跟没有进行运算一样[/quote]
引用 2 楼 S君别放弃 的回复:
[quote=引用 1 楼 活在梦里吗的回复:]浮点数问题?二进制无法准确表达小数,就跟十进制无法准备表达1/3这种数一样
可是最后使用long类型时,结果也差1是为什么呢? 而且0.0这个结果也太诡异了,跟没有进行运算一样[/quote] 细看了下,题目本身就是有问题的,结果肯定是很大的整数。再来math.pow()中传的是double类型,做浮点数肯定有误差。要精确BigDecimal中有对应的pow方法
S君别放弃 2019-11-21
  • 打赏
  • 举报
回复
引用 4 楼 格子z 的回复:
首先,长度很长,精度要求高的计算,都得用BigDecimal来计算,不然会丢失精度(题注用的double)。 其次,BigDecimal已经是完善的计算类了。 最后,问题出在题目上,一看就知道肯定是正数并且数据很大,题目是某人心算出来的吗?默认-10的22次方和-10的12次方是负数? 运行代码,对比一下两个结果。

public class Test2 {
    public static void main(String[] args) {
        BigDecimal b1 = new BigDecimal(10).pow(20).multiply(new BigDecimal(10).pow(20));
        BigDecimal b2 = new BigDecimal(1223).multiply(new BigDecimal(2));
        BigDecimal b3 = new BigDecimal(10).pow(18).multiply(new BigDecimal(-10).pow(22));
        BigDecimal b4 = new BigDecimal(10).pow(15).multiply(new BigDecimal(10).pow(13));
        BigDecimal b5 = new BigDecimal(3).multiply(new BigDecimal(2111));
        BigDecimal b6 = new BigDecimal(-10).pow(12).multiply(new BigDecimal(10).pow(16));
        BigDecimal result1 = new BigDecimal(0);
        BigDecimal result2 = new BigDecimal(0);
        result1 = b1.add(b2).add(b3).add(b4).add(b5).add(b6);
        result2 = b1.add(b2).subtract(b3).add(b4).add(b5).subtract(b6);
        System.out.println(result1);
        System.out.println(result2);
    }
}
不是的, 是先计算次方再加负号, 这个是可以手算的, 因为其中带次幂的数都被抵消了, 最后只剩下x2y2 + x5y5 得出8779. 另外请问第二种BigDecimal是我使用错误吗 因为输出结果是一串非常诡异的数.
S君别放弃 2019-11-21
  • 打赏
  • 举报
回复
引用 3 楼 qybao 的回复:
Math的power方法返回double,再进行强制转换会有精度丢失,而且小数本身计算机不一定能精确存储,所以可能达不到你要的结果 自己写个power方法,就用int,任其溢出,但至少没有小数存储,精度丢失等问题 for example
public class Sample {
    public static void main(String[] args) throws Exception {
        try {
            int x1 = power(10, 20);
            int x2 = 1223;
            int x3 = power(10, 18);
            int x4 = power(10, 15);
            int x5 = 3;
            int x6 = -power(10, 12);
            int y1 = power(10, 20);
            int y2 = 2;
            int y3 = -power(10, 22);
            int y4 = power(10, 13);
            int y5 = 2111;
            int y6 = power(10, 16);
            System.out.println(x1*y1+x2*y2+x3*y3+x4*y4+x5*y5+x6*y6);    
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public static int power(int base, int times) {
        if (times == 0) return 1;
        else if (times == 1) return base;
        for (int i=1; i<times; i++) {
            base *= base;
        }
        return base;
    }
}
好的, 我试试自己写一个方法; 还有一个问题, 请问第二种使用BigDecimal也是由于精度损失造成的吗
格子z 2019-11-21
  • 打赏
  • 举报
回复
首先,长度很长,精度要求高的计算,都得用BigDecimal来计算,不然会丢失精度(题注用的double)。 其次,BigDecimal已经是完善的计算类了。 最后,问题出在题目上,一看就知道肯定是正数并且数据很大,题目是某人心算出来的吗?默认-10的22次方和-10的12次方是负数? 运行代码,对比一下两个结果。

public class Test2 {
    public static void main(String[] args) {
        BigDecimal b1 = new BigDecimal(10).pow(20).multiply(new BigDecimal(10).pow(20));
        BigDecimal b2 = new BigDecimal(1223).multiply(new BigDecimal(2));
        BigDecimal b3 = new BigDecimal(10).pow(18).multiply(new BigDecimal(-10).pow(22));
        BigDecimal b4 = new BigDecimal(10).pow(15).multiply(new BigDecimal(10).pow(13));
        BigDecimal b5 = new BigDecimal(3).multiply(new BigDecimal(2111));
        BigDecimal b6 = new BigDecimal(-10).pow(12).multiply(new BigDecimal(10).pow(16));
        BigDecimal result1 = new BigDecimal(0);
        BigDecimal result2 = new BigDecimal(0);
        result1 = b1.add(b2).add(b3).add(b4).add(b5).add(b6);
        result2 = b1.add(b2).subtract(b3).add(b4).add(b5).subtract(b6);
        System.out.println(result1);
        System.out.println(result2);
    }
}
qybao 2019-11-21
  • 打赏
  • 举报
回复
Math的power方法返回double,再进行强制转换会有精度丢失,而且小数本身计算机不一定能精确存储,所以可能达不到你要的结果
自己写个power方法,就用int,任其溢出,但至少没有小数存储,精度丢失等问题
for example
public class Sample {
public static void main(String[] args) throws Exception {
try {
int x1 = power(10, 20);
int x2 = 1223;
int x3 = power(10, 18);
int x4 = power(10, 15);
int x5 = 3;
int x6 = -power(10, 12);
int y1 = power(10, 20);
int y2 = 2;
int y3 = -power(10, 22);
int y4 = power(10, 13);
int y5 = 2111;
int y6 = power(10, 16);
System.out.println(x1*y1+x2*y2+x3*y3+x4*y4+x5*y5+x6*y6);
} catch (Exception e) {
e.printStackTrace();
}
}

public static int power(int base, int times) {
if (times == 0) return 1;
else if (times == 1) return base;
for (int i=1; i<times; i++) {
base *= base;
}
return base;
}
}
S君别放弃 2019-11-21
  • 打赏
  • 举报
回复
引用 1 楼 活在梦里吗的回复:
浮点数问题?二进制无法准确表达小数,就跟十进制无法准备表达1/3这种数一样
可是最后使用long类型时,结果也差1是为什么呢? 而且0.0这个结果也太诡异了,跟没有进行运算一样
活在梦里吗 2019-11-20
  • 打赏
  • 举报
回复
浮点数问题?二进制无法准确表达小数,就跟十进制无法准备表达1/3这种数一样

62,614

社区成员

发帖
与我相关
我的任务
社区描述
Java 2 Standard Edition
社区管理员
  • Java SE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧