【问题标题】:Illogical results of my program [duplicate]我的程序不合逻辑的结果[重复]
【发布时间】:2013-09-26 20:16:34
【问题描述】:

我正在尝试解决这个问题:

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

...所以这是我的解决方案:

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 不起作用并给出无限循环!

问题出在哪里,让我的程序正常工作的解决方案是什么?

【问题讨论】:

  • 什么是“0”。在while循环条件下?我的意思是为什么“。” ?
  • @Rndm: 0 本身就是一个int0.0.0 相同,并且是double 类型的常量(不是int)。
  • 二进制浮点数不能准确表示所有(甚至大多数)十进制数。 12.345 实际上可能在内存中表示为 123.4999... 之类的东西(打印出来时会四舍五入),从而导致无限循环。
  • 老实说,这里更容易的是sprintf()double,删除小数点,然后再次将其解析为int。 IE。依靠标准库为您处理这些事情。
  • 有多少重复项...以及谁能引用“每个计算机科学家都应该了解的浮点运算知识”的 URL?

标签: c++


【解决方案1】:

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

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

 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。

【讨论】:

    【解决方案2】:

    以下解决方案的灵感来自 Millimoose 的评论 - 非常感谢!

    将输入作为字符串读取。去除小数点右侧的所有尾随零。去掉小数。转换为 int。

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

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

    简而言之 - 如前所述,该问题没有确切的解决方案,但以上内容应该让您对采取的方法有所了解。

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

    #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 << "\n";
    
      // method 2: use a finite length conversion
      sscanf(buf2, "%lf", &d);
      printf("The string converted to double is %lf\n", 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\n", i, j, d);
          j = 10 * j + (int) d;
          d -= (int) d;
        }
        else break;
      }
      printf("after conversion, the number is %ld\n", j);
      printf("using the string method, it is %s\n", buffer);
    }
    

    这是示例输出:

    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 点停止了它,这实际上太多了 - j 这时候已经溢出了。不过,我认为它向您展示了到底发生了什么。

    您必须定义您想要得到答案的精度,否则您无法笼统地解决这个问题。如果您确实需要超过 10 个左右的数字,我建议您使用基于字符串的方法,或 BigDecimal 之类的方法。

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

    PS2:稍微修改一下上面的代码,我可以找到你提到的两个数字的完整十进制表示。原来是这样的

    1.12 --> 112000000000000010658141036401502788066864013671875
    

    换句话说,实际表示比1.12略大。另一方面,

    1.123 --> 11229999999999999982236431605997495353221893310546875
    

    如您所见,它略小。这就是为什么您的循环永远不会终止的原因,因为您正在检查是否d - (int) d &gt; 0.0。随着int 转换向下舍入,此条件始终为真。

    【讨论】:

      【解决方案3】:

      您正在处理浮点数,因此请始终记住您必须小心精度问题!

      一般来说,如果您将此值读取到double 变量,这是不可能,因为当您输入 3.33 时,它表示为类似3.32999978434 和更多符号。你真的想把它转换成整数这个的方式吗?

      【讨论】:

        【解决方案4】:

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

        调试:打印这些值——可能使用精确控制。使用printf() 等人比使用cout 等人更容易(更简洁)。

        【讨论】:

          【解决方案5】:

          您为什么不打印出这些值并自己弄清楚呢?

          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;
          

          【讨论】:

            猜你喜欢
            • 2020-08-27
            • 1970-01-01
            • 2013-10-28
            • 1970-01-01
            • 2017-09-01
            • 1970-01-01
            • 2017-08-22
            • 2016-05-03
            • 1970-01-01
            相关资源
            最近更新 更多