前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >疑难杂症小记 - 浮点运算的精度问题

疑难杂症小记 - 浮点运算的精度问题

作者头像
用户2615200
发布2018-08-02 16:48:34
6170
发布2018-08-02 16:48:34
举报

先上一段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);
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018年03月29日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 二进制小数无法精确表达十进制小数
  • 浮点数乘法可能是以高精度执行的
  • 浮点数转整数采用的是截断方式
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档