在Java中,使用double类型可以保持精度。

184
public class doublePrecision {
    public static void main(String[] args) {

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

上面的代码输出:

11.399999999999

如何使其只打印(或者能够使用它作为) 11.4?


5
相关内容:浮点数运算是否存在问题? - Bernhard Barker
24个回答

181
如其他人所提到的,如果你想要对11.4进行精确表示,你可能会想要使用BigDecimal类。
现在,让我简单解释一下为什么会发生这种情况:
在Java中,floatdouble是浮点数的原始类型,其中数字以二进制的形式存储为分数和指数的表示。
更具体地说,双精度浮点值(如double类型)是一个64位的值,其中:
- 1位表示符号(正数或负数)。 - 11位表示指数。 - 52位表示有效数字(二进制的小数部分)。
这些部分组合在一起,形成了一个double值的表示。
(来源:Wikipedia: Double precision
有关Java中如何处理浮点数的详细描述,请参阅Java语言规范的第4.2.3节:浮点类型、格式和值bytecharintlong类型是定点数,它们是数字的精确表示。与定点数不同,浮点数有时(可以安全地假设是“大多数情况下”)无法返回数字的精确表示。这就是为什么5.6 + 5.8的结果会变成11.399999999999的原因。
当需要精确的值,例如1.5或150.1005时,您应该使用其中一种定点类型,它能够精确表示该数字。
正如已经多次提到的,Java有一个BigDecimal类,可以处理非常大和非常小的数字。
从Java API参考中可以看到BigDecimal类的定义:

不可变的、任意精度的有符号十进制数。BigDecimal由一个任意精度的整数无标度值和一个32位整数标度组成。如果为零或正数,则标度是小数点右侧的数字位数。如果为负数,则数字的无标度值乘以10的标度的负数次幂。因此,BigDecimal所表示的数字的值为(无标度值×10^-标度)。

在Stack Overflow上有很多关于浮点数及其精度的问题。以下是一些相关问题的列表,可能会引起您的兴趣:

如果你真的想深入了解浮点数的细节,可以看看《每个计算机科学家都应该了解的浮点数算术》


3
实际上,通常有53个有效位,因为在非规格化值之外的所有值中,在“小数点”前面的1都是隐含的,这样就多了一位精度。例如,3被存储为(1.)1000... x 2^1,而0.5被存储为(1.)0000... x 2^-1。当值变成非规格化值(所有指数位都为零)时,可能会有更少的有效数字,通常也确实如此。例如,1 x 2^-1030 被存储为 (0.)00000001 x 2^-1022,这样就牺牲了七个有效数字来进行比例缩放。 - Sarah Phillips
2
需要注意的是,虽然在这种情况下BigDecimaldouble慢得多,但它并不需要,因为double具有15位小数的精度,您只需要四舍五入即可。 - Peter Lawrey
2
@PeterLawrey 它具有15个十进制数字的精度,如果它们全部在小数点之前。由于十进制和二进制分数的不可比性,小数点后面可能发生任何事情。 - user207421
@EJP 你是正确的,它有大约15个有效数字的精度。它可以是16,但更安全的做法是假设它是15或者可能是14。 - Peter Lawrey
@PeterLawrey 因为我的问题,EJP进行了更正:https://dev59.com/aloV5IYBdhLWcg3wXNzj#36345535 你能否详细解释一下为什么它不是完全的15,以及在什么情况下它可能是16或14? - Shivam Sinha

110

当你输入一个double类型的数字,例如33.33333333333333,实际上得到的值是最接近的可以表示为双精度浮点数的值,该值正好是:

33.3333333333333285963817615993320941925048828125

将其除以100得:

0.333333333333333285963817615993320941925048828125

这也不能表示为双精度数字,所以它再次四舍五入到最接近的可表示值,其值正好是:

0.3333333333333332593184650249895639717578887939453125

当你将这个值打印出来时,它会再次四舍五入保留17位小数,结果为:

0.33333333333333326

139
对于未来阅读此文并感到困惑为何答案与问题无关的任何人:某些版主决定将我(和其他人)回答的问题与这个完全不同的问题合并。 - Stephen Canon
你怎么知道精确的 double 值? - Michael Yaworski
@mikeyaworski 参见双精度浮点格式示例:http://en.wikipedia.org/wiki/Double-precision_floating-point_format - Jaydee

24

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

编写add、subtract、multiply和divide方法以及toDouble方法。这样,在计算过程中可以避免使用浮点数。

编辑:快速实现如下,

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
肯定应该将 numeratordenominator 定义为 int 类型,为什么需要浮点精度呢? - Samir Talwar
猜想这不是必须的,但它避免了在 toDouble 函数中进行强制类型转换,因此代码看起来更好。 - Viral Shah
6
ViralShah说:当处理数学运算时,它也可能引入浮点错误。考虑到这个练习的目的正是要避免这种情况,修改它似乎是明智的选择。 - Samir Talwar
已根据Samir Talwar的建议,将double更改为int。 - Viral Shah
3
这个分数的实现存在问题,因为它没有将其化简为最简形式。例如2/3*1/2会得到2/6,而真正想要的答案是1/3。理想情况下,在构造函数中,你需要找到分子和分母的最大公约数,并将两者都除以它。 - Salix alba
它给出了错误:不兼容的类型:从double到int可能会有损失的转换。 - shareef

16

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

不幸的是,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.399999999999是11.4,因此结果将几乎与需要仅几个小数位的值相等,并且更易于人类阅读。

指定的精度取决于您对数字进行了多少数学运算-一般来说,您做的越多,错误积累得就越多,但有些算法比其他算法累积得更快(它们被称为相对于舍入误差是“不稳定”的而不是“稳定”的)。如果您只是将几个值相加,则我猜只要减少一个小数位的精度即可解决问题。试验一下。


3
不要在货币值中使用双精度类型!货币需要精度,应该使用BigDecimal。否则,你的答案是正确的。任何需要精度的情况下,都应该使用BigDecimal,如果精度不是很重要,可以使用float或double。 - MetroidFan2002
1
问题不再说明或暗示涉及金钱。我明确说要使用BigDecimal或整数来处理货币。有什么问题吗? - Steve Jessop
1
等同于“不要用double来处理货币”,就是“不要使用BigDecimal或double来处理三分之一”。但有时问题涉及除法,在这种情况下,所有底数不能被所有分母的所有质因数整除的情况都差不多。 - Steve Jessop
1
如果您的精度小于4个有效数字,则.9999 = 1。 - Brian Leahy

9
如果您真正需要精确计算,可以考虑使用Java的java.math.BigDecimal类。这里是Oracle/Sun关于BigDecimal的论述。虽然像某人提到的那样,您永远无法表示1/3,但您可以决定结果的精确度。setScale()是您的好朋友.. :)
好的,因为我现在手头时间太多了,这里有一个与您问题相关的代码示例:
import java.math.BigDecimal;
/**
 * Created by a wonderful programmer known as:
 * Vincent Stoessel
 * xaymaca@gmail.com
 * 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,这里提供一个更简洁的示例:

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

6
您不能这样做,因为7.3在二进制中没有有限的表示形式。最接近的是2054767329987789/2 ** 48 = 7.3 + 1/1407374883553280。
请参考http://docs.python.org/tutorial/floatingpoint.html了解更多解释(虽然这是Python网站,但Java和C ++也存在相同的“问题”)。
解决方案取决于您的问题具体是什么:
- 如果您只是不喜欢看到所有这些噪音数字,请修复字符串格式。 不要显示超过15个有效数字(或7个浮点数)。 - 如果您的数字的不准确性破坏了像“if”语句这样的东西,那么您应该编写 if (abs(x - 7.3) < TOLERANCE) 而不是 if (x == 7.3)。 - 如果您正在处理货币,则实际上可能需要的是小数定点。 存储以分或您的货币的最小单位的整数数量。 - (非常不可能)如果您需要超过53个有效位(15-16个有效数字)的精度,则使用高精度浮点类型,例如BigDecimal。

7.3在二进制中可能没有有限的表示,但是当我在C++中尝试同样的操作时,我确实得到了-7.3。 - wrongusername
3
不,你不需要。它只是以那种方式显示。使用"%.17g"格式(最好使用"%.51g")来查看真正的答案。 - dan04

5

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

Java.Math 提供了一些任意精度算术工具。


...例如java.math包。http://java.sun.com/j2se/1.5.0/docs/api/java/math/BigDecimal.html - Matthew

5

我相信你可以把这个例子简化成三行代码。

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


5

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

如果精度很重要,请使用BigDecimal,但如果你只想要友好的输出:

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

我会为您提供:

11.40

5
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
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接