女生直接问你你真棒什么意思_你的意思在哪里?

尽管几乎每种处理器和编程语言都支持浮点算术,但大多数程序员对此却很少注意。 这是可以理解的-我们大多数人很少要求使用非整数数字类型。 除了科学计算和偶尔的计时测试或基准测试外,它只是没有出现。 大多数开发人员同样会忽略java.math.BigDecimal提供的任意精度的十进制数字-绝大多数应用程序都没有使用它们。 但是,代表非整数的变量确实偶尔会潜入其他以整数为中心的程序中。 例如,JDBC使用BigDecimal作为SQL DECIMAL列的首选交换格式。

IEEE浮点数

Java语言支持两种原始浮点类型: floatdouble ,以及它们的包装类对应的FloatDouble 。 这些基于IEEE 754标准,该标准定义了32位浮点和64位双精度浮点二进制十进制数的二进制标准。

IEEE 754用科学计数法将浮点数表示为以2为基数的十进制数。 IEEE浮点数将1位用于数字的符号,将8位用于指数,将23位用于尾数或小数部分。 指数被解释为有符号整数,允许正负两个指数。 小数表示为二进制(以2为基)的十进制,表示最高位对应于值½(2 -1 ),第二位对应值¼(2 -2 ),依此类推。 对于双精度浮点,指数专用于11位,而尾数专用于52位。 IEEE浮点值的布局如图1所示。

图1. IEEE 754浮点布局
图1. IEEE 754浮点布局

因为任何给定的数字都可以用科学的表示法以多种方式表示,所以对浮点数进行了归一化,以便将它们表示为以2为基数的小数,并在小数点的左侧加上1,并根据需要调整指数以使该要求成立。 。 因此,例如,数字1.25的尾数为1.01,指数为0:
(-1)

数字10.0的尾数为1.01,指数为3:
(-1)

特殊号码

除了标准值的范围允许由编码(从1.4E-45到3.4028235E + 38为float ),有表示无穷大的特殊值,负无穷大, -0和NaN(其代表“不是一个数”)。 这些值的存在使错误条件(例如算术溢出,取负数的平方根并除以0可以产生可以在浮点值集中表示的结果。

这些特殊数字具有一些不同寻常的特征。 例如, 0-0是不同的值,但是在进行相等性比较时,它们被视为相等。 将非零数字除以无穷大将得出0 。 特殊数字NaN是无序的; 使用==<>运算符在NaN和其他浮点值之间进行任何比较都会产生false 。 如果f为NaN,则偶数(f == f)也会得出false 。 如果要将浮点值与NaN进行比较,请改用Float.isNaN()方法。 表1显示了无限和NaN的一些特性。

表1.特殊浮点值的属性

表达 结果
Math.sqrt(-1.0) -> NaN
0.0 / 0.0 -> NaN
1.0 / 0.0 -> Infinity
-1.0 / 0.0 -> -Infinity
NaN + 1.0 -> NaN
Infinity + 1.0 -> Infinity
Infinity + Infinity -> Infinity
NaN > 1.0 -> false
NaN == 1.0 -> false
NaN < 1.0 -> false
NaN == NaN -> false
0.0 == -0.01 -> true

原始浮点类型和包装类浮点具有不同的比较行为

更糟的是,在原始float类型和包装类Float之间,用于比较NaN和-0的规则不同。 对于float值,比较两个NaN值是否相等将产生false ,但是使用Float.equals()比较两个NaN Float对象将产生true 。 这样做的动机是,否则将不可能使用NaN Float对象作为HashMap的键。 类似地,虽然0-0在表示为float值时被视为相等,但使用Float.compareTo()0-0作为Float对象进行Float.compareTo()表明-0被视为小于0

浮点危害

由于无穷大,NaN和0的特殊行为,当应用于浮点数时,某些可能无害的变换和优化实际上是不正确的。 例如,虽然0.0-f-f等效似乎很明显,但是当f0时,这不是正确的。 还有其他类似的陷阱,表2中显示了其中一些。

表2.无效的浮点假设

这个表情... 不一定与此相同... 什么时候...
0.0 - f -f f为0
f < g ! (f >= g) f或g为NaN
f == f true f是NaN
f + g - g f g是无穷大或NaN

舍入误差

浮点运算很少精确。 虽然某些数字(例如0.5 )可以精确地表示为二进制(以2为底)的十进制数(因为0.5等于2 -1 ),但是其他数字(例如0.1 )则不能。 结果,浮点运算可能会导致舍入错误,产生的结果接近于(但不等于)您可能期望的结果。 例如,下面的简单计算得出2.600000000000001 ,而不是2.6

double s=0;

  for (int i=0; i<26; i++)
    s += 0.1;
  System.out.println(s);

类似地,将.1*26乘以得到的结果与将.1自身加26倍的结果不同。 当从浮点数转换为整数时,舍入误差会变得更加严重,因为转换为整数类型会丢弃非整数部分,即使对于“看起来”它们应该具有整数值的计算。 例如,以下语句:

double d = 29.0 * 0.01;
  System.out.println(d);
  System.out.println((int) (d * 100));

将产生作为输出:

0.29
  28

一开始可能不是您所期望的。

比较浮点数的准则

由于NaN具有异常的比较行为,并且几乎在所有浮点计算中实际上都保证了舍入误差,因此解释比较运算符对浮点值的结果非常棘手。

最好尝试完全避免浮点比较。 当然,这并非总是可能的,但是您应该意识到浮点比较的局限性。 如果必须比较浮点数以查看它们是否相同,则应将其差值的绝对值与某些预先选择的epsilon值进行比较,以便测试它们是否“足够接近”。 (如果您不知道基础测量的规模,则使用测试“ abs(a / b-1)<epsilon”可能比简单比较差异更健壮。)甚至测试一个值以查看是否大于或小于零是有风险的-由于累积的舍入误差,“假定为”计算得出的值略大于零实际上可能导致数字略小于零。

当比较浮点数时,NaN的无序性质增加了进一步的出错机会。 比较浮点数时避开无穷大和NaN周围许多陷阱的一个好的经验法则是显式测试一个值的有效性,而不是尝试排除无效值。 在清单1中,对于属性只能使用非负值的setter,有两种可能的实现。 第一个将接受NaN,第二个将不接受。 第二种形式是可取的,因为它可以显式测试您认为有效的值范围。

清单1.要求float值为非负的更好和更糟糕的方法
// Trying to test by exclusion -- this doesn't catch NaN or infinity
    public void setFoo(float foo) { 
      if (foo < 0) 
          throw new IllegalArgumentException(Float.toString(f));
        this.foo = foo;
    }

    // Testing by inclusion -- this does catch NaN 
    public void setFoo(float foo) { 
      if (foo >= 0 && foo < Float.INFINITY) 
        this.foo = foo;
      else 
        throw new IllegalArgumentException(Float.toString(f));
    }

请勿将浮点数用于确切值

一些非整数值(例如十进制的美元和美分)需要精确度。 浮点数不正确,操作它们将导致舍入错误。 结果,使用浮点数来表示精确的数量(例如货币金额)是一个坏主意。 使用浮点数进行美元和美分的计算是灾难的根源。 浮点数最适合用于测量之类的值,其值从一开始就根本不精确。

小数位数大

从JDK 1.3开始,Java开发人员可以使用另一种非整数的替代方法: BigDecimalBigDecimal是一个标准类,在编译器中没有特殊支持,它表示任意精度的十进制数并对其执行算术运算。 在内部, BigDecimal表示为任意精度的未缩放值和比例因子,该比例因子表示将小数点向左移动多少位以获得缩放值。 因此,由BigDecimal表示的数字是unscaledValue*10 -scale

BigDecimal值的算术由加,减,乘和除方法提供。 由于BigDecimal对象是不可变的,因此这些方法中的每一个都会产生一个新的BigDecimal对象。 结果,由于对象创建的开销, BigDecimal不适用于密集的数字计算,但它旨在表示精确的十进制数。 如果您要表示精确的数量(例如金额), BigDecimal非常适合此任务。

不是所有的equals方法都相等

像浮点类型一样, BigDecimal也有一些怪癖。 特别是,请小心使用equals()方法测试数字相等性。 equals()方法不会将两个表示相同数字但具有不同比例值(例如100.00100.000 )的BigDecimal值视为相等。 但是, compareTo()方法将认为它们相等,因此在对两个BigDecimal值进行数字比较时,应使用compareTo()而不是equals()

在某些情况下,任意精度的十进制算术仍然不足以保持准确的结果。 例如,将1除以9得到无限的重复小数.111111...因此, BigDecimal使您可以在执行除法运算时对舍入进行显式控制。 movePointLeft()方法支持精确除以十的幂。

使用BigDecimal作为交换类型

SQL-92包括DECIMAL数据类型,这是一种精确的数字类型,用于表示定点十进制数字,并对十进制数字执行基本的算术运算。 一些SQL方言更喜欢将此类型称为NUMERIC ,而另一些方言还包含MONEY数据类型,该数据类型定义为十进制数,在小数点右边有两个位置。

如果要将数字存储到数据库的DECIMAL字段中,或从DECIMAL字段中检索值,如何确保数字正确传输? 您不想使用JDBC PreparedStatementResultSet类提供的setFloat()getFloat()方法,因为浮点数和十进制数之间的转换可能会导致准确性丧失。 而是使用PreparedStatementResultSetsetBigDecimal()getBigDecimal()方法。

类似地,诸如Castor之类的XML数据绑定工具将使用BigDecimal为十进制值的属性和元素(在XSD架构中作为基本数据类型支持)生成getter和setter。

构造BigDecimal数

BigDecimal有几种可用的构造函数。 一个采用双精度浮点作为输入,另一个采用整数和比例因子,另一个采用十进制数字的String表示。 您应该小心使用BigDecimal(double)构造函数,因为它可以允许舍入错误在您不知不觉中潜入您的计算中。 而是使用基于整数或基于String的构造函数。

传递给JDBC setBigDecimal()方法时, BigDecimal(double)构造函数使用不当可能会在JDBC驱动程序中显示为奇怪的异常。 例如,考虑以下JDBC代码,该代码希望将数字0.01存储到十进制字段中:

PreparedStatement ps = 
    connection.prepareStatement("INSERT INTO Foo SET name=?, value=?");
  ps.setString(1, "penny");
  ps.setBigDecimal(2, new BigDecimal(0.01));
  ps.executeUpdate();

取决于您的JDBC驱动程序,这种看似无害的代码在执行时可能会引发一些令人困惑的异常,因为双精度近似值0.01会导致较大的比例值,这可能会使JDBC驱动程序或数据库感到困惑。 该异常将起源于JDBC驱动程序,但除非您知道二进制浮点数的局限性,否则可能几乎无法说明代码的实际错误。 而是使用BigDecimal("0.01")BigDecimal(1, 2) BigDecimal("0.01")构造BigDecimal以避免出现此问题,因为这两个方法中的任何一个都将导致精确的十进制表示形式。

摘要

在Java程序中使用浮点数和十进制数充满了陷阱。 浮点数和十进制数的表现不如整数好,并且您不能假设“应该”具有整数或精确结果的浮点计算实际上就可以做到。 最好保留使用浮点算法进行涉及根本不精确值的计算,例如测量。 如果需要表示定点数,例如美元和美分,请改用BigDecimal


翻译自: https://www.ibm.com/developerworks/java/library/j-jtp0114/index.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值