读书人

JAVA的浮点运算精度有关问题和math类

发布时间: 2012-09-10 11:02:32 作者: rapoo

JAVA的浮点运算精度问题和math类

JAVA的浮点运算精度问题和math类

的提出:
如果我行下面程式看到什?
public class Test{
public static void main(String args[]){
System.out.println(0.05+0.01);
System.out.println(1.0-0.42);
System.out.println(4.015*100);
System.out.println(123.3/100);
}
};
你有看!果是
0.060000000000000005
0.5800000000000001
401.49999999999994
1.2329999999999999
Java中的浮型float和double不能行算。不光是Java,在其他很多程言中也有的。在大多情下,算的果是的,但是多次(可以做一圈)就可以出似上面的。在於理解什要有BCD了。
相重,如果你有9.999999999999元,你的是不你可以10元的商品的。
在有的程言中提供了的型理情,但是Java有。在我看看如何解。


四五入
我的第一反是做四五入。Math中的round方法不能置保留位小,我只能象(保留位):
public double round(double value){
return Math.round(value*100)/100.0;
}
非常不幸,上面的代不能正常工作,方法入4.015它返回4.01而不是4.02,如我在上面看到的
4.015*100=401.49999999999994
因此如果我要做到精的四五入,不能利用型做任何算
java.text.DecimalFormat也不能解:
System.out.println(new java.text.DecimalFormat("0.00").format(4.025));
出是4.02
BigDecimal
在《Effective java》本中也提到原,float和double只能用做科算或者是工程算,在商算中我要用java.math.BigDecimal。BigDecimal一共有4造方法,我不心用BigInteger造的那,那有,它是:
BigDecimal(double val)
Translates a double into a BigDecimal.
BigDecimal(String val)
Translates the String repre sentation of a BigDecimal into a BigDecimal.
上面的API要描述相的明,而且通常情下,上面的那一使用起要方便一些。我可能想都不想就用上了,有什呢?等到出了的候,才上面哪造方法的明中有一段:
Note: the results of this constructor can be somewhat unpredictable. One might assume that new BigDecimal(.1) is exactly equal to .1, but it is actually equal to .1000000000000000055511151231257827021181583404541015625. This is so because .1 cannot be represented exactly as a double (or, for that matter, as a binary fraction of any finite length). Thus, the long value that is being passed in to the constructor is not exactly equal to .1, appearances nonwithstanding.
The (String) constructor, on the other hand, is perfectly predictable: new BigDecimal(".1") is exactly equal to .1, as one would expect. Therefore, it is generally recommended that the (String) constructor be used in preference to this one.

原我如果需要精算,非要用String造BigDecimal不可!在《Effective java》一中的例子是用String造BigDecimal的,但是上有一,也是一小小的失吧。
解方案
在我已可以解了,原是使用BigDecimal且一定要用String造。
但是想像一下吧,如果我要做一加法算,需要先浮String,然後造成BigDecimal,在其中一上用add方法,入另一作,然後把算的果(BigDecimal)再浮。你能忍受的程?下面我提供一工具Arith化操作。它提供以下方法,包括加乘除和四五入:
public static double add(double v1,double v2)
public static double sub(double v1,double v2)
public static double mul(double v1,double v2)
public static double div(double v1,double v2)
public static double div(double v1,double v2,int scale)
public static double round(double v,int scale)

原始案Arith.java:
import java.math.BigDecimal;

