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 | 1.123 --> 11229999999999999982236431605997495353221893310546875 |
。
如你所见,它稍微小一些。这就是为什么循环永远不会终止的原因,因为您正在检查
浮点舍入错误-因为二进制浮点变量不能精确表示所有的十进制值。当以十进制数字打印时,一些精确的值存储在一个稍微小一点的二进制数字中,因此在
调试:打印值-可能使用精度控制。使用
你在处理浮点数,所以要记住你必须小心精度问题!
一般来说,如果您将该值读取到
你为什么不把这些值打印出来,自己解决呢?
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; |