【问题标题】:Moving decimal places over in a double将小数位移动到双精度
【发布时间】:2011-06-23 16:05:58
【问题描述】:

所以我有一个等于 1234 的双精度集,我想移动一个小数位使其成为 12.34

为此,我将 0.1 乘以 1234 两次,有点像这样

double x = 1234;
for(int i=1;i<=2;i++)
{
  x = x*.1;
}
System.out.println(x);

这将打印结果“12.340000000000002”

有没有一种方法,无需简单地将其格式化为两位小数,就可以正确存储双精度 12.34?

【问题讨论】:

标签: java double decimal


【解决方案1】:

如果您使用doublefloat,则应该使用舍入,否则会出现一些舍入错误。如果您不能这样做,请使用BigDecimal

您遇到的问题是 0.1 不是一个精确的表示,并且通过执行两次计算,您正在加剧该错误。

但是,100可以准确表示,所以试试:

double x = 1234;
x /= 100;
System.out.println(x);

哪个打印:

12.34

这是因为Double.toString(d) 代表您执行少量舍入,但并不多。如果您想知道不四舍五入会是什么样子:

System.out.println(new BigDecimal(0.1));
System.out.println(new BigDecimal(x));

打印:

0.100000000000000005551115123125782702118158340454101562
12.339999999999999857891452847979962825775146484375

简而言之,无论您是否明确执行此操作,对于浮点中的合理答案,舍入都是不可避免的。


注意:x / 100x * 0.01 在舍入误差方面并不完全相同。这是因为第一个表达式的舍入误差取决于 x 的值,而第二个表达式中的 0.01 具有固定的舍入误差。

for(int i=0;i<200;i++) {
    double d1 = (double) i / 100;
    double d2 = i * 0.01;
    if (d1 != d2)
        System.out.println(d1 + " != "+d2);
}

打印

0.35 != 0.35000000000000003
0.41 != 0.41000000000000003
0.47 != 0.47000000000000003
0.57 != 0.5700000000000001
0.69 != 0.6900000000000001
0.7 != 0.7000000000000001
0.82 != 0.8200000000000001
0.83 != 0.8300000000000001
0.94 != 0.9400000000000001
0.95 != 0.9500000000000001
1.13 != 1.1300000000000001
1.14 != 1.1400000000000001
1.15 != 1.1500000000000001
1.38 != 1.3800000000000001
1.39 != 1.3900000000000001
1.4 != 1.4000000000000001
1.63 != 1.6300000000000001
1.64 != 1.6400000000000001
1.65 != 1.6500000000000001
1.66 != 1.6600000000000001
1.88 != 1.8800000000000001
1.89 != 1.8900000000000001
1.9 != 1.9000000000000001
1.91 != 1.9100000000000001

注意:这与系统(或电源)中的随机性无关。这是由于表示错误,每次都会产生相同的结果。 double 的精度是有限的,并且是以 2 为底而不是以 10 为底的,因此可以用十进制精确表示的数字通常不能以 2 为底精确表示。

【讨论】:

  • 我不敢相信我一开始就没有想到这样做!谢谢:-P
  • 虽然100可以用二进制格式准确表示,但除以100不能准确表示。因此,正如您所做的那样,写1234/100 并没有真正解决根本问题——它应该完全等于写1234 * 0.01
  • @Peter Lawrey:你能解释一下为什么这个数字是奇数还是偶数会影响四舍五入?我认为 /=100 和 *=.01 是相同的,因为即使 100 是一个 int,由于类型强制,它无论如何都会被转换为 100.0。
  • /100*0.01 彼此等价,但不等同于 OP 的 *0.1*0.1
  • 我要说的是,平均而言,乘以 0.1 两次会引入比乘以 0.01 一次更大的误差;但我很乐意承认@JasperBekkers 的观​​点,即 100 是不同的,完全可以二进制表示。
【解决方案2】:

是的,有。对于每个双重操作,您可能会失去准确性,但每次操作的准确性会有所不同,并且可以通过选择正确的操作顺序来最小化。例如,当对一组数字进行乘法运算时,最好在乘法之前按指数对集合进行排序。

任何关于数字运算的体面书籍都描述了这一点。例如: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

回答你的问题:

使用除法而不是乘法,这样可以得到正确的结果。

double x = 1234;
for(int i=1;i<=2;i++)
{
  x =  x / 10.0;
}
System.out.println(x);

