用Java保持精度翻倍


Answers:


151

正如其他人所提到的BigDecimal,如果您希望精确表示为11.4,则可能要使用该类。

现在,对造成这种情况的原因进行一些解释:

Java中的floatdouble类型是浮点数,其中的数字以小数和指数的二进制表示形式存储。

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

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

这些部分被组合以产生double值的表示。

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

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

bytecharintlong类型的定点数字,这是数字的精确representions。与定点数不同,浮点数有时(可以安全地假定为“大部分时间”)不能返回数字的精确表示形式。这就是为什么你最终的原因11.399999999999为的结果5.6 + 5.8

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

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

BigDecimal该类的Java API参考中:

不变的,任意精度的带符号十进制数字。BigDecimal由任意精度的整数无标度值和32位整数标度组成。如果为零或正数,则小数位数是小数点右边的位数。如果为负,则该数字的未标度值乘以十,即标度取反的幂。因此,由BigDecimal表示的数字的值为(unscaledValue×10 ^ -scale)。

关于浮点数及其精度的堆栈溢出问题很多。这是您可能感兴趣的相关问题列表:

如果您真的想深入了解浮点数的细节,请查看每位计算机科学家应该了解的浮点算术知识


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,因此牺牲了七个有效数字来进行缩放。
莎拉·菲利普斯

1
应该注意的是,虽然BigDecimaldouble这种情况要慢得多,但因为double具有15个小数位的精度,所以您不需要四舍五入。
彼得·劳瑞

2
@PeterLawrey它具有小数点后15 位数字的准确性,如果他们都是之前的小数点。由于小数和二进制分数的不可通约性,在小数点后可能发生任何事情。
罗恩侯爵

@EJP是的,它的精度约为15个有效数字。它可以是16,但它的安全假设它是15或者是14
彼得Lawrey

@PeterLawrey EJP的更正是由于我的问题:stackoverflow.com/questions/36344758 / ...您能否解释一下为什么它不完全是15,以及在什么情况下可能是16或14?
Shivam Sinha

103

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

33.3333333333333285963817615993320941925048828125

除以100可得出:

0.333333333333333285963817615993320941925048828125

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

0.3333333333333332593184650249895639717578887939453125

当您打印出该值时,它将再次舍入到17位十进制数字,从而得到:

0.33333333333333326

114
对于任何将来会阅读此书并且对答案为何与该问题无关的人都感到困惑:某个主持人决定合并我(和其他人)已经回答过的这个问题,而这个问题却与以往完全不同。
斯蒂芬·佳能

您如何知道确切的双精度值?
Michael Yaworski 2014年


23

如果只想将值作为分数处理,则可以创建一个包含分子和分母字段的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
当然numerator并且denominator应该是intS' 为什么要浮点精度?
萨米尔·塔瓦尔

猜猜这不是真正必要,但可以避免强制转换toDouble函数,因此代码读起来更好。
Viral Shah 2010年

5
ViralShah:在处理数学运算时,它还可能引入浮点错误。鉴于此练习的目的是为了避免这种情况,因此更改它似乎是审慎的做法。
萨米尔·塔尔瓦

出于上述Samir Talwar提到的原因,将其编辑为使用int而不是double。
Viral Shah 2010年

3
分数的这种实现方式存在问题,因为它没有将其简化为最简单的形式。2/3 * 1/2给出2/6,您真正希望答案是1/3。理想情况下,在构造函数中,您要查找分子和除数的gcd并将其除以。
Salix alba 2014年

15

如果使用有限精度的十进制算术,并且要处理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,因此在二进制结果非常接近仅需要小数位的值的情况下,结果将几乎一样准确且更易于理解。

指定的精度在某种程度上取决于您对数字进行的数学处理-一般来说,您做的越多,累积的错误就越多,但是某些算法的累积速度比其他算法快(它们被称为“不稳定”算法,因为在舍入错误方面与“稳定”相对)。如果您要做的只是添加一些值,那么我猜想只降低小数点后一位的精度就可以解决问题。实验。


3
不,具有货币价值的双重使用!您需要金钱来保证精度,请改用BigDecimal。否则,您的答案是好的。任何需要精度的东西都可以使用BigDecimal,如果精度不是那么重要,则可以使用float或double。
MetroidFan2002

1
这个问题不再说明或暗示涉及金钱。我专门说要使用BigDecimal或整数作为货币。有什么问题?
史蒂夫·杰索普

1
等于“不要用双倍赚钱”等于“不要用BigDecimal 用两倍赚钱”。但是有时问题会涉及除法,在这种情况下,所有分母的所有素数都不能将其除以的所有基数大约同样不好。
史蒂夫·杰索普

1
如果您的精度小于4个有效数字,则.9999 = 1
Brian Leahy 2010年

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

5

可以肯定的是,您可以将其做成三行示例。:)

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


5

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

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

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

会给你:

11.40


5

您不能,因为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-错误的用户名2010年

2
错误的用户名:不,您不知道。它只是以这种方式显示。使用“%.17g”格式(或者更好的是“%.51g”)查看真实答案。
dan04 '18

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

3

使用java.math.BigDecimal

双精度字是内部的二进制分数,因此有时不能表示精确到十进制的十进制分数。


1
-1用于盲目推荐BigDecimal。如果您实际上不需要十进制算术(即,如果您使用金钱进行计算),则BigDecimal不会为您提供帮助。它不能解决您所有的浮点错误:您仍然必须处理1/3 * 3 = 0.9999999999999999999999999999和sqrt(2)** 2 = 1.999999999999999999999999999。此外,BigDecimal带来巨大的速度损失。更糟糕的是,由于Java缺少运算符重载,因此您必须重写所有代码。
dan04 '18

2
@ dan04-如果您用金钱进行计算,为什么要使用浮动表示法知道其中的固有误差...。由于没有分数,您可以使用十进制并计算分数,而不是使用近似的美元,因此您有确切的分数金额。如果您真的想要一小部分美分,请使用a并计算数千美分。此外,OP没有提到无理数,他所担心的只是加法。在回答之前,请仔细阅读文章并理解问题,可能会为您节省一些尴尬。
Newtopian 2010年

3
@Newtopian:我没什么好尴尬的。OP 没有提到金钱,也没有任何迹象表明他的问题具有固有的十进制性。
10年

@ dan04-没有操作人员没有...您做了,并且由于提供的详细信息很少,盲目的提出了最有可能是一个完全可以接受的答案的背景调查
Newtopian 2010年

2

将所有内容乘以100,并将其存储在很长的美分中。


2
@Draemon-在最后一次编辑之前看一下帖子-所有“ shoppingTotal”,“ calcGST”和“ calcPST”这些东西对我来说都是钱。
Paul Tomblin's

2

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


我正在做一些赔率计算,我希望尽可能拥有最高的精度。但我知道这里有局限性
-Aly

2

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

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

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

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


1

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

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


1

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


1

简短答案:始终使用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);
    }
}

0

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

新呼叫将如下所示:

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

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


0

为什么不使用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

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

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

-1

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

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

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

2
我认为他担心输出而不是数值精度。如果您愿意,BigDecimal将无济于事。除以三。它甚至可能使情况变得更糟……
Maciek D.

您永远不要永远不要将浮点数用于金钱。我看到承包商对大型承包商进行了重大返工,尽管承包商如此指示,却违反了这一规定。
罗恩侯爵
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.