关于浮点:在Java中保留double的精度

Retain precision with double in Java

1
2
3
4
5
6
7
8
9
public class doublePrecision {
    public static void main(String[] args) {

        double total = 0;
        total += 5.6;
        total += 5.8;
        System.out.println(total);
    }
}

在以上代码打印:

1
11.399999999999

我会把这两个打印(或被完全能够使用它为11.4)?


正如其他人提到的,如果您想要11.4的精确表示,那么您可能需要使用BigDecimal类。

现在,请解释一下为什么会发生这种情况:

Java中的EDCOX1 1和EDCOX1 2个原始类型是浮点数,其中的数目被存储为分数和指数的二进制表示。

更具体地说,双精度浮点值(如double类型)是64位值,其中:

  • 1位表示符号(正或负)。
  • 11位表示指数。
  • 52位表示有效数字(小数部分为二进制)。

这些部分结合在一起产生一个值的double表示。

(来源:维基百科:双精度)

有关如何在Java中处理浮点值的详细描述,请参阅第4.2.3节:Java语言规范的浮点类型、格式和值。

bytecharintlong类型为定点数,是数字的精确表示。与定点数字不同,浮点数有时(可以安全地假定"大部分时间")无法返回数字的精确表示。这就是为什么你最终选择了11.399999999999作为5.6 + 5.8的结果。

当需要精确的值时,例如1.5或150.1005,您将希望使用固定点类型之一,它将能够精确地表示数字。

正如已经提到过的几次,Java有一个EDCOX1×0类,它将处理非常大的数字和非常小的数字。

从Java API引用的EDCOX1,0类:

Immutable,
arbitrary-precision signed decimal
numbers. A BigDecimal consists of an
arbitrary precision integer unscaled
value and a 32-bit integer scale. If
zero or positive, the scale is the
number of digits to the right of the
decimal point. If negative, the
unscaled value of the number is
multiplied by ten to the power of the
negation of the scale. The value of
the number represented by the
BigDecimal is therefore (unscaledValue
× 10^-scale).

关于浮点数及其精度的栈溢出问题一直存在很多问题。以下是可能感兴趣的相关问题列表:

  • 为什么我看到一个双变量被初始化为21.4这样的值,比如21.399999618530273?
  • 如何在C++中打印大数字
  • 如何存储浮点?什么时候重要?
  • 使用浮动还是小数来计算美元金额?

如果你真的想了解浮点数的基本细节,看看每一个计算机科学家应该知道什么是浮点数算法。


当您输入一个双精度数时,例如33.33333333333333,您得到的值实际上是最接近的可表示双精度值,确切地说:

1
33.3333333333333285963817615993320941925048828125

除以100得到:

1
0.333333333333333285963817615993320941925048828125

它也不能表示为双精度数字,因此它再次四舍五入到最接近的可表示值,即:

1
0.3333333333333332593184650249895639717578887939453125

当您打印出这个值时,它会再次四舍五入到17位十进制数字,给出:

1
0.33333333333333326


如果只想将值作为分数处理,可以创建一个包含分子和分母字段的分数类。

编写加法、减法、乘法和除法以及todouble方法。这样可以避免计算时出现浮动。

编辑:快速实施,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class Fraction {

private int numerator;
private int denominator;

public Fraction(int n, int d){
    numerator = n;
    denominator = d;
}

public double toDouble(){
    return ((double)numerator)/((double)denominator);
}


public static Fraction add(Fraction a, Fraction b){
    if(a.denominator != b.denominator){
        double aTop = b.denominator * a.numerator;
        double bTop = a.denominator * b.numerator;
        return new Fraction(aTop + bTop, a.denominator * b.denominator);
    }
    else{
        return new Fraction(a.numerator + b.numerator, a.denominator);
    }
}

public static Fraction divide(Fraction a, Fraction b){
    return new Fraction(a.numerator * b.denominator, a.denominator * b.numerator);
}

public static Fraction multiply(Fraction a, Fraction b){
    return new Fraction(a.numerator * b.numerator, a.denominator * b.denominator);
}

public static Fraction subtract(Fraction a, Fraction b){
    if(a.denominator != b.denominator){
        double aTop = b.denominator * a.numerator;
        double bTop = a.denominator * b.numerator;
        return new Fraction(aTop-bTop, a.denominator*b.denominator);
    }
    else{
        return new Fraction(a.numerator - b.numerator, a.denominator);
    }
}

}


