`
lynnwong
  • 浏览: 37385 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

非精确运算和精确运算

    博客分类:
  • java
 
阅读更多

非精确运算和精确运算


Bug场景

sku价格比较

    @Override
    public boolean checkSKUEqualPrice(ItemDO itemDO) {
        boolean result = true;
        List<ItemSkuDO> list=itemDO.getSkuList();
        //如果为空就是非sku商品
        if(list==null || list.isEmpty()){
            return result;
        }
        long tmpPrice = list.get(0).getPrice();
        for(ItemSkuDO sku:list){
             if (sku.getPrice()!=tmpPrice) {
                result= false;//存在不同的价格,校验失败
            }
        }
        return result && (tmpPrice == itemDO.getPrice()*100);
    }

如果熟悉价格比较的同学一眼就能看出这个bug的问题所在

return result && (tmpPrice == itemDO.getPrice()*100);

其中tmpPrice是以分为单位的价格long型,而item.getPrice()是double型的价格,在乘以100的操作后,将会返回一个近似double值,例如23.45*100,这个结果就不一定是2345,而是一个近似接近这个的值的数字。按照这种方法去比较,不能做到精确相等,bug就自然而然地产生了。 修改后的代码如下

@Override
public boolean checkSKUEqualPrice(ItemDO itemDO) {
    boolean result = true;
    List<ItemSkuDO> list=itemDO.getSkuList();
    //如果为空就是非sku商品
    if(list==null || list.isEmpty()){
        return result;
    }
    long tmpPrice = list.get(0).getPrice();
    for(ItemSkuDO sku:list){
         if (sku.getPrice()!=tmpPrice) {
            result= false;//存在不同的价格,校验失败
        }
    }
    return result && (tmpPrice == itemDO.getReservePriceLong());
}

通过取一个同样以分为单位的long型数来保证精确匹配。

这个bug算是个引子,接下来详细介绍下近似计算和精确计算在java中是如何实现的。

实数的表达方式

在介绍java的近似计算和精确计算之前,先简单介绍下一些实数的表达方式。

  • 定点数 定点数(Fixed Point Number),在这种表达方式中,小数点固定的位于实数所有数字中间的某个位置。货币的表达就可以使用这种方式,比如 99.00 或者 00.99 可以用于表达具有四位精度(Precision),小数点后有两位的货币值。由于小数点位置固定,所以可以直接用四位数值来表达相应的数值。SQL 中的 NUMBER 数据类型就是利用定点数来定义的。定点数表达法的缺点在于其形式过于僵硬,固定的小数点位置决定了固定位数的整数部分和小数部分,不利于同时表达特别大的数或者特别小的数。
  • 有理数 用两个整数的比值来表达实数。
  • 浮点数 利用科学计数法来表达实数,即用一个尾数(Mantissa ),一个基数(Base),一个指数(Exponent)以及一个表示正负的符号来表达实数。比如 123.45 用十进制科学计数法可以表达为 1.2345 × 10的2次方 ,其中 1.2345 为尾数,10 为基数,2 为指数。浮点数利用指数达到了浮动小数点的效果,从而可以灵活地表达更大范围的实数。 同样的数值可以有多种浮点数表达方式,比如上面例子中的 123.45 可以表达为 12.345 × 10的1次方,0.12345 × 10的3次方或者 1.2345 × 10的2次方。因为这种多样性,有必要对其加以规范化以达到统一表达的目标。规范的(Normalized)浮点数表达方式具有如下形式:

    ±d.dd...d × β 的e方 , (0 ≤ d i < β),其中 d.dd...d 即尾数,β 为基数,e 为指数。
    

    比如二进制数 1001.101 ,其规范浮点数表达为 1.001101 × 2的3次方

实数在计算机中的表达方式

计算机中是用有限的连续字节保存浮点数的。保存这些浮点数当然必须有特定的格式,Java 平台上的浮点数类型float 和 double 采纳了 IEEE 754 标准中所定义的单精度 32 位浮点数和双精度 64 位浮点数的格式。

举例:

1001.101对应于十进制的 9.625,按照标准可以表达成 1.001101 × 2的3次方

那么在计算机中是如何存储的,以单精度为例

  • 符号位 1

  • 指数存在正负的情况,指数可以为正数,也可以为负数。为了处理负指数的情况,实际的指数值按要求需要加上一个偏差(Bias)值作为保存在指数域中的值,单精度数的偏差值为 127,而双精度数的偏差值为 1023。例子中的指数在计算机中存储为 3+127= 130 二进制表示成 1000 0010

  • 尾数 IEEE 标准要求浮点数必须是规范的。这意味着尾数的小数点左侧必须为 1,因此我们在保存尾数的时候,可以省略小数点前面这个 1,从而腾出一个二进制位来保存更多的尾数。这样我们实际上用 23 位长的尾数域表达了 24 位的尾数。 例子中的尾数表示为00110100000000000000000

为什么浮点数不能保证精度

我们来看下实数和计算机存储的浮点数后之间的相互转化后,就能够明白为什么计算机中存储的浮点数不能保证精度了。

假定我们有一个 32 位的数据,用十六进制表示为 0xC0B40000,并且我们知道它实际上是一个单精度的浮点数。为了得到该浮点数实际表达的实数,我们首先将它变换为二进制形式:

  C    0    B    4    0    0    0    0
1100 0000 1011 0100 0000 0000 0000 0000

接着按照浮点数的格式切分为相应的域:

1  10000001 01101000000000000000000

符号域 1 意味着负数;指数域为 129 意味着实际的指数为 2 (减去偏差值 127);尾数域为 01101 意味着实际的二进制尾数为 1.01101 (加上隐含的小数点前面的 1)。所以,实际的实数为:

-1.01101 × 2的2次方
-5.625

接下来再看看实数向计算机中存储的浮点数转变的例子。

假定我们需要将实数 -9.625 表达为单精度的浮点数格式。方法是首先将它用二进制浮点数表达,然后变换为相应的浮点数格式。 首先,将小数点左侧的整数部分变换为其二进制形式,9 的二进制性形式为 1001。处理小数部分的算法是将我们的小数部分乘以基数 2,记录乘积结果的整数部分,接着将结果的小数部分继续乘以 2,并不断继续该过程:

0.625 × 2 = 1.25        1
0.25  × 2 = 0.5         0
0.5   × 2 = 1           1
0

当最后的结果为零时,结束这个过程。这时右侧的一列数字就是我们所需的二进制小数部分,即 0.101。这样,我们就得到了完整的二进制形式 1001.101。用规范浮点数表达为 1.001101 × 2的3次方。

因为是负数,所以符号域为 1。指数为 3,所以指数域为 3 + 127 = 130,即二进制的 10000010。尾数省略掉小数点左侧的 1 之后为 001101,右侧用零补齐。最终结果为:

1 10000010 00110100000000000000000

在上面这个我们有意选择的示例中,不断的将产生的小数部分乘以 2 的过程掩盖了一个事实。该过程结束的标志是小数部分乘以 2 的结果为 1,不难想象,很多小数根本不能经过有限次这样的过程而得到结果(比如最简单的 0.1)。我们已经知道浮点数尾数域的位数是有限的,为此,浮点数的处理办法是持续该过程直到由此得到的尾数足以填满尾数域,之后对多余的位进行舍入。换句话说,除了我们之前讲到的精度问题之外,十进制到二进制的变换也并不能保证总是精确的,而只能是近似值。事实上,只有很少一部分十进制小数具有精确的二进制浮点数表达。再加上浮点数运算过程中的误差累积,结果是很多我们看来非常简单的十进制运算在计算机上却往往出人意料。这就是最常见的浮点运算的"不准确"问题。

至此浮点数运算不准确性终于浮出水面。 那么,如果我们需要进行精确的运算呢?比如,电子商务中,一分钱都不能算错的情况下。接下俩就会介绍下java提供了什么样的机制来保证精确运算。

BigDecimal使用

对于精确计算,java提供了BigDecimal来保证,详细的BigDecimal使用可以参考JDK介绍,下面简单介绍下,使用BigDecimal的过程中可能会遇到的问题。

在使用BigDecimal类来进行计算的时候,主要分为以下步骤:

  1. 构建BigDecimal对象。
  2. 通过调用BigDecimal的加,减,乘,除等相应的方法进行算术运算。
  3. 把BigDecimal对象转换成float,double,int等类型。

BigDecimal构造

想了解BigDecimal的构造,先看两个构造函数的例子

 BigDecimal aDouble =new BigDecimal(1.22);
 System.out.println("construct with a double value: " + aDouble);
 BigDecimal aString = new BigDecimal("1.22");
 System.out.println("construct with a String value: " + aString);

你认为输出结果会是什么呢?如果你没有认为第一个会输出1.22,那么恭喜你答对了,输出结果如下:

construct with a doublevalue:1.2199999999999999733546474089962430298328399658203125
construct with a String value: 1.22

对于JDK的构造函数,有这样的描述

  1. 参数类型为double的构造方法的结果有一定的不可预知性。有人可能认为在Java中写入newBigDecimal(0.1)所创建的BigDecimal正好等于 0.1(非标度值 1,其标度为 1),但是它实际上等于0.1000000000000000055511151231257827021181583404541015625。这是因为0.1无法准确地表示为 double(或者说对于该情况,不能表示为任何有限长度的二进制小数)。这样,传入到构造方法的值不会正好等于 0.1(虽然表面上等于该值)。
  2. 另一方面,String 构造方法是完全可预知的:写入 newBigDecimal("0.1") 将创建一个 BigDecimal,它正好等于预期的 0.1。因此,比较而言,通常建议优先使用String构造方法。
  3. 当double必须用作BigDecimal的源时,请注意,此构造方法提供了一个准确转换;它不提供与以下操作相同的结果:先使用Double.toString(double)方法,然后使用BigDecimal(String)构造方法,将double转换为String。要获取该结果,请使用staticvalueOf(double)方法。

不可变性

BigInteger与BigDecimal都是不可变的(immutable)的,在进行每一步运算时,都会产生一个新的对象,所以a.add(b);虽然做了加法操作,但是a并没有保存加操作后的值,正确的用法应该是a=a.add(b);

BigDecimal和浮点型的性能比较

精确计算和非精确计算可以通过以下代码执行的结果来直接体现

1. import java.math.BigDecimal;  
2.   
3. public class BigDecimalEfficiency {  
4.   
5.     public static int REPEAT_TIMES = 1000000;  
6.   
7.     public static double computeByBigDecimal(double a, double b) {  
8.         BigDecimal result = BigDecimal.valueOf(0);  
9.         BigDecimal decimalA = BigDecimal.valueOf(a);  
10.         BigDecimal decimalB = BigDecimal.valueOf(b);  
11.         for (int i = 0; i < REPEAT_TIMES; i++) {  
12.             result = result.add(decimalA.multiply(decimalB));  
13.         }  
14.         return result.doubleValue();  
15.     }  
16.   
17.     public static double computeByDouble(double a, double b) {  
18.         double result = 0;  
19.         for (int i = 0; i < REPEAT_TIMES; i++) {  
20.             result += a * b;  
21.         }  
22.         return result;  
23.     }  
24.   
25.     public static void main(String[] args) {  
26.         long test = System.nanoTime();  
27.         long start1 = System.nanoTime();  
28.         double result1 = computeByBigDecimal(0.120000000034, 11.22);  
29.         long end1 = System.nanoTime();  
30.         long start2 = System.nanoTime();  
31.         double result2 = computeByDouble(0.120000000034, 11.22);  
32.         long end2 = System.nanoTime();  
33.   
34.         long timeUsed1 = (end1 - start1);  
35.         long timeUsed2 = (end2 - start2);  
36.         System.out.println("result by BigDecimal:" + result1);  
37.         System.out.println("time used:" + timeUsed1);  
38.         System.out.println("result by Double:" + result2);  
39.         System.out.println("time used:" + timeUsed2);  
40.   
41.         System.out.println("timeUsed1/timeUsed2=" + timeUsed1 / timeUsed2);  
42.     }  
43. }  

结果:

result by BigDecimal:1346400.00038148
time used:572537012
result by Double:1346400.000387465
time used:5280000
timeUsed1/timeUsed2=108

可见,二者的执行效率还是相差非常大的。

参考文献

http://www.cjsdn.net/Doc/JDK60/java/math/BigDecimal.html

http://singleant.iteye.com/blog/1159884

http://blog.csdn.net/jackiehff/article/details/8582449

http://blog.csdn.net/cppptr/article/details/573372

  • 大小: 6 KB
  • 大小: 21.8 KB
分享到:
评论

相关推荐

    java 精确的浮点数运算java 精确的浮点数运算java 精确的浮点数运算

    java 精确的浮点数运算java 精确的浮点数运算java 精确的浮点数运算 java 精确的浮点数运算java 精确的浮点数运算java 精确的浮点数运算 java 精确的浮点数运算java 精确的浮点数运算java 精确的浮点数运算 java ...

    java 精确的浮点数运算 工具类 java 精确的浮点数运算 工具类

    java 精确的浮点数运算 工具类 java 精确的浮点数运算 工具类java 精确的浮点数运算 工具类 java 精确的浮点数运算 工具类java 精确的浮点数运算 工具类 java 精确的浮点数运算 工具类java 精确的浮点数运算 工具类 ...

    ArithmeticUtils用于高精确处理常用的数学运算工具类

    ArithmeticUtils用于高精确处理常用的数学运算工具类 提供精确的加法运算 提供精确的减法运算 提供精确的乘法运算 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到小数点以后10位,以后的数字四舍五入 ...

    大整数精确运算的数据结构与基选择.pdf

    首先,标题“大整数精确运算的数据结构与基选择.pdf”指向的主题是大整数运算中数据结构的选择和基数(基)的选择。在这两个关键概念的基础上,文件内容展开了深入的讨论。 1. 大整数的概念及其重要性: 大整数是指...

    大数精确阶乘运算

    实现了大数精确的计算并输出,可以用于要求得到精确值的算法中。

    7-信号的运算和处理.ppt

    《信号的运算和处理》这一主题涵盖了集成运放组成的运算电路、模拟乘法器的应用以及有源滤波电路等多个方面,这些都是电子信息系统中信号处理的关键技术。本章内容旨在理解和掌握信号运算电路的设计、分析方法及其在...

    基于51单片机的超强科学计算器--精确浮点运算

    51单片机是实现这个科学计算器的核心硬件平台,它拥有足够的处理能力和内存资源来执行浮点运算和错误检测算法。51单片机的I/O端口可以连接显示器和按键,实现人机交互,而其内部的CPU则负责解析用户的输入,执行相应...

    double类型,精确的数据运算

    在提供的标题“double类型,精确的数据运算”中,我们可以推测这是一个针对`double`类型进行精确计算的类包。这类包通常通过内部使用大整数运算或者特殊的数据结构来存储和操作浮点数,以达到比内置`double`类型更高...

    精确地加减乘除运算

    该类提供了精确地加减乘除运算,可以到你想精确的位数

    SIMATIC 逻辑运算指令.zip

    在SIMATIC系统中,逻辑运算指令是编程的基础,它们使得PLC能够理解和执行复杂的逻辑操作,以实现对机械设备或生产过程的精确控制。这份名为"SIMATIC 逻辑运算指令.zip"的压缩包文件包含了一个名为"SIMATIC 逻辑运算...

    大数运算包含加,减,乘,除,取模,幂运算,模幂运算。支持十进制运算,二进制运算.zip

    大数运算处理的是超出普通整型数据类型范围的数值,通常涉及的运算包括加法、减法、乘法、除法、取模以及幂运算和模幂运算。在给定的"大数运算包含加,减,乘,除,取模,幂运算,模幂运算。支持十进制运算,二进制...

    大数运算-RSA-c语言大数运算库

    在C语言中,大数运算库扮演着至关重要的角色,它们提供了高效且精确的大整数计算功能。 “BigNum Math - Implementing Cryptographic Multiple Precision Arithmetic.pdf”可能是关于大数数学和加密多重精度算术的...

    cgal 模型布尔运算代码

    在三维布尔运算方面,CGAL提供了高效且精确的算法,可以处理模型的交、并、差集等。 2. **DLL(动态链接库)生成**: `DllFunction.cpp`、`dllmain.cpp`和相关的头文件表明这个项目是构建一个动态链接库(DLL)。...

    cpp-PostgreSQL精确的分数运算

    本主题聚焦于"cpp-PostgreSQL精确的分数运算",这意味着我们将探讨如何在C++中利用PostgreSQL进行高精度的分数运算。 在传统的浮点数运算中,由于浮点数表示的局限性,可能会导致精度损失。对于财务、科学计算等对...

    JavaScript小数点精确计算

    这个库可能提供了更高级的算法和数据结构,确保在JavaScript中进行精确的数学运算。安装这个库可以通过运行`npm install easy-calc`命令,然后在代码中导入并使用。 5. **使用其他数据格式**:例如,可以将数字转换...

    运算放大器详细的应用电路

    乘法器还用于平方运算、正弦波倍频、开方运算和调制等。电流-电压变换器和电压-电流变换器则实现了电流和电压之间的相互转换,适应不同的信号处理需求。 以上内容涵盖了运算放大器在电路设计中的多种应用,从基础的...

    计组实验报告-2运算器实验.docx

    最后,通过联机方式的实验,我们进一步理解了在实际计算机系统中,运算器如何与控制器协同工作,控制器如何生成精确的控制信号来驱动运算器执行指令,这为后续学习计算机体系结构提供了坚实的基础。 综上所述,此次...

    运算放大器权威指南

    这些设计工具和技术极大地提高了设计工程师的效率,使其能够更快速、更精确地完成电路设计。 此外,本书还介绍了运算放大器的设计方法。设计者需要掌握的不仅仅是运算放大器的工作原理,更关键的是如何将这些基本...

    mesh布尔运算

    **正文** 在三维建模和计算机图形学领域,"Mesh布尔运算"是一种强大的工具...在游戏开发、工业设计、3D打印等多个领域都有广泛应用,并且随着技术的发展,相关算法和工具也在不断优化,以满足更高的性能和精确度要求。

Global site tag (gtag.js) - Google Analytics