/**
* 由於Java的型不能精的浮行算,工具提供精
* 的浮算,包括加乘除和四五入。
*/
public class Arith{

//默除法算精度
private static final int DEF_DIV_SCALE = 10;

//不能例化
private Arith(){
}


/**
* 提供精的加法算。
* @param v1 被加
* @param v2 加
* @return 的和
*/
public static double add(double v1,double v2){
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.add(b2).doubleValue();
}

/**
* 提供精的法算。
* @param v1 被
* @param v2
* @return 的差
*/
public static double sub(double v1,double v2){
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.subtract(b2).doubleValue();
}

/**
* 提供精的乘法算。
* @param v1 被乘
* @param v2 乘
* @return 的
*/
public static double mul(double v1,double v2){
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.multiply(b2).doubleValue();
}

/**
* 提供(相)精的除法算,生除不的情,精到
* 小以後10位元,以後的字四五入。
* @param v1 被除
* @param v2 除
* @return 的商
*/
public static double div(double v1,double v2){
return div(v1,v2,DEF_DIV_SCALE);
}

/**
* 提供(相)精的除法算。生除不的情,由scale指
* 定精度,以後的字四五入。
* @param v1 被除
* @param v2 除
* @param scale 表示表示需要精到小以後位。
* @return 的商
*/
public static double div(double v1,double v2,int scale){
if(scale<0){
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.divide(b2,scale,BigDecimal.ROUND_HALF_UP).doubleValue();
}

/**
* 提供精的小位四五入理。
* @param v 需要四五入的位
* @param scale 小後保留位
* @return 四五入後的果
*/
public static double round(double v,int scale){
if(scale<0){
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b = new BigDecimal(Double.toString(v));
BigDecimal one = new BigDecimal("1");
return b.divide(one,scale,BigDecimal.ROUND_HALF_UP).doubleValue();
}
};

==================================================

1.舍掉小数取整:Math.floor(3.5)=3

2.四舍五入取整:Math.rint(3.5)=4

3.进位取整:Math.ceil(3.1)=4


在Java中进行取整,尤其是四舍五入取整还有点麻烦。

下面是我根据网上的一些解答整理的三种取整运算(包括截尾取整,四舍五入,凑整),类似于面向过程语言(如C和Basic)中的取整函数(不过在Java中它叫类的方法,“类名.方法名(参数)”的运算都是类的静态方法)。

其中,注释掉的那段是在网上查到的有的朋友认为正确的四舍五入的取整方法,但是经过我的实验却是不正确的四舍五入的取整方法。

TestGetInt.java 源代码


import java.math.BigDecimal;
import java.text.DecimalFormat;

public class TestGetInt{
?? public static void main(String[] args){
???? double i=2, j=2.1, k=2.5, m=2.9;
???? System.out.println("舍掉小数取整:Math.floor(2)=" + (int)Math.floor(i));
???? System.out.println("舍掉小数取整:Math.floor(2.1)=" + (int)Math.floor(j));
???? System.out.println("舍掉小数取整:Math.floor(2.5)=" + (int)Math.floor(k));
???? System.out.println("舍掉小数取整:Math.floor(2.9)=" + (int)Math.floor(m));
???????????????????????????????????????????????????????????????????????????????
???? /* 这段被注释的代码不能正确的实现四舍五入取整
???? System.out.println("四舍五入取整:Math.rint(2)=" + (int)Math.rint(i));
???? System.out.println("四舍五入取整:Math.rint(2.1)=" + (int)Math.rint(j));
???? System.out.println("四舍五入取整:Math.rint(2.5)=" + (int)Math.rint(k));
???? System.out.println("四舍五入取整:Math.rint(2.9)=" + (int)Math.rint(m));
????
???? System.out.println("四舍五入取整:(2)=" + new DecimalFormat("0").format(i));
???? System.out.println("四舍五入取整:(2.1)=" + new DecimalFormat("0").format(i));
???? System.out.println("四舍五入取整:(2.5)=" + new DecimalFormat("0").format(i));
???? System.out.println("四舍五入取整:(2.9)=" + new DecimalFormat("0").format(i));
???? */
????
???? System.out.println("四舍五入取整:(2)=" + new BigDecimal("2").setScale(0, BigDecimal.ROUND_HALF_UP));
???? System.out.println("四舍五入取整:(2.1)=" + new BigDecimal("2.1").setScale(0, BigDecimal.ROUND_HALF_UP));
???? System.out.println("四舍五入取整:(2.5)=" + new BigDecimal("2.5").setScale(0, BigDecimal.ROUND_HALF_UP));
???? System.out.println("四舍五入取整:(2.9)=" + new BigDecimal("2.9").setScale(0, BigDecimal.ROUND_HALF_UP));

???? System.out.println("凑整:Math.ceil(2)=" + (int)Math.ceil(i));
???? System.out.println("凑整:Math.ceil(2.1)=" + (int)Math.ceil(j));
???? System.out.println("凑整:Math.ceil(2.5)=" + (int)Math.ceil(k));
???? System.out.println("凑整:Math.ceil(2.9)=" + (int)Math.ceil(m));

???? System.out.println("舍掉小数取整:Math.floor(-2)=" + (int)Math.floor(-i));
???? System.out.println("舍掉小数取整:Math.floor(-2.1)=" + (int)Math.floor(-j));
???? System.out.println("舍掉小数取整:Math.floor(-2.5)=" + (int)Math.floor(-k));
???? System.out.println("舍掉小数取整:Math.floor(-2.9)=" + (int)Math.floor(-m));
????
???? System.out.println("四舍五入取整:(-2)=" + new BigDecimal("-2").setScale(0, BigDecimal.ROUND_HALF_UP));
???? System.out.println("四舍五入取整:(-2.1)=" + new BigDecimal("-2.1").setScale(0, BigDecimal.ROUND_HALF_UP));
???? System.out.println("四舍五入取整:(-2.5)=" + new BigDecimal("-2.5").setScale(0, BigDecimal.ROUND_HALF_UP));
???? System.out.println("四舍五入取整:(-2.9)=" + new BigDecimal("-2.9").setScale(0, BigDecimal.ROUND_HALF_UP));

???? System.out.println("凑整:Math.ceil(-2)=" + (int)Math.ceil(-i));
???? System.out.println("凑整:Math.ceil(-2.1)=" + (int)Math.ceil(-j));
???? System.out.println("凑整:Math.ceil(-2.5)=" + (int)Math.ceil(-k));
???? System.out.println("凑整:Math.ceil(-2.9)=" + (int)Math.ceil(-m));
???? }
}

=====================================================

数学类

算法,特别是数值算法,按使用习惯是不应该封装成为类的。就仿佛C++的算法模板库,使用起来很方便,但java已经被设计成为纯面向对象的语言了,所以java采用了静态函数来完成Math类。Math类非常丰富而且简便,封装了几乎所有数学上需要的基本运算,是一个非常出色的类组件。
这也就是很多C++程序员羡慕java程序员的原因——java程序员有太多丰富的组件可以调用,几乎不用自己费神去实现那些复杂而且无聊的东西。
下面介绍部分Math的方法(我觉得我能用到的)。

Math.PI 记录的圆周率
Math.E 记录e的常量
Math中还有一些类似的常量,都是一些工程数学常用量。

Math.abs 求绝对值
Math.sin 正弦函数 Math.asin 反正弦函数
Math.cos 余弦函数 Math.acos 反余弦函数
Math.tan 正切函数 Math.atan 反正切函数 Math.atan2 商的反正切函数
Math.toDegrees 弧度转化为角度 Math.toRadians 角度转化为弧度
Math.ceil 得到不小于某数的最大整数
Math.floor 得到不大于某数的最大整数
Math.IEEEremainder 求余
Math.max 求两数中最大
Math.min 求两数中最小
Math.sqrt 求开方
Math.pow 求某数的任意次方, 抛出ArithmeticException处理溢出异常
Math.exp 求e的任意次方
Math.log10 以10为底的对数
Math.log 自然对数
Math.rint 求距离某数最近的整数(可能比某数大,也可能比它小)
Math.round 同上,返回int型或者long型(上一个函数返回double型)
Math.random 返回0,1之间的一个随机数

读书人网 >编程

热点推荐