`
HowieChih
  • 浏览: 1875 次
  • 来自: 上海
最近访客 更多访客>>
社区版块
存档分类
最新评论

Java中浮点数丢失精度的解决方案

 
阅读更多

根据IEEE 754标准,规定浮点数有floatdouble两种编码方式,基本格式为:

 

> 符号数 | 阶码数 | 尾数

 

float   单精度,长度4个字节,最高位符号位,接下来8位为指数,低23位为位数。 

double  双精度,长度8个字节,最高位符号位,接下来11位为指数,低52位为位数。

由于大多数小数无法用精确的二进制表示,以会出现精度丢的情况。如,

 

 

System.out.println(0.05 + 0.01);
result: 0.060000000000000005

System.out.println(1.0 - 0.42);
result:0.5800000000000001
		
System.out.println(4.015 * 100);
result:401.49999999999994
		
System.out.println(123.3 / 100);
result:1.2329999999999999

 

 

[解决方案]

 

使用BigDecimal

BigDecimal是Java提供的一个不变的,任意精度的,有符号十进制数对象。

 

1、获取BigDecimal对象

为了实现精确计算,有以下几种方法获得BigDecimal对象:

 

//使用参数为String类型的构造方法
BigDecimal bigDecimal1 = new BigDecimal ("0.01");

//将double转换为String后,利用构造方法获得
BigDecimal bigDecimal2 = new BigDecimal (Double.toString(0.05));

//使用静态方法,内部实现还是先将double转换为了String
BigDecimal bigDecimal3 = BigDecimal.valueOf(0.01);

 

注:直接将double类型作为参数利用构造方法获得的BigDecimal对象也是不精确的。

 

2、方法介绍

 

[ 获取小数点后位数 ]

int scale()

Returns the scale of this BigDecimal.

 

注:涉及到无限小数的点后位数时,一定要使用有RoundingMode(舍入模式)的方法,否则在精确小数时无法使用精确模式导致报错。

 

[ 设置小数点后位数,精确小数 ]

> BigDecimal setScale(int newScale)

BigDecimal setScale(int newScale, int roundingMode)

或者BigDecimal setScale(int newScale, RoundingMode roundingMode)

Returns a BigDecimal whose scale is the specified value, and whose value is numerically equal to this BigDecimal's. Throws an ArithmeticException if this is not possible. 

第一个方法设置的小数点后位数小于当前的小数点后位数的话,程序将会报错,应该采用指定舍入模式的方法

 

[ 精确的加法 ]

BigDecimal add(BigDecimal augend)

Returns a BigDecimal whose value is (this + augend), and whose scale is max(this.scale(), augend.scale()).

 

[ 精确的减法 ]

BigDecimal subtract(BigDecimal subtrahend)

Returns a BigDecimal whose value is (this - augend), and whose scale is max(this.scale(), augend.scale()).

 

[ 精确的乘法 ]

BigDecimal mutiply(BigDecimal mutiplicand)

Returns a BigDecimal whose value is (this * mutiplicand), and whose scale is (this.scale() + mutiplicand.scale()).

 

[ (相对)精确的除法 ]

如果商本身是有限的,那么除法将消除精度丢失,而如果商本身是无限的,那么采用舍入模式实现相对精确.

 

> BigDecimal divide(BigDecimal divisor)

Returns a BigDecimal whose value is (this / divisor), and whose preferred scale is (this.scale() - divisor.scale()); if the exact quotient( 商 ) cannot be represented (because it has a non-terminating decimal expansion) an ArithmeticException is thrown.

不太能理解preferred scale.

 

> BigDecimal divide(BigDecimal divisor, int roundingMode)

或者BigDecimal divide(BigDecimal divisor, RoundingMode roundingMode)

Returns a BigDecimal whose value is (this / divisor), and whose scale is this.scale().

采用指定的舍入模式

 

> BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)

或者BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode)

Returns a BigDecimal whose value is (this / divisor), and whose scale is as specified.

采用指定的舍入模式

 

[ 舍入模式 ] 有2种,一种是RoundingMode的枚举类,一种是BigDecimal的常量值。以RoundingMode类对象为例

> static RoundingMode CEILING

Rounding mode to round towards positive infinity.

向正无穷方向舍入

 

> static RoundingMode DOWN

Rounding mode to round towards zero.

向零方向舍入

 

> static RoundingMode FLOOR

Rounding mode to round towards negative infinity.

向负无穷方向舍入

 

> static RoundingMode HALF_DOWN

Rounding mode to round towards "nearest neighbor" unless both neighbors are equidistant, in which case round down.

向(距离)最近的一边舍入,除非两边(的距离)是相等,如果是这样,向下舍入, 例如1.55 保留一位小数结果为1.5

 

> static RoundingMode HALF_EVEN

Rounding mode to round towards the "nearest neighbor" unless both neighbors are equidistant, in which case, round towards the even neighbor.

以下参考自:数字修约规则

商业计算的舍入模式通常使用这个,也叫做四舍六入五留双规则或者Bank's Rounding,更应该叫做四舍六入逢五无后则留双,规则如下:

# 尾数小于等于4时,舍弃;尾数大于等于6时,将尾数舍去并向前一位进位。

# 尾数等于5时,当尾数后面的数字均为0时,如果尾数前一位为奇数,尾数舍去向前一位进位,如果为偶数,直接舍弃(此时0为偶数);如果尾数后面的数字任意一位不为0,直接向前进位。

如,1.050,保留1位有效数字为1.0

       1.150,保留1位有效数字为1.2

       1.0501,保留1位有效数字为1.1

 

> static RoundingMode HALF_UP

Rounding mode to round towards "nearest neighbor" unless both neighbors are equidistant, in which case round up.

向(距离)最近的一边舍入,除非两边(的距离)是相等,如果是这样,向上舍入, 1.55保留一位小数结果为1.6

 

> static RoundingMode UNNECESSARY

Rounding mode to assert that the requested operation has an exact result, hence no rounding is necessary.

计算结果是精确的,不需要舍入模式

 

> static RoundingMode UP

Rounding mode to round away from zero.

向远离0的方向舍入

 

另外,利用数学计算也可以实现小数点后精确位的四舍五入。

 

/**
 * 实现小数点后scale的四舍五入
 * 
 * @param value
 *            数值
 * @param scale
 *            精确到小数点后位数
 * @return 四舍五入结果
 */
public static double round(double value, int scale) {
	double temp = 1;
	for (int i = 0; i < scale; i++) {
		temp = temp * 10;
	}
	return ((int) (value * temp + 0.5)) / temp;
}

System.out.println(round(0.61125, 3));
result: 0.611

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics