【问题标题】:Retain precision with double in Java在 Java 中使用 double 保持精度
【发布时间】:2010-09-24 06:40:28
【问题描述】:
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 floating-point double precision


【解决方案1】:

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

【讨论】:

  • @Draemon - 看看上次编辑之前的帖子 - 所有“shoppingTotal”、“calcGST”和“calcPST”的东西对我来说都像是钱。
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

      使用 BigDecimal。它甚至可以让您指定舍入规则(如 ROUND_HALF_EVEN,如果两者距离相同,则会通过舍入到偶数邻居来最小化统计误差;即 1.5 和 2.5 都舍入为 2)。

      【讨论】:

        【解决方案5】:

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

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

        如果您使用的所有数字都是非循环小数,并且您想要精确的结果,请使用 BigDecimal。或者正如其他人所说,如果你的值就像钱一样,它们都是 0.01 或 0.001 或其他东西的倍数,那么将所有值乘以 10 的固定幂并使用 int 或 long(加法和减法是琐碎的:注意乘法)。

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

        指定的精度在一定程度上取决于您对数字做了多少数学运算 - 通常,您做的越多,累积的错误就越多,但有些算法的累积速度比其他算法快得多(它们被称为“在舍入误差方面不稳定”而不是“稳定”)。如果您所做的只是添加一些值,那么我猜只删除小数点后一位就可以解决问题。实验。

        【讨论】:

        • 不,不要不要将双精度值与货币值一起使用!您需要精确的金钱,请改用 BigDecimal。否则,你的答案是好的。任何你需要精确的东西,使用 BigDecimal,如果精度不是那么重要,你可以使用 float 或 double。
        • 该问题不再说明或暗示涉及金钱。我特别说要使用 BigDecimal 或整数来赚钱。有什么问题?
        • 并且等于“不要使用 double 来赚钱”是“不要使用 BigDecimal double 来做三分之二”。但有时问题涉及除法,在这种情况下,不能被所有分母的所有素因数整除的所有基都差不多。
        • .9999 = 1 如果您的精度小于 4 个有效数字
        【解决方案6】:

        正如其他人所提到的,如果您想获得 11.4 的精确表示,您可能希望使用 BigDecimal 类。

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

        Java 中的floatdouble 基本类型是floating point 数字,其中数字存储为分数和指数的二进制表示。

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

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

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

        (来源:Wikipedia: Double precision

        有关如何在 Java 中处理浮点值的详细说明,请参阅 Java 语言规范的 Section 4.2.3: Floating-Point Types, Formats, and Values

        bytecharintlong 类型是 fixed-point 数字,它们是数字的精确表示。与定点数不同,浮点数有时(可以安全地假设“大部分时间”)无法返回数字的精确表示。这就是为什么你最终得到11.399999999999 作为5.6 + 5.8 的结果的原因。

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

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

        来自BigDecimal 类的 Java API 参考:

        不可变, 任意精度有符号十进制 数字。 BigDecimal 由一个 未缩放的任意精度整数 值和 32 位整数刻度。如果 零或正,比例是 右边的位数 小数点。如果为负,则 数字的未缩放值是 乘以十的幂 否定尺度。的价值 所代表的数字 因此 BigDecimal 是 (unscaledValue × 10^-比例)。

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

        如果您真的想深入了解浮点数的细节,请查看What Every Computer Scientist Should Know About Floating-Point Arithmetic

        【讨论】:

        • 事实上,通常有 53 个有效位,因为“小数点”前的 1 对除非规范化值之外的所有值都隐含,从而提供了额外的精度。例如3 存储为 (1.)1000... x 2^1 而 0.5 存储为 (1.)0000... x 2^-1 当值被非规范化(所有指数位为零)时,可以,并且通常会是更少的有效数字,例如1 x 2^-1030 存储为 (0.)00000001 x 2^-1022,因此牺牲了七个有效数字来进行缩放。
        • 应该注意,虽然BigDecimaldouble 慢得多,但在这种情况下不需要它,因为 double 具有 15 个小数位的精度,您只需要四舍五入。
        • @PeterLawrey 如果它们都在小数点之前,则它具有15 位小数数字 的准确性。由于十进制和二进制分数的不可通约性,小数点后的任何事情都可能发生。
        • @EJP 你是对的,它有大约 15 位有效数字的精度。它可以是 16,但假设它是 15 或 14 会更安全。
        • @PeterLawrey EJP 的更正是由于我的问题:stackoverflow.com/questions/36344758/… 你能否详细说明为什么它不完全是 15 以及它可能是 16 或 14 的情况?
        【解决方案7】:

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

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

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

        会给你:

        11.40
        

        【讨论】:

          【解决方案8】:

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

          新调用如下所示:

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

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

          【讨论】:

            【解决方案9】:

            使用 java.math.BigDecimal

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

            【讨论】:

            • -1 用于盲目推荐 BigDecimal。如果您实际上并不需要十进制算术(即,如果您正在用金钱进行计算),那么 BigDecimal 对您没有帮助。它并不能解决所有浮点错误:您仍然需要处理 1/3*3=0.9999999999999999999999999999 和 sqrt(2)**2=1.999999999999999999999999999。此外,BigDecimal 会带来巨大的速度损失。更糟糕的是,由于 Java 缺乏运算符重载,您必须重写所有代码。
            • @dan04 - 如果你用钱计算,为什么要使用浮动表示,知道它的固有错误......因为没有美分的分数,你可以使用小数并计算美分而不是使用近似美元你有确切的一分钱。如果您真的想要美分的分数,请使用 long 并计算数千美分。此外,OP没有提到无理数,他所关心的只是加法。在回答之前,请仔细阅读帖子并理解问题,可能会为您节省一些尴尬。
            • @Newtopian:我没有什么好尴尬的。 OP 没有提到钱,也没有任何迹象表明他的问题有任何内在的小数。
            • @ dan04 - 不,OP 没有......你做到了 鉴于提供的细节数量很少,盲目地提出了断章取义的意见,认为这是一个完全可以接受的答案
            【解决方案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)
            • 如果您使用金钱,那么您可能真正想要的是十进制固定点。存储整数美分或任何货币的最小单位。
            • (非常不可能)如果您需要超过 53 个有效位(15-16 个有效数字)的精度,请使用高精度浮点类型,例如 BigDecimal。

            【讨论】:

            • 7.3 在二进制中可能没有有限的表示,但是当我在 C++ 中尝试同样的事情时,我确实得到了 -7.3
            • 错误的用户名:不,你没有。它只是以这种方式显示。使用“%.17g”格式(或者更好的是“%.51g”)来查看真正的答案。
            【解决方案11】:

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

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

            【讨论】:

            【解决方案12】:

            计算机以二进制形式存储数字,实际上不能准确地表示诸如 33.333333333 或 100.0 之类的数字。这是使用双打的棘手问题之一。在将答案显示给用户之前,您将不得不对其进行四舍五入。幸运的是,在大多数应用程序中,您不需要那么多小数位。

            【讨论】:

            • 我正在做一些赔率计算,我希望获得尽可能高的精度。但我知道有限制
            【解决方案13】:

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

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

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

            之前在这里问过一个类似的问题 Java floating point high precision library

            【讨论】:

              【解决方案14】:

              当你输入一个双精度数时,例如33.33333333333333,你得到的值实际上是最接近的可表示的双精度值,也就是:

              33.3333333333333285963817615993320941925048828125
              

              除以 100 得到:

              0.333333333333333285963817615993320941925048828125
              

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

              0.3333333333333332593184650249895639717578887939453125
              

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

              0.33333333333333326
              

              【讨论】:

              • 致任何将来阅读本文并对为什么答案与问题无关的人感到困惑:一些版主决定将我(和其他人)回答的问题与此合并,相当不同,问题。
              • 你怎么知道确切的双精度值?
              【解决方案15】:

              如果您真的需要精确数学,您可能需要考虑使用 java 的 java.math.BigDecimal 类。这是 Oracle/Sun 在the case for 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
              

              【讨论】:

                【解决方案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);
                    }
                }
                
                }
                

                【讨论】:

                • 当然numeratordenominator 应该是ints?为什么需要浮点精度?
                • 猜这不是真的必要,但它避免了在 toDouble 函数中进行强制转换,因此代码阅读效果更好。
                • ViralShah:在处理数学运算时,它还可能引入浮点错误。鉴于此练习的目的是完全避免这种情况,因此更改它似乎是谨慎的。
                • 编辑为使用整数而不是双精度数,因为上面 Samir Talwar 提到的原因。
                • 这种分数的实现存在问题,因为它没有将它们简化为最简单的形式。 2/3*1/2 在您真正希望答案为 1/3 的地方给出 2/6。理想情况下,您希望在构造函数中找到分子和除数的 gcd 并将两者除以。
                【解决方案17】:

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

                要获得不错的输出,请使用:

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

                【讨论】:

                • 我认为他担心的是输出,而不是数值精度。如果您例如, BigDecimal 将无济于事。除以三。它甚至会使事情变得更糟......
                • 你永远不应该永远不要用浮点数来赚钱。我已经看到对违反此规则的承包商实施了重大返工,尽管得到了这样的指示。
                【解决方案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
                }
                

                【讨论】:

                  【解决方案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
                  

                  【讨论】:

                    【解决方案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;
                    }
                    

                    【讨论】:

                      【解决方案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);
                          }
                      }
                      

                      【讨论】:

                        【解决方案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
                        

                        【讨论】:

                          【解决方案23】:

                          您可以执行以下操作!

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

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

                          【讨论】:

                            猜你喜欢
                            • 1970-01-01
                            • 1970-01-01
                            • 2018-05-09
                            • 2020-06-22
                            • 2011-02-11
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            相关资源
                            最近更新 更多