1. 概述
float和double类型的主要设计目的是为了科学计算和工程计算。它们执行二进制浮点运算,这是为了在广域数值范围上提供较为精确的快速近似计算而精心设计的。然而,它们没有提供完全精确的结果,所以不应该被用于要求精确结果的场合。float和double类型对于货币计算尤为不合适,因为要让一个float或者double精确地表示0.1(或者10的任何负数次方值)是不可能的,比如System.out.println(2.0-1.1)将会打印0.899999999999999,而不是你所希望的0.9,这种舍入错误产生的原因是浮点数实际上是用二进制系统实现的,而分数1/10在二进制系统中没有精确的表示,其道理就如同在十进制系统中无法精确表示1/3一样;再比如0.5在二进制系统中有精确表示,而0.55在二进制系统中没有精确表示。
许多数都是无法在有限的n内完全精确的表示出来的,我们只能利用更大的n值来更精确的表示这个数,但更大的n也只是更加逼近精确值而无法得到精确值。例如
- public static void main(String[] args) {
- System.out.println("100.5 - 100 = " + (100.5 - 100));
- System.out.println("100.5F - 100F = " + (100.5F - 100F));
- System.out.println("100.55 - 100.00 = " + (100.55 - 100.00));
- System.out.println("100.55F - 100.00F = " + (100.55F - 100.00F));
- System.out.println("new Double(100.55 - 100.00) = " + new Double(100.55 - 100.00));
- System.out.println("new Float(100.55 - 100.00) = " + new Float(100.55 - 100.00));
- System.out.println("(double)(100.55 - 100.00) = " + (double)(100.55 - 100.00));
- System.out.println("(float)(100.55 - 100.00) = " + (float)(100.55 - 100.00));
- System.out.println("2.0 - 1.1 = " + (2.0 - 1.1));
- System.out.println("2.0F - 1.1F = " + (2.0F - 1.1F));
- System.out.println("new Double(2.0 - 1.1) = " + new Double(2.0 - 1.1));
- System.out.println("new Float(2.0 - 1.1) = " + new Float(2.0 - 1.1));
- System.out.println("(double)(2.0 - 1.1) = " + (double)(2.0 - 1.1));
- System.out.println("(float)(2.0 - 1.1) = " + (float)(2.0 - 1.1));
- System.out.println("1.0 - 0.9 = " + (1.0 - 0.9));
- System.out.println("1.0F - 0.9F = " + (1.0F - 0.9F));
- System.out.println("new Double(1.0 - 0.9) = " + new Double(1.0 - 0.9));
- System.out.println("new Float(1.0 - 0.9) = " + new Float(1.0 - 0.9));
- System.out.println("(double)(1.0 - 0.9) = " + (double)(1.0 - 0.9));
- System.out.println("(float)(1.0 - 0.9) = " + (float)(1.0 - 0.9));
- /*
- * 输出结果:
- * 100.5 - 100 = 0.5
- * 100.5F - 100F = 0.5
- * 100.55 - 100.00 = 0.5499999999999972
- * 100.55F - 100.00F = 0.55000305
- * new Double(100.55 - 100.00) = 0.5499999999999972
- * new Float(100.55 - 100.00) = 0.55 (将一个Double转换为Float自然会有精度丢失, 所以舍入为0.55)
- * (double)(100.55 - 100.00) = 0.5499999999999972
- * (float)(100.55 - 100.00) = 0.55 (将一个double转换为float自然会有精度丢失, 所以舍入为0.55)
- *
- * 2.0 - 1.1 = 0.8999999999999999
- * 2.0F - 1.1F = 0.9 (这里用Float可以得到正确值, 我不知道原因, 你去问cpu吧. 只要记住这是一个巧合就可以了)
- * new Double(2.0 - 1.1) = 0.8999999999999999
- * new Float(2.0 - 1.1) = 0.9 (将一个Double转换为Float自然会有精度丢失, 所以舍入为0.9)
- * (double)(2.0 - 1.1) = 0.8999999999999999
- * (float)(2.0 - 1.1) = 0.9 (将一个double转换为float自然会有精度丢失, 所以舍入为0.9)
- *
- * 1.0 - 0.9 = 0.09999999999999998
- * 1.0F - 0.9F = 0.100000024
- * new Double(1.0 - 0.9) = 0.09999999999999998
- * new Float(1.0 - 0.9) = 0.1 (将一个Double转换为Float自然会有精度丢失, 所以舍入为0.1)
- * (double)(1.0 - 0.9) = 0.09999999999999998
- * (float)(1.0 - 0.9) = 0.1 (将一个double转换为float自然会有精度丢失, 所以舍入为0.1)
- * */
- }
2. 为什么浮点数会丢失精度(浮点数没办法用十进制来精确表示)
(1)十进制与二进制转换
要归咎于CPU表示浮点数的方法:2.4的二进制表示并非就是精确的2.4,而是最为接近2.3999999999999999;原因在于浮点数由两部分组成:指数和尾数,浮点数的值实际上是由cpu的某个数学公式计算得到的,所以您所遇到的精度损失会在任何操作系统和编程环境中遇到。
(2)类型失配
您可能混合了浮点数和双精度浮点数类型。请确定您在进行数学运算的时候所有的数据类型全部相同。注意:float类型的变量只有7位的精度,而double类型的变量有15位的精度。
3. 解决方法
(1)java.lang.Math中的public static int round(float a)和public static long round(double a)不能解决问题; java.lang.Object --> java.text.Format --> java.text.NumberFormat --> java.text.DecimalFormat 也不能解决问题
(2)使用BigDecimal、int或long(将小数转换为整数再进行计算,例如:将以美元为单位的货币计算改为以美分为单位的货币计算)。java.lang.Object --> java.lang.Number -- Direct Known Subclasses: BigDecimal, Byte, Double, Float, Integer, Long, Short
BigDecimal常用构造方法:
public BigDecimal(double val) --> Translates a double into a BigDecimal which is the exact decimal representation of the double's binary floating-point value.
public BigDecimal(String val) --> Translates the string representation of a BigDecimal into a BigDecimal
通常情况下,上面的那一个使用起来要方便一些。我们可能想都不想就用上了,会有什么问题呢?等到出了问题的时候,才发现上面哪个够造方法的详细说明中有这么一段。
The results of this constructor can be somewhat unpredictable. One might assume that writing new BigDecimal(0.1) in Java creates a BigDecimal which is exactly equal to 0.1 (an unscaled value of 1, with a scale of 1), but it is actually equal to 0.1000000000000000055511151231257827021181583404541015625. This is because 0.1 cannot be represented exactly as a double (or, for that matter, as a binary fraction of any finite length). Thus, the value that is being passed in to the constructor is not exactly equal to 0.1, appearances notwithstanding.
The String constructor, on the other hand, is perfectly predictable: writing new BigDecimal("0.1") creates a BigDecimal which is exactly equal to 0.1, as one would expect. Therefore, it is generally recommended that the String constructor be used in preference to this one.
When a double must be used as a source for a BigDecimal, note that this constructor provides an exact conversion; it does not give the same result as converting the double to a String using the Double.toString(double) method and then using the BigDecimal(String) constructor. To get that result, use the static valueOf(double) method.
原来我们如果需要精确计算,非要用String来够造BigDecimal不可!在《Effective Java》一书中的例子也是用String来够造BigDecimal的,但是书上却没有强调这一点,这也许是一个小小的失误吧。
使用BigDecimal加法运算步骤:(1)将两个浮点数转为String,然后构造成BigDecimal;(2)在其中一个上调用add()方法,传入另一个作为参数,然后把运算的结果(BigDecimal)再转换为浮点数。这样比较繁琐,我们可以自己写一个工具类Arith来简化操作:
- package edu.hust.test;
- import java.math.BigDecimal;
- /**
- * 由于Java的简单类型不能够精确的对浮点数进行运算,这个工具类提供精确的浮点数运算,包括加减乘除和四舍五入。
- * 所以提供以下静态方法:
- * 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)
- *
- */
- 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();
- }
- }
使用BigDecimal缺点:要创建对象并占用内存;其floatValue()方法有时会精度丢失而造成计算错误(所以一般小数计算尽量使用double)。
最后补充一点:也正因为此,浮点数不能用于循环判断,否则容易造成死循环。
相关推荐
例如,使用`%.8lf`格式化`float`可能无法避免精度丢失,而在适当情况下,对`double`使用`%.20lf`可以保留更多位数,减少精度损失。 3. **浮点数比较**: 直接使用`==`操作符比较两个`double`类型的浮点数是否相等...
6. **编程语言中的差异**:可能对比了不同编程语言中float和double类型的使用和特性。 7. **实际应用**:给出了使用float和double类型的实例,如科学计算、图形渲染、物理模拟等。 8. **性能考量**:讨论了在内存和...
5. **转换类型**:如果需要将`BigDecimal`转换回`double`或`float`,可以使用`doubleValue()`和`floatValue()`方法,但要注意这可能会丢失精度。 在实际开发中,`BigDecimal`的使用可以确保计算结果的准确无误,但...
在 Java 中,float 和 double 都是基本数据类型,它们在定义和使用时都有其特点和限制。 float 和 double 都可以用来表示实数,但是它们的精度和取值范围不同。 float 的精度是 8 位有效数字,取值范围是 10 的 -38 ...
例如,`float`和`double`类型的值在进行加减乘除运算后,结果可能与预期不符,尤其是在比较两个浮点数是否相等时,直接使用`==`可能会得到错误的结果。 为了解决这个问题,我们可以采用以下策略: 1. **避免直接...
`double`通常具有更高的精度和更大的数值范围,因此在需要更高精度的计算中更常被使用。 了解这些基础知识后,可以进一步探讨如何从二进制表示转换为十进制表示,以及如何进行浮点数的算术运算。浮点数的存储和运算...
在进行精确计算时,应该避免使用float和double类型,而应该使用BigDecimal类型或高精度的浮点数库。 四、结论 Java中的数值范围和浮点数精度问题是非常重要的知识点。在进行开发时,需要了解Java中的数值范围和...
总之,理解float和double的存储机制对于优化数值计算和避免精度错误至关重要。在进行浮点数操作时,开发者应当意识到这些内在的限制,并根据需要选择合适的精度类型,或者使用特殊的数学库来提高计算的精确度。同时...
Java 中的浮点数类型 float 和 double 在进行运算时会出现不精确的问题,这是因为计算机无法精确地表示十进制小数。这种问题不仅存在于 Java 中,在其它许多编程语言中也存在。 问题的提出: 编译运行下面的 Java ...
在C语言中,`float` 和 `double` 类型用于存储实数,即带有小数点的数值。这两种类型的主要区别在于它们在内存中的占用空间不同:`float` 类型通常占用4个字节(32位),而 `double` 类型则占用8个字节(64位)。...
在计算机内部,浮点数(如float和double)是用二进制表示的,但不是所有十进制小数都能精确地转换为二进制,这就会导致一些看似简单的数学运算结果出现误差。例如,0.1 + 0.2在二进制表示下并不等于0.3,这就是...
整型包括`Int`(32位)和`Long`(64位),而浮点型包括`Float`(单精度)和`Double`(双精度)。在四则运算中,数字默认被视为`Double`类型,除非明确声明为其他类型。 **2. 运算符** Kotlin提供了标准的四则运算符...
float和double只能用来做科学计算或者是工程计算,在商业计算中要用java.math.BigDecimal。BigDecimal所创建的是对象,我们不能使用传统的+、-、*、/等算术运算符直接对其对象进行数学运算,而必须调用其相对应的...
使用BigDecimal进行精确运算的步骤如下: 1. **创建BigDecimal对象**:你可以通过传入String表示的数值来创建BigDecimal对象,或者使用`BigDecimal.valueOf()`静态方法,将基本类型(如double)转换为BigDecimal。...
例如,如果业务逻辑要求精确存储和计算小数,那么选择DECIMAL而非FLOAT或DOUBLE会更为合适。 总的来说,理解和处理Java与MySQL中的小数问题,关键在于选择合适的数据类型、控制精度并适当地进行运算处理,以确保...
具有31个准确的十进制数字(106位)的浮点展开,也称为双精度双精度算术或模拟float128。 该库对于扩展精度的快速计算很有用。 例如,在轨道力学,计算几何和数值不稳定算法中,例如执行三角剖分,多边形修剪,求逆...
使用BigDecimal进行精确运算通常包括以下几个步骤: 1. **创建BigDecimal对象**:你可以通过`new BigDecimal(double)`构造函数或者`BigDecimal.valueOf(double)`静态方法将double类型的值转换为BigDecimal对象。...
在计算机编程领域,尤其是涉及到数值运算时,经常会遇到由于浮点数表示不精确而导致的计算误差问题。本篇文章将深入探讨在C#、SQL Server以及Oracle数据库中使用`double`类型进行计算时可能出现的误差,并通过具体的...