在 Java 中使用 double 保持精度

Retain precision with double in Java

提问人:Deinumite 提问时间:11/27/2008 最后编辑:MakotoDeinumite 更新时间:9/6/2023 访问量:260705

问:

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?

Java 浮点 精度

评论

5赞 Bernhard Barker 2/25/2018
相关新闻: 浮点数学坏了吗?

答:

2赞 Paul Tomblin 11/27/2008 #1

将所有内容乘以 100 并将其存储为美分。

评论

2赞 Paul Tomblin 11/28/2008
@Draemon - 看看上次编辑前的帖子 - 所有“shoppingTotal”和“calcGST”和“calcPST”的东西对我来说都像是钱。
5赞 Dustin 11/27/2008 #2

可以肯定的是,你可以把它变成一个三行的例子。:)

如果需要精确的精度,请使用 BigDecimal。否则,您可以使用整数乘以 10 ^ 来表示您想要的任何精度。

3赞 S.Lott 11/27/2008 #3

双精度是 Java 源代码中十进制数的近似值。你会看到双精度值(二进制编码值)和源值(十进制编码)之间不匹配的结果。

Java 正在生成最接近的二进制近似值。您可以使用 java.text.DecimalFormat 来显示更美观的十进制值。

1赞 Adam Jaskiewicz 11/27/2008 #4

使用 BigDecimal。它甚至允许您指定舍入规则(如ROUND_HALF_EVEN,如果两者的距离相同,则通过四舍五入到偶数邻居来最大程度地减少统计误差;即 1.5 和 2.5 四舍五入为 2)。

16赞 Steve Jessop 11/27/2008 #5

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

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

如果您使用的所有数字都是非重复的小数,并且您想要精确的结果,请使用 BigDecimal。或者正如其他人所说,如果你的值就像货币一样,它们都是 0.01 或 0.001 的倍数,或者其他什么,那么将所有内容乘以 10 的固定幂并使用 int 或 long(加法和减法是微不足道的:注意乘法)。

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

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

评论

4赞 MetroidFan2002 11/27/2008
不,不要将 double 与货币价值一起使用!你需要精确的钱,请改用BigDecimal。否则,你的答案是好的。任何需要精度的东西,都可以使用 BigDecimal,如果精度不是那么重要,则可以使用 float 或 double。
1赞 Steve Jessop 11/27/2008
该问题不再说明或暗示涉及金钱。我特别说要用BigDecimal或整数来赚钱。怎么了?
1赞 Steve Jessop 11/27/2008
而等于“不要用双倍的钱”是“不要用BigDecimal双倍换三分”。但有时问题涉及除法,在这种情况下,所有不能被所有分母的所有质因数整除的所有基数都差不多。
1赞 Brian Leahy 3/18/2010
.9999 = 1,如果精度小于 4 位有效数字
181赞 coobird 11/27/2008 #6

正如其他人所提到的,如果你想获得 11.4 的精确表示形式,你可能想要使用 BigDecimal 类。

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

Java 中的 和 原始类型是浮点数,其中数字存储为分数和指数的二进制表示。floatdouble

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

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

这些部分组合在一起以生成值的表示形式。double

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

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

、、 类型是定点数,是数的精确表示。与定点数不同,浮点数有时(可以安全地假设“大部分时间”)无法返回数字的精确表示。这就是为什么你最终得到的结果。bytecharintlong11.3999999999995.6 + 5.8

当需要精确的值(如 1.5 或 150.1005)时,您需要使用一种定点类型,该类型将能够准确表示数字。

正如已经多次提到的,Java 有一个 BigDecimal 类,它将处理非常大的数字和非常小的数字。

从该类的 Java API 参考:BigDecimal

变 任意精度有符号十进制 数字。BigDecimal 由 任意精度整数未缩放 value 和 32 位整数刻度。如果 零或正,刻度为 右侧的位数 小数点。如果为负数,则 未缩放的数字值为 乘以 10 的幂 否定规模。的值 数字表示 因此,BigDecimal 是 (unscaledValue × 10^ 刻度)。

关于 Stack Overflow 上有许多关于浮点数及其精度的问题。以下是您可能感兴趣的相关问题列表:

如果你真的想深入了解浮点数的细节,请看一下每个计算机科学家都应该知道的关于浮点运算的知识

评论

3赞 Sarah Phillips 4/13/2014
事实上,通常有 53 个有效位,因为“小数”点前的 1 隐含在除非规范化值之外的所有值中,从而提供了额外的精度。例如,3 存储为 (1.)1000... x 2^1,而 0.5 存储为 (1.)0000... x 2^-1 当值被非规范化(所有指数位均为零)时,有效位数可以而且通常会减少,例如 1 x 2^-1030 存储为 (0.)00000001 x 2^-1022,因此牺牲了七个有效数字来缩放。
2赞 Peter Lawrey 12/24/2015
应该注意的是,虽然比这种情况慢得多,但不需要它,因为 double 的精度为小数点后 15 位,您只需要四舍五入。BigDecimaldouble
2赞 user207421 4/1/2016
@PeterLawrey 它有 15 位十进制数字的精度,如果它们都在小数点之前。小数点后任何事情都可能发生,因为小数点和二进制分数的不可比性。
0赞 Peter Lawrey 4/1/2016
@EJP 你是对的,它有大约 15 位有效数字的精度。它可以是 16,但假设它是 15 或 14 更安全。
0赞 Shivam Sinha 4/5/2016
@PeterLawrey EJP 的更正是由于我的问题:stackoverflow.com/questions/36344758/...... 您能否详细解释一下为什么它不完全是 15 岁,以及它可以是 16 或 14 的情况?
5赞 Draemon 11/27/2008 #7

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

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

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

会给你:

11.40
0赞 Dark Castle 3/18/2010 #8

看看BigDecimal,它处理了像这样的浮点运算问题。

新调用如下所示:

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

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

4赞 Kevin Crowell 3/18/2010 #9

使用 java.math.BigDecimal

双精度在内部是二进制分数,因此它们有时无法将十进制分数表示为精确的小数点。

评论

1赞 dan04 3/18/2010
-1 盲目推荐 BigDecimal。如果你实际上不需要十进制算术(即,如果你用钱进行计算),那么BigDecimal对你没有帮助。它并不能解决你所有的浮点错误:你仍然需要处理 1/3*3=0.99999999999999999999999999999 和 sqrt(2)**2=1.999999999999999999999999999999999999。此外,BigDecimal 会带来巨大的速度损失。更糟糕的是,由于 Java 缺少运算符重载,您必须重写所有代码。
2赞 Newtopian 3/18/2010
@dan04 - 如果你用钱进行计算,为什么要使用浮动表示,知道其中的固有错误......由于没有美分的分数,您可以使用小数并计算美分,而不是使用近似美元,因此您有确切的美分金额。如果你真的想要美分的分数,请使用一个多头并计算数千美分。此外,OP没有提到无理数,他所关心的只是加法。在回答之前,请仔细阅读帖子并了解问题,这可能会为您省去一些尴尬。
3赞 dan04 3/18/2010
@Newtopian:我没什么好尴尬的。OP没有提到钱,也没有任何迹象表明他的问题有任何固有的小数。
0赞 Newtopian 3/18/2010
@ dan04 - 不,OP 没有......你做到了 并且盲目地提供了断章取义的意见,鉴于提供的细节数量很少,这很可能是一个完全可以接受的答案
6赞 dan04 3/18/2010 #10

你不能,因为 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。

评论

0赞 wrongusername 3/18/2010
7.3 在二进制中可能没有有限的表示,但是当我在 C++ 中尝试同样的事情时,我确实得到了 -7.3
3赞 dan04 3/18/2010
wrongusername:不,你没有。它只是以这种方式显示。使用“%.17g”格式(或者更好的是“%.51g”)来查看真正的答案。
5赞 John 3/18/2010 #11

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

Java.Math 有一些任意精度的算术工具。

评论

0赞 Matthew 3/18/2010
...例如 java.math 包。java.sun.com/j2se/1.5.0/docs/api/java/math/BigDecimal.html
2赞 Jay Askren 3/18/2010 #12

计算机以二进制形式存储数字,实际上不能准确表示 33.3333333333 或 100.0 等数字。这是使用双打的棘手问题之一。在向用户显示答案之前,您必须将答案四舍五入。幸运的是,在大多数应用程序中,您无论如何都不需要那么多小数位。

评论

0赞 Aly 3/18/2010
我正在做一些赔率计算,我希望有尽可能高的精度。但我知道有局限性
2赞 Spike 3/18/2010 #13

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

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

Apfloat for Java 声称可以使用任意精度的浮点数,但我从未使用过它。可能值得一看。http://www.apfloat.org/apfloat_java/

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

111赞 Stephen Canon 3/18/2010 #14

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

33.3333333333333285963817615993320941925048828125

将其除以 100 得到:

0.333333333333333285963817615993320941925048828125

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

0.3333333333333332593184650249895639717578887939453125

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

0.33333333333333326

评论

140赞 Stephen Canon 5/22/2010
对于将来阅读本文并对为什么答案与问题无关感到困惑的人:一些主持人决定将我(和其他人)回答的问题与这个相当不同的问题合并。
0赞 Michael Yaworski 5/8/2014
你怎么知道确切的双精度值?
0赞 Jaydee 2/16/2015
@mikeyaworski en.wikipedia.org/wiki/Double-precision_floating-point_format 查看双精度示例
9赞 Vinny 3/18/2010 #15

如果你真的需要精确数学,你可能想考虑使用 java 的 java.math.BigDecimal 类。这是一篇来自 Oracle/Sun 的关于 BigDecimal 案例的好文章。虽然你永远不能像有人提到的那样表示 1/3,但你可以决定你希望结果的精确程度。setScale() 是你的朋友。:)

