Java中高精度问题的bigdecimal解决方式

最近有空写了点老的J2EE的代码,发现有一个十分有意思的问题,当用Hibernate从数据库里把浮点数读取出来的时候做一些比如累加的工作,例如 summary 或者递减之类的,就会发现在最后的结果中会出现些许问题。

如:3.41+5.2+56.2+23.3+... (这类两位小数的价钱),结果会出现103.00000000000001这种结果,但是人算的话反而会得出正常的数据。看样子double,float这类数据精度上来了还会有这类问题。

    于是,翻了点资料,在有的编程语言中提供了专门的货币类型来处理这种情况,但是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:

package com.common.util;
import java.math.BigDecimal;

public final class Arith {
 // 默认除法运算精度
 private static final int DEF_DIV_SCALE = 2;
 // 这个类不能实例化
 private Arith() {
 }
 
 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();
 }
 
 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();
 }
 
 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();
 }
 
 public static double div(double v1, double v2) {
  return div(v1, v2, DEF_DIV_SCALE);
 }
 
 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();
 }
 
 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();
 }
}

呵呵,float和double只能用来做科学计算或者是工程计算,在商业计算中我们要用 java.math.BigDecimal。这个才是解决的方式,我也试过了发现确实不错。不过话又说回来了,在.net里似乎没有发现有这类问题呀,能说他好么?我看未必,有时候封装的太好,功能太多,往往让人忘记了这些背后本来的面目:)

时间: 2024-12-26 10:59:24

Java中高精度问题的bigdecimal解决方式的相关文章

Java线程并发干扰问题及解决方式(锁对象)

并发干扰 使用多线程开发可以很好地提高代码效率,但是在多线程对同一数据资源的共享中,许多线程需要的同一个数据做读写修改操作,因此必然会存在竞争问题,而且这些问题通常会带来灾难性的后果,我们来看一个例子: 现在有一个多线程的银行账户管理系统,我的账户内有余额 1000 元,现在有两个线程对我的账户进行操作: 线程 A :存入500元 线程 B :取出200元 我们应该知道,在大多服务器操作系统中都采用抢占式调度(线程状态及属性),每个线程只有一个时间片来执行任务,当时间片用完后会立刻暂停运行交由其

redis的分布式解决方式--codis (转)

codis是豌豆荚开源的分布式server.眼下处于稳定阶段. 原文地址:https://github.com/wandoulabs/codis/blob/master/doc/tutorial_zh.md Codis 是一个分布式 Redis 解决方式, 对于上层的应用来说, 连接到 Codis Proxy 和连接原生的 Redis Server 没有明显的差别 (不支持的命令列表), 上层应用能够像使用单机的 Redis 一样使用, Codis 底层会处理请求的转发, 不停机的数据迁移等工作

对接第三方平台JAVA接口问题推送和解决

原文:对接第三方平台JAVA接口问题推送和解决 前言 本节所讲为实际项目中与第三方对接出现的问题最后还是靠老大解决了问题以此作为备忘录,本篇分为三小节,一小节解析Java加密接口数据,二小节解析XML文件需注意问题,最后一节则是请求Java Soap协议接口.因为第三方平台都是采用JAVA语言,所以这种情况应该对大家有所帮助. DES加密/解密Java接口 关于Java中序列化为XML文件就不搞了,我们首先需要讲解的是关于加密问题,由于是第三方是采用的DES加密,所以我们只讲解DES,有很多人可

乱码-JAVA 创建FILE怎么指定其编码方式?

问题描述 JAVA 创建FILE怎么指定其编码方式? File file=new File("D:CreateNewFolder1.txt"): 怎么设置1.txt的编码方式! Eclipse可以设置,电脑也可以设置1.txt的文件格式,但是为了程序的可移植性,怎么在写程序的时候就指定生成文件的编码格式解决乱码问题. 解决方案 File file = new File("c:a.test"); Writer writer = new OutputStreamWrit

Java读取、写入文件如何解决乱码问题_java

读取文件流时,经常会遇到乱码的现象,造成乱码的原因当然不可能是一个,这里主要介绍因为文件编码格式而导致的乱码的问题.首先,明确一点,文本文件与二进制文件的概念与差异. 文本文件是基于字符编码的文件,常见的编码有ASCII编码,UNICODE编码.ANSI编码等等.二进制文件是基于值编码的文件,你可以根据具体应用,指定某个值是什么意思(这样一个过程,可以看作是自定义编码.) 因此可以看出文本文件基本上是定长编码的(也有非定长的编码如UTF-8).而二进制文件可看成是变长编码的,因为是值编码嘛,多少

java defunct产生的原因和解决办法

解决 java defunct产生的原因和解决办法: 在很多时候,我们启动JAVA进程后,如果退出这个JAVA进程,在系统进程中仍然可以看到这个进程.这种情况一般出现在UNIX/LINUX系统,对于WIN平台出现情况较少. 这个进程在UNIX平台上,你即使kill进程号也不能杀掉它,但它仍然占用系统资源,成了真正的"僵尸". 产生的原因:以前我们下载JDK时,可以下载到JDK的SRC然后自己编译,现在的官方网站上已经找不到可以自己编译的JDK包,下载回来的JDK都是释放包,我们可以想象

java 读取dbf文件-java读取dbf文件问题怎样解决使用javadbf.jar

问题描述 java读取dbf文件问题怎样解决使用javadbf.jar 使用javadbf.jar读取dbf文件只能读取到字段名和第一行记录,然后遇到com.linuxense.javadbf.DBFException: Failed to parse Number: multiple points错误怎么解决

Java项目中连接数据库的几种方式和范例

连接数据库|项目 Java项目中连接数据库的几种方式和范例 1, 最原始的写法(也是一般初学者的写法) 首先import数据库连接基础类,然后其它的然后就简单了.实际操作过程中只需加载驱动程序类,之后调用sql语句就行了.以下是一个简单的程序例子. //Select.java import java.net.URL; import java.sql.*; class Select{ public static void main(String[] args){ try{ //创建连接URL St

Java编程那些事儿94——多线程实现方式1

12.2 多线程实现方式 线程的概念虽然比较复杂,但是在Java语言中实现线程却比较简单,只需要按照Java语言中对于线程的规定进行编程即可. 在实现线程编程时,首先需要让一个类具备多线程的能力,继承Thread类或实现Runnable接口的类具备多线程的能力,然后创建线程对象,调用对应的启动线程方法开始执行即可实现多线程编程. 在一个程序中可以实现多个线程,多线程编程指在同一个程序中启动了两个或两个以上的编程形式.当启动的线程数量比较多时,对于系统资源的要求比较多,所以程序支持的最大线程数量和