关于c ++:我的程序的不合逻辑的结果


Illogical results of my program

本问题已经有最佳答案,请猛点这里访问。

我正在努力解决这个问题:

将浮点数转换为十进制。例如,如果输入为12.345,则输出应为12345

…这就是我的解决方案:

1
2
3
4
5
6
7
8
double d=0;
cout<<"Enter a double :";
cin>>d;

while(d-(int)d > 0.)
    d*=10;

cout<<"Result :"<<d<<endl;

我认为算法理论上是正确的,但实际上有些价值观对我不起作用!!

例如:

1.123工作正常,程序给出1123,

但是1.12不起作用,会产生无限循环!!

问题出在哪里,如何才能使我的程序正常工作?


问题是浮点运算中涉及的舍入:不能用二进制的十进制数字表示所有可以表示的数字。

例如,假设您在1/3上使用系数3运行算法。1/3(十进制)表示为0.3333,因此您的算法将计算以下内容:

1
2
3
4
5
 0.3333 * 3 =  0.9999
 0.9999 * 3 =  2.9997
 2.9997 * 3 =  8.9991
 8.9991 * 3 = 26.9973
26.9993 * 3 = 80.9919

如你所见,分数部分并没有如你所期望的那样消失。

就像1/3不能用十进制表示一样,1/10不能用二进制表示,所以你不能期望10*(1/10)用二进制计算为1,就像你不能期望3*(1/3)用十进制计算为1一样。


下面的解决方案是受到米莉穆斯的评论的启发-所以谢谢!

将输入作为字符串读取。将任何尾随的零移到小数点右边。删除小数。转换为int。

注意-这尤其有效,因为它避免了小数表示中的有限精度问题-这是一个您总是会遇到的问题,因为您从人类可读的浮点数字开始。但是,如果您需要传递一个不精确的浮点数表示形式的函数,您最好希望使用您的方法,并在有限的迭代次数后停止循环。

更好的方法是减去,然后将余数乘以10。这样,循环总是可以终止(这是数字到字符串表示函数的工作方式)。如果要在末尾转换为整数,则需要确保不会溢出。如果将数字收集到另一个字符串中,就不必担心溢出。

简而言之,正如所述,这个问题没有一个确切的解决方案,但是上面的内容应该能让您对要采取的方法有一些了解。

这里的编辑是一些执行我描述的代码(不同的方法-并演示溢出问题):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <iostream>
#include <math.h>

int main(void){
  double d=0;
  char buffer[100];
  char buf2[100];
  long int i,j;

  std::cout<<"Enter a double :";
  fgets(buffer, 100, stdin);
  strncpy(buf2, buffer, 100);

  // method 1: use string manipulation to get the answer
  i = (int)(strstr(buffer,".") - buffer);
  if (i >= 0)
  {
    j = strlen(buffer)-1;
    while(buffer[j-1]=='0')
    {
      j--;
    }
    for(; i<j; i++)
    {
      buffer[i]=buffer[i+1];
    }
  buffer[j-1]='\0'; // trim it
  }
  std::cout <<"The integer representation of that string is" << buffer <<"
"
;

  // method 2: use a finite length conversion
  sscanf(buf2,"%lf", &d);
  printf("The string converted to double is %lf
"
, d);
  j = (long int)d;
  sprintf(buffer,"%ld", j);
  int k;
  k = strlen(buffer);
  d -= j;
  for(i=0; i<20; i++)
  {
    d *= 10;
    if (fabs(d) > 0)
    {
      sprintf(buffer + k,"%01d", (int)d);
      k++;
      printf("i = %ld; j is now %ld; d is %lf
"
, i, j, d);
      j = 10 * j + (int) d;
      d -= (int) d;
    }
    else break;
  }
  printf("after conversion, the number is %ld
"
, j);
  printf("using the string method, it is %s
"
, buffer);
}

以下是示例输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Enter a double : 123.456001
The integer representation of that string is 123456001
The string converted to double is 123.456001
i = 0; j is now 123; d is 4.560010
i = 1; j is now 1234; d is 5.600100
i = 2; j is now 12345; d is 6.001000
i = 3; j is now 123456; d is 0.010000
i = 4; j is now 1234560; d is 0.100000
i = 5; j is now 12345600; d is 1.000000
i = 6; j is now 123456001; d is 0.000000
i = 7; j is now 1234560010; d is 0.000000
i = 8; j is now 12345600100; d is 0.000001
i = 9; j is now 123456001000; d is 0.000005
i = 10; j is now 1234560010000; d is 0.000054
i = 11; j is now 12345600100000; d is 0.000545
i = 12; j is now 123456001000000; d is 0.005448
i = 13; j is now 1234560010000000; d is 0.054479
i = 14; j is now 12345600100000000; d is 0.544787
i = 15; j is now 123456001000000000; d is 5.447873
i = 16; j is now 1234560010000000005; d is 4.478733
i = 17; j is now -6101143973709551562; d is 4.787326
i = 18; j is now -5671207515966860768; d is 7.873264
i = 19; j is now -1371842938539952825; d is 8.732636
after conversion, the number is 4728314688310023374
using the string method, it is 12345600100000000054478

从这里可以看到,有一些"舍入误差位"一直停留在右边;当我进行乘法和减法时,这些位最终变为可见的。这个循环可能永远持续下去。我在20岁时停了下来,那时候实在是太多了——埃多克斯一〔5〕号已经满了。不过,我想它能告诉你到底发生了什么。

你必须定义你想要答案的精确性,否则你不能用一般的术语来解决这个问题。如果您确实需要10位以上的数字,我建议您使用基于字符串的方法,或者类似bigdecimal的方法。

PS我为混合C和C++而道歉。我从来都不是一个C++的人,所以在星期六的早晨更自然。

PS2:稍微修改一下上面的代码,我就可以找到你提到的两个数字的全十进制表示。结果是

1
1.12 --> 112000000000000010658141036401502788066864013671875

换言之,实际表示略大于1.12。另一方面,

1
1.123 --> 11229999999999999982236431605997495353221893310546875

如你所见,它稍微小一些。这就是为什么循环永远不会终止的原因,因为您正在检查d - (int) d > 0.0。当int转换向下舍入时,这种情况总是正确的。


浮点舍入错误-因为二进制浮点变量不能精确表示所有的十进制值。当以十进制数字打印时,一些精确的值存储在一个稍微小一点的二进制数字中,因此在while条件下的计算会留下一个负数。

调试:打印值-可能使用精度控制。使用printf()等比cout等更容易(更简洁)。


你在处理浮点数,所以要记住你必须小心精度问题!

一般来说,如果您将该值读取到double变量,则不可能实现这一点,因为当您输入3.33时,它表示为类似3.32999978434的内容以及更多的符号。您真的想用这种方式把它转换成整数吗?


你为什么不把这些值打印出来,自己解决呢?

1
2
3
4
5
6
7
8
9
10
11
double d=0;
cout<<"Enter a double :";
cin>>d;

while(d-(int)d > 0.){
    cout <<"d - (int)d is:" << d <<" -" << (int)d <<endl;
    d*=10;
    cout <<"Now d is:" << d << endl;
}

cout<<"Result :"<<d<<endl;