好的,因为我现在手上有太多时间,这里有一个与您的问题相关的代码示例:

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,这里有一个更简洁的例子:

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
24赞 Viral Shah 3/18/2010 #16

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

编写加法、减法、乘法和除法的方法以及 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赞 Samir Talwar 3/18/2010
当然而且应该是s?为什么需要浮点精度?numeratordenominatorint
0赞 Viral Shah 3/18/2010
猜想这并不是真的必要,但它避免了在 toDouble 函数中强制转换,因此代码读起来更好。
6赞 Samir Talwar 3/18/2010
ViralShah:在处理数学运算时,它还可能引入浮点误差。鉴于这项工作的重点是避免这种情况,因此改变它似乎是谨慎的。
0赞 Viral Shah 3/18/2010
出于上面 Samir Talwar 提到的原因,编辑为使用 ints 而不是 doubles。
3赞 Salix alba 2/4/2014
这种分数的实现存在问题,因为它没有将它们简化为最简单的形式。2/3*1/2 给出 2/6,你真的希望答案是 1/3。理想情况下,在构造函数中,您希望找到分子和除数的 gcd,然后将两者除以它。
-2赞 Maciek D. 5/21/2010 #17

不要使用 BigDecimal 浪费你的 efford。在 99.99999% 的情况下,您不需要它。Java double 类型是近似的,但在几乎所有情况下,它都足够精确。请注意,您在第 14 位有效数字处有错误。这真的可以忽略不计!

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

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

评论

2赞 Maciek D. 5/21/2010
我认为他担心的是输出,而不是数字精度。如果你,BigDecimal 将无济于事。除以三。它甚至会让事情变得更糟......
0赞 user207421 4/1/2016
你永远不应该永远永远使用浮点来赚钱。我见过一个承包商被强制执行重大返工,尽管他被指示违反了这一规则。
5赞 sravan 12/7/2011 #18
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
}
1赞 Mr.Cat 11/15/2015 #19

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

// 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
0赞 user5734382 5/7/2018 #20

如果除了使用双精度值之外别无选择,可以使用以下代码。

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;
}
3赞 jackycflau 9/23/2019 #21

简短的回答:始终使用 BigDecimal 并确保使用带有 String 参数的构造函数,而不是双精度参数。

回到您的示例,以下代码将根据需要打印 11.4。

public class doublePrecision {
    public static void main(String[] args) {
      BigDecimal total = new BigDecimal("0");
      total = total.add(new BigDecimal("5.6"));
      total = total.add(new BigDecimal("5.8"));
      System.out.println(total);
    }
}
5赞 Harry Zhang 9/18/2020 #22
        /*
        0.8                     1.2
        0.7                     1.3
        0.7000000000000002      2.3
        0.7999999999999998      4.2
        */
        double adjust = fToInt + 1.0 - orgV;
        
        // The following two lines works for me. 
        String s = String.format("%.2f", adjust);
        double val = Double.parseDouble(s);

        System.out.println(val); // output: 0.8, 0.7, 0.7, 0.8
0赞 Vimal Raj 10/18/2021 #23

您可以执行以下操作!

System.out.println(String.format("%.12f", total));

如果在此处更改十进制值 %.12f

0赞 Hiro 5/31/2022 #24

到目前为止,我将其理解为主要目标,以纠正错误。doubledouble

寻找我的解决方案如何从“近似”错误值中获得正确的值 - 如果它是真正的浮点数,它会舍入最后一位数字 - 从所有数字开始计数 - 在点之前计数并尝试在点之后保持最大可能的数字 - 希望它在大多数情况下足够精确:

public static double roundError(double value) {
    BigDecimal valueBigDecimal = new BigDecimal(Double.toString(value));
    String valueString = valueBigDecimal.toPlainString();
    if (!valueString.contains(".")) return value;
    String[] valueArray = valueString.split("[.]");
    int places = 16;
    places -= valueArray[0].length();
    if ("56789".contains("" + valueArray[0].charAt(valueArray[0].length() - 1))) places--;
    //System.out.println("Rounding " + value + "(" + valueString + ") to " + places + " places");
    return valueBigDecimal.setScale(places, RoundingMode.HALF_UP).doubleValue();
}

我知道这是很长的代码,肯定不是最好的,也许有人可以修复它以使其更优雅。无论如何,它正在工作,请参阅示例:

roundError(5.6+5.8) = 11.399999999999999 = 11.4
roundError(0.4-0.3) = 0.10000000000000003 = 0.1
roundError(37235.137567000005) = 37235.137567
roundError(1/3) 0.3333333333333333 = 0.333333333333333
roundError(3723513756.7000005) = 3.7235137567E9 (3723513756.7)
roundError(3723513756123.7000005) = 3.7235137561237E12 (3723513756123.7)
roundError(372351375612.7000005) = 3.723513756127E11 (372351375612.7)
roundError(1.7976931348623157) = 1.797693134862316