【讨论】:

    【解决方案3】:

    如果是只是格式,试试 printf

    double x = 1234;
    for(int i=1;i<=2;i++)
    {
      x = x*.1;
    }
    System.out.printf("%.2f",x);
    

    输出

    12.34
    

    【讨论】:

    • 评分较高的答案在技术上更具洞察力,但这是对 OP 问题的正确答案。我们通常不关心 double 的 slight 不准确,所以 BigDecimal 是多余的,但是在显示时我们经常希望确保我们的输出符合我们的直觉,所以 System.out.printf() 是正确的方法。
    【解决方案4】:

    你可以尝试整数表示

    int i =1234;
    int q = i /100;
    int r = i % 100;
    
    System.out.printf("%d.%02d",q, r);
    

    【讨论】:

    • @Dan:为什么?这是金融应用程序(或任何其他即使是微小的舍入误差也不可接受的应用程序)的正确方法,同时仍保持硬件级速度。 (当然,它会被包裹在一个类中,通常不会每次都写出来)
    • 这个解决方案有一个小问题 - 如果余数 r 小于 10,则不会发生 0 填充,而 1204 将产生 12.4 的结果。正确的格式化字符串更类似于“%d.%02d”
    【解决方案5】:

    有趣的是,许多帖子都提到使用 BigDecimal,但没有人费心根据 BigDecimal 给出正确答案?因为即使使用 BigDecimal,您仍然可能出错,正如这段代码所示

    String numstr = "1234";
    System.out.println(new BigDecimal(numstr).movePointLeft(2));
    System.out.println(new BigDecimal(numstr).multiply(new BigDecimal(0.01)));
    System.out.println(new BigDecimal(numstr).multiply(new BigDecimal("0.01")));
    

    给出这个输出

    12.34
    12.34000000000000025687785232264559454051777720451354980468750
    12.34
    

    BigDecimal 构造函数特别提到使用 String 构造函数比使用数字构造函数更好。最终精度也受可选的 MathContext 影响。

    根据 BigDecimal Javadoc 有可能创建一个 恰好等于 0.1 的 BigDecimal,前提是您使用 String 构造函数。

    【讨论】:

      【解决方案6】:

      在财务软件中,通常使用整数表示便士。在学校,我们被教导如何使用定点而不是浮点,但这通常是 2 的幂。以整数存储便士也可以称为“定点”。

      int i=1234;
      printf("%d.%02d\r\n",i/100,i%100);
      

      在课堂上,我们一般被问到可以在基数中精确表示哪些数字。

      对于base=p1^n1*p2^n2...,您可以表示任何 N,其中 N=n*p1^m1*p2^m2。

      base=14=2^1*7^1... 你可以表示 1/7 1/14 1/28 1/49 但不能表示 1/3

      我了解财务软件——我将 Ticketmaster 的财务报告从 VAX asm 转换为 PASCAL。他们有自己的 formatln() 和便士代码。转换的原因是 32 位整数已经不够用了。 +/- 20 亿便士是 2000 万美元,我忘记了,这用于世界杯或奥运会。

      我发誓要保密。那好吧。在学术界,如果它是好的,你发表;在工业界,你保守秘密。

      【讨论】:

        【解决方案7】:

        不,因为Java floating point types(实际上是所有浮点类型)是大小和精度之间的权衡。虽然它们对很多任务都非常有用,但如果您需要任意精度,您应该使用BigDecimal

        【讨论】:

          【解决方案8】:

          这是由计算机存储浮点数的方式引起的。他们不完全这样做。作为程序员,您应该阅读this floating-point guide 以熟悉处理浮点数的考验和磨难。

          【讨论】:

          • 啊,我只是在写一个链接到同一个地方的解释。 +1。
          • @Lord 哈哈,对不起。无论如何,我得到了Skeeted。 :-)
          • 我想这就是为什么,但我想知道是否有一些创造性的方法来移动小数位?因为可以将 12.34 干净地存储在 double 中,所以它只是不喜欢乘以 .1
          • 如果可以将 12.34 干净地存储在 double 中,您不认为 Java 会做到吗?它不是。您必须使用其他一些数据类型(如 BigDecimal)。另外,为什么不直接除以 100 而不是循环进行呢?
          • Do'h...是的,将其除以 100 得到干净的 12.34...谢谢 :-P
          【解决方案9】:

          否 - 如果您想准确存储十进制值,请使用 BigDecimaldouble 只是不能准确地表示像 0.1 这样的数字,就像你可以用有限的十进制数字准确地写出三分之一的值一样。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多