浮点数判断相等问题
啤酒和饮料
啤酒每罐2.3元,饮料每罐1.9元。小明买了若干啤酒和饮料,一共花了82.3元。
我们还知道他买的啤酒比饮料的数量少,请你计算他买了几罐啤酒。
注意:答案是一个整数。请通过浏览器提交答案
不要书写任何多余的内容(例如:写了饮料的数量,添加说明文字等)。
这道题是送分题,很多同学用通过暴力破解法这种方法实现的:
public class 浮点数1_啤酒和饮料 {
public static void main(String[] args) {
for (int a = 0; a < 100; a++) //a 是啤酒数量,b是饮料的数量,假设它们的取值范围都在0 ~ 100
{
for (int b = 0; b < 100; b++) {
if (a * 2.3 + b * 1.9 == 82.3 && a < b)
System.out.println(a + "," + b);
}
}
}
}
只不过,犯了一个大忌——两个浮点数之间不能用“==”来判断是否相等。因为浮点数在计算式内部是采用二进制的方式来表示。因此在计算机中0.1+0.2不是等于0.3,而是等于“0.30000000000000004”(不信自己去敲一敲)。这道题能算出结果算是你运气好。
既然这样,那我们如何来判断浮点数是否相等呢?
要判断浮点数是否相等,则a-b的绝对值无限接近一个非常小的值。
即|a - b| < set
,一般set=1E-6(科学计数法,1*10的负6次方)即可。
由此可以这样实现:
public class 浮点数1_啤酒和饮料 {
public static void main(String[] args) {
for (int a = 0; a < 100; a++) //a 是啤酒数量,b是饮料的数量,假设它们的取值范围都在0 ~ 100
{
for (int b = 0; b < 100; b++) {
if (Math.abs(a * 2.3 + b * 1.9 - 82.3) < 1E-10 && a < b)
System.out.println(a + "," + b);
}
}
}
}
由于题目给的这些浮点数都保留一位小数,则可以把所有数值都乘以10,变成整数来全等判断:
public class 浮点数1_啤酒和饮料 {
public static void main(String[] args) {
for (int a = 0; a < 100; a++) //a 是啤酒数量,b是饮料的数量,假设它们的取值范围都在0 ~ 100
{
for (int b = 0; b < 100; b++) {
if (a * 23 + b * 19 == 823 && a < b)
System.out.println(a + "," + b);
}
}
}
}
使用整数进行全等判断,比浮点数安全多了。
海盗问题
有一群海盗(不多于20人),在船上比拼酒量。过程如下:打开一瓶酒, 所有在场的人平分喝下,有几个人倒下了。再打开一瓶酒平分,又有倒下的,再次重复...... 直到开了第4瓶酒,坐着的已经所剩无几,海盗船长也在其中。当第4瓶酒平分喝下后,大家都倒下了。等船长醒来,发现海盗船搁浅了。他在航海日志中写到:“......昨天,我正好喝了一瓶.......奉劝大家,开船不喝酒,喝酒别开船......”
请你根据这些信息,推断开始有多少人,每一轮喝下来还剩多少人。
如果有多个可能的答案,请列出所有答案,每个答案占一行。
格式是:人数,人数,...
例如,有一种可能是:20,5,4,2,0
思路:这道题关键是船长的这句话,“昨天,我正好喝了一瓶”,船上打开了4瓶酒,每瓶酒向没醉的人进行平分。所以,由题意可得,每次平分的酒量相加为1。
代码如下:
public class 浮点数2_海盗问题 {
public static void main(String[] args) {
int a, b, c, d; //每一轮剩下的人
for (a = 20; a >= 1; a--)
//第一轮,海盗的人不多于20人,但至少会有一人
for (b = a - 1; b >= 1; b--)
//每一轮都有人倒下,因此第二轮肯定比第一轮a的人数少
for (c = b - 1; c >= 1; c--)
//第三轮人数比第二轮少,但至少有一人
for (d = c - 1; d >= 1; d--) {
//第四轮人数比第三轮少,但至少有一人
//船长四轮都喝了,每轮都是平分的酒,一共喝了一瓶
if (1.0 / a + 1.0 / b + 1.0 / c + 1.0 / d == 1.0)
System.out.println(a + "," + b + "," + c + "," + d);
}
}
}
注意:判断式内一定是(1.0/a+1.0/b+1.0/c+1.0/d==1.0)
,如果是(1/a+1/b+1/c+1/d==1)
,由于1/a是整型,所以1/a为0,不是小数。
但是,上面的代码忽略了“两个浮点数之间不能用全等来判断是否相等。”这个易错点。
修改的代码如下:
public class 浮点数2_海盗问题 {
public static void main(String[] args) {
int a, b, c, d; //每一轮剩下的人
for (a = 20; a >= 1; a--)
//第一轮,海盗的人不多于20人,但至少会有一人
for (b = a - 1; b >= 1; b--)
//每一轮都有人倒下,因此第二轮肯定比第一轮a的人数少
for (c = b - 1; c >= 1; c--)
//第三轮人数比第二轮少,但至少有一人
for (d = c - 1; d >= 1; d--) {
//第四轮人数比第三轮少,但至少有一人
//船长四轮都喝了,每轮都是平分的酒,一共喝了一瓶
if (Math.abs(1.0 / a + 1.0 / b + 1.0 / c + 1.0 / d - 1.0) < 1E-10)
System.out.println(a + "," + b + "," + c + "," + d);
}
}
}
这道题,我们还有另一个判断的方式,就是在(1/a+1/b+1/c+1/d==1)
中,每项分别乘以(a * b * c * d)
修改的代码如下:
public class 浮点数2_海盗问题 {
public static void main(String[] args) {
int a, b, c, d; //每一轮剩下的人
for (a = 20; a >= 1; a--)
//第一轮,海盗的人不多于20人,但至少会有一人
for (b = a - 1; b >= 1; b--)
//每一轮都有人倒下,因此第二轮肯定比第一轮a的人数少
for (c = b - 1; c >= 1; c--)
//第三轮人数比第二轮少,但至少有一人
for (d = c - 1; d >= 1; d--) {
//第四轮人数比第三轮少,但至少有一人
//船长四轮都喝了,每轮都是平分的酒,一共喝了一瓶
if (b * c * d + a * c * d + a * b * d + a * b * c == a * b * c * d)
System.out.println(a + "," + b + "," + c + "," + d);
}
}
}
四舍六入五成双
Java舍入方式叫“四舍六入五成双”。
规则:
(1)被修约的数字小于5时,该数字舍去;
(2)被修约的数字大于5时,则进位;
(3)被修约的数字等于5时,要看5前面的数字,若是奇数则进位,若是偶数则将5舍掉,即修约后末尾数字都成为偶数;若5的后面还有不为“0”的任何数,则此时无论5的前面是奇数还是偶数,均应进位。
举例,用上述规则对下列数据保留3位有效数字:
9.8249=9.82, 9.82671=9.83
9.8350=9.84, 9.8351 =9.84
9.8250=9.82, 9.82501=9.83
从统计学的角度,“四舍六入五成双”比“四舍五入”要科学,在大量运算时,它使舍入后的结果误差的均值趋于零,而不是像四舍五入那样逢五就入,导致结果偏向大数,使得误差产生积累进而产生系统误差,“四舍六入五成双”使测量结果受到舍入误差的影响降到最低。