将小数位移至两倍


97

所以我将double设置为1234,我想将小数点后移至12.34

因此,我将.1乘以1234两次,就像这样

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

这将打印结果“ 12.340000000000002”

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



43
您没有理由x /= 100;吗?
马克·英格拉姆

Answers:


189

如果使用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

26
我不敢相信我没有想到要这样做!谢谢:-P
BlackCow 2011年

6
尽管可以用二进制格式精确表示100,但不能精确表示100除。因此,1234/100正如您所做的那样,写作实际上并没有对根本的问题做任何事情,它应该与写作完全相同1234 * 0.01
Brooks Moses

1
@Peter Lawrey:您能解释一下为什么这个数字是奇数还是偶数会影响舍入吗?我认为/ = 100和* =。01会是相同的,因为即使100是一个int值,由于类型强制,它仍将转换为100.0。
eremzeit

1
/100并且*0.01彼此相等,但不等同于OP *0.1*0.1
阿玛丹

1
我要说的是,平均乘以0.1两次会比乘以0.01一次产生更大的误差。但我会很高兴地承认@JasperBekkers的观点是100完全不同,它们完全可以用二进制表示。
阿玛丹(Amadan)2012年

52

否-如果要精确存储十进制值,请使用BigDecimaldouble简直不能精确地表示一个像0.1这样的数字,甚至比用有限数量的十进制数字精确地写出三分之一的值还要多。


46

如果只是格式化,请尝试使用printf

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

输出

12.34

8
评分较高的答案在技术上更具洞察力,但这是对OP问题的正确答案。我们通常不关心double 的轻微误差,因此BigDecimal是过大的,但是在显示时,我们通常希望确保输出与我们的直觉相符,因此System.out.printf()正确的方法是。
dimo414 2012年

28

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

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亿便士等于2,000万美元,我忘了世界杯或奥运会的溢价。

我发誓要保密。那好吧。在学术界,如果好的话,你可以发表;在行业中,您要保密。


12

您可以尝试整数表示

int i =1234;
int q = i /100;
int r = i % 100;

System.out.printf("%d.%02d",q, r);

5
@丹:为什么?对于金融应用程序(或什至很小的舍入错误是不可接受的任何其他应用程序),这是正确的方法,同时仍保持硬件级别的速度。(当然,它通常会包装在一个类中,通常不会每次
都写出来

7
该解决方案存在一个小问题-如果余数r小于10,则不会出现0填充,并且1204的结果为12.4。正确的格式字符串更类似于“%d。%02d”
jakebman 2012年

10

这是由计算机存储浮点数的方式引起的。他们没有完全做到这一点。作为程序员,您应该阅读此浮点指南,以熟悉处理浮点数的试验和麻烦。


啊,我只是写了一个解释,链接到完全相同的地方。+1。
流行

@哈哈勋爵,对不起。无论如何,我都被Skeeted了。:-)
CanSpice

我知道是为什么,但是我想知道是否存在一些创新的方式来移动小数位?因为可以将整齐的12.34存储为两倍,所以它不喜欢乘以.1
BlackCow

1
如果可以将double干净地存储12.34,您是否认为Java会做到这一点?不是。您将不得不使用其他一些数据类型(例如BigDecimal)。另外,为什么不将其除以100而不是循环执行呢?
CanSpice 2011年

是吗...是的,将其除以100会得到干净的12.34 ...谢谢:-P
BlackCow 2011年

9

有趣的是,很多帖子都提到要使用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 ,如果您使用String构造函数,则可以创建一个完全等于0.1 的BigDecimal 。


5

就在这里。每次执行两次操作都可能会丢失精度,但是每种操作的精度会有所不同,可以通过选择正确的操作顺序来最大程度地降低精度。例如,将一组数字相乘时,最好在相乘之前按指数对组进行排序。

任何有关数字处理的不错的书都对此进行了描述。例如: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

不,因为Java浮点类型(实际上是所有浮点类型)是大小和精度之间的折衷方案。尽管它们对于许多任务非常有用,但如果需要任意精度,则应使用BigDecimal

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.