先上一段C#代码,有兴趣的朋友可以人脑执行一遍~
代码语言:javascript复制 int num = 160;
float test = 1.3f;
float result = num * test;
int result_1 = (int)result;
int result_2 = (int)(num * test);
int result_3 = (int)(float)(num * test);
Console.WriteLine("{0} {1} {2} {3}", result, result_1, result_2, result_3);
代码看上去非常简单,运行结果却出人意料 : 208 208 207 208 !
result_2 = (int)(num * test) = (int)(160 * 1.3) = 208, 为什么程序会输出 207, WTF ? 更加诡异的是, result_3 的计算方式与 result_2 一模一样,只是中间多了一步float的转换,然后计算结果便正确了, WTF ??
SO上请教了一下,自己也去了解了一些相关知识,大抵弄清楚了原因,这里一步步的讲下,算作笔记了~
二进制小数无法精确表达十进制小数
拿上面的 test 为例,虽然代码中我们将他初始化为了十进制小数 1.3f, 但实际上,由于二进制小数无法精确表达十进制小数 1.3f, 所以浮点数 test 实际表达的是 1.3 的近似值. (细节来讲, test 的二进制表示为 0 01111111 01001100110011001100110,实际表示的数值为 1.29999995231628)
浮点数乘法可能是以高精度执行的
考虑上面的代码 float result = num * test, 实际的运算过程可能是在 double 精度下(或者更高精度下)进行的,翻译成代码,大概是这个样子:
代码语言:javascript复制float result = (float)((double)num * (double)test);
首先我们来计算 (double)num * (double)test = 160 * 1.29999995231628 = 207.9999923706048, 其二进制表示为 0 10000000110 1001111111111111111111110000000000000000 接着我们要将计算结果转为 float, 由于 float 精度有限(23位精度),但是计算结果需要更高精度(24位精度),所以转化使结果被近似到了(0舍1入?)0 10000110 10100000000000000000000 (即208)
浮点数转整数采用的是截断方式
承接上面的说明, 我们计算出了高精度下的乘法数值 (double)num * (double)test = 160 * 1.29999995231628 = 207.9999923706048 (二进制表示为 0 10000000110 1001111111111111111111110000000000000000) 不同于浮点数转化,整数转换采用的是截断方式: 首先将上述结果的二进制转换为定点二进制小数 11001111.11111111111111111, 然后直接截断小数部分,得到: 11001111 (即207)
综合上面所述的三点原因,我们就可以解释上面的遗留问题了:
result_2 = (int)(num * test) = (int)(160 * 1.3) = 208, 为什么程序会输出 207 ? 因为 1.3 的实际二进制表示为 1.29999995231628,与 160 相乘后结果为 207.9999923706048,转换为整数时进行了截断,所以 result_2 的结果为 207
result_3 的计算方式与 result_2 一模一样,只是中间多了一步float的转换,为什么计算结果便正确了? 因为 1.3 的实际二进制表示为 1.29999995231628,与 160 相乘后结果为 207.9999923706048,转换为浮点数是采用了近似方式,得到了208,之后再转化为整数自然仍然是 208
如果你大抵明白了上面的讲述,试一试这道练习题吧,想想最后的输出会是什么:
代码语言:javascript复制 int num = 160;
double test = 1.3f;
double result = num * test;
int result_1 = (int)result;
int result_2 = (int)(num * test);
int result_3 = (int)(double)(num * test);
Console.WriteLine("{0} {1} {2} {3}", result, result_1, result_2, result_3);