注意,如果您使用有限精度的十进制算术,并且想要处理1/3:0.33333333*3是0.99999999,而不是1.00000000,您也会遇到同样的问题。

不幸的是,5.6、5.8和11.4不是二进制的整数,因为它们涉及五分之一。所以它们的浮点表示并不精确,就像0.3333不完全是1/3。

如果您使用的所有数字都是非循环小数,并且您希望得到精确的结果,请使用bigdecimal。或者如其他人所说,如果你的值和金钱一样,都是0.01或0.001的倍数,或者其他值,那么用10的固定幂乘以所有值,然后用int或long(加法和减法都很简单:注意乘法)。

但是,如果您对二进制计算感到满意,但只想以稍微友好的格式打印出来,那么可以尝试使用java.util.FormatterString.format。在格式字符串中,指定的精度小于double的完整精度。对于10个有效数字,例如,11.3999999999是11.4,因此在二进制结果非常接近只需要几个小数位的值的情况下,结果几乎同样准确,更易于人类阅读。

要指定的精度在一定程度上取决于你对数字做了多少数学运算——一般来说,你做的越多,积累的错误就越多,但有些算法积累的速度比其他算法快得多(它们被称为"不稳定",而不是舍入错误的"稳定")。如果你所要做的就是添加一些值,那么我想只要精确到小数点后一位就可以解决问题了。实验。


如果你真的需要精确的数学,你可能想研究一下Java的java. Math.BigDeCiMax类。下面是Oracle/Sun关于BigDecimal的一篇好文章。虽然你永远不能像别人提到的那样代表1/3,但你可以准确地决定你想要的结果有多精确。setscale()是你的朋友。:)

好吧,因为现在我手头有太多时间,下面是一个与您的问题相关的代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.math.BigDecimal;
/**
 * Created by a wonderful programmer known as:
 * Vincent Stoessel
 * [email protected]
 * on Mar 17, 2010 at  11:05:16 PM
 */

public class BigUp {

    public static void main(String[] args) {
        BigDecimal first, second, result ;
        first = new BigDecimal("33.33333333333333")  ;
        second = new BigDecimal("100") ;
        result = first.divide(second);
        System.out.println("result is" + result);
       //will print : result is 0.3333333333333333


    }
}

为了插入我最喜欢的新语言,groovy,这里有一个更整洁的例子:

1
2
3
4
5
6
7
import java.math.BigDecimal

def  first =   new BigDecimal("33.33333333333333")
def second = new BigDecimal("100")


println"result is" + first/second   // will print: result is 0.33333333333333


您遇到了double类型的精度限制。

数学有一些精确的算术运算设备。


很肯定你可以把它做成一个三行的例子。:)

如果需要精确的精度,请使用bigdecimal。否则,您可以使用整数乘以10^


正如其他人所指出的,并不是所有的十进制值都可以表示为二进制,因为十进制是基于10的幂,二进制是基于2的幂。

如果精度很重要,请使用bigdecimal,但如果您只希望友好的输出:

1
2
System.out.printf("%.2f
"
, total);

会给你:

1
11.40


不能,因为7.3没有二进制的有限表示。最接近的是20547673329987789/2**48=7.3+1/1407374883553280。

请参阅http://docs.python.org/tutorial/floatingpoint.html了解进一步的解释。(在Python网站上,但是Java和C++有相同的问题。

解决方案取决于您的具体问题是什么:

  • 如果你只是不喜欢看到所有这些干扰数字,那么就修正你的字符串格式。显示的有效数字不得超过15位(或浮点数不得超过7位)。
  • 如果数字的不精确性破坏了"if"语句,那么应该写if(abs(x-7.3)<公差),而不是if(x==7.3)。
  • 如果你在用钱,那么你可能真正想要的是小数不动点。存储整数美分或任何最小的货币单位。
  • (非常不可能)如果需要超过53个有效位(15-16个有效位)的精度,那么使用高精度浮点类型,如bigdecimal。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void getRound() {
    // this is very simple and interesting
    double a = 5, b = 3, c;
    c = a / b;
    System.out.println(" round  val is" + c);

    //  round  val is  :  1.6666666666666667
    // if you want to only two precision point with double we
            //  can use formate option in String
           // which takes 2 parameters one is formte specifier which
           // shows dicimal places another double value
    String s = String.format("%.2f", c);
    double val = Double.parseDouble(s);
    System.out.println(" val is :" + val);
    // now out put will be : val is :1.67
}


使用java.math.bigdecimal

双精度数在内部是二进制分数,因此有时不能将小数部分表示为精确的小数。


计算机以二进制形式存储数字,不能准确地表示33.33333333或100.0这样的数字。这是使用双打的一个棘手问题。在向用户显示答案之前,您必须先对其进行四舍五入。幸运的是,在大多数应用程序中,无论如何都不需要这么多小数点。


把所有的都乘以100,然后把它存储在一个很长的分币里。


浮点数不同于实数,因为对于任何给定的浮点数,都有下一个更高的浮点数。与整数相同。1到2之间没有整数。

无法将1/3表示为浮点数。下面有一个浮球,上面有一个浮球,它们之间有一定的距离。1/3在这个空间。

AppLoad用于Java声明使用任意精度浮点数字,但我从未使用过。可能值得一看。http://www.apfloat.org/apfloat_java/

以前这里也问过类似的问题Java浮点高精度库


使用BigDecimal。它甚至允许您指定舍入规则(例如,舍入到偶数邻居(如果两者距离相同,即1.5和2.5舍入到2),从而将统计误差最小化)。


双倍是您的Java源代码中十进制数的近似值。您将看到double(二进制编码的值)和源(十进制编码的值)之间不匹配的结果。

Java产生最接近的二进制近似值。您可以使用java.text.deciMalfat显示外观更好的十进制值。


如果除了使用双精度值之外没有其他选择,可以使用下面的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static double sumDouble(double value1, double value2) {
    double sum = 0.0;
    String value1Str = Double.toString(value1);
    int decimalIndex = value1Str.indexOf(".");
    int value1Precision = 0;
    if (decimalIndex != -1) {
        value1Precision = (value1Str.length() - 1) - decimalIndex;
    }

    String value2Str = Double.toString(value2);
    decimalIndex = value2Str.indexOf(".");
    int value2Precision = 0;
    if (decimalIndex != -1) {
        value2Precision = (value2Str.length() - 1) - decimalIndex;
    }

    int maxPrecision = value1Precision > value2Precision ? value1Precision : value2Precision;
    sum = value1 + value2;
    String s = String.format("%." + maxPrecision +"f", sum);
    sum = Double.parseDouble(s);
    return sum;
}


查看bigdecimal,它处理处理处理类似浮点运算的问题。

新呼叫如下:

1
term[number].coefficient.add(co);

使用setscale()设置要使用的小数位数精度。


为什么不使用数学类中的round()方法?

1
2
3
4
// The number of 0s determines how many digits you want after the floating point
// (here one digit)
total = (double)Math.round(total * 10) / 10;
System.out.println(total); // prints 11.4


不要用bigdecimal浪费你的效率。99.99999%的情况下你不需要它。Java双类型是CARCE近似,但在几乎所有的情况下,它是足够精确的。请注意,您的第14个有效数字有错误。这真是微不足道!

要获得良好的输出,请使用:

1
2
System.out.printf("%.2f
"
, total);