免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
楼主: pmerofc
打印 上一主题 下一主题

费解的难题:编译器错了吗? [复制链接]

论坛徽章:
2
程序设计版块每日发帖之星
日期:2015-06-17 22:20:00每日论坛发贴之星
日期:2015-06-17 22:20:00
41 [报告]
发表于 2012-03-27 16:51 |只看该作者
提示: 作者被禁止或删除 内容自动屏蔽

论坛徽章:
95
程序设计版块每日发帖之星
日期:2015-09-05 06:20:00程序设计版块每日发帖之星
日期:2015-09-17 06:20:00程序设计版块每日发帖之星
日期:2015-09-18 06:20:002015亚冠之阿尔艾因
日期:2015-09-18 10:35:08月度论坛发贴之星
日期:2015-09-30 22:25:002015亚冠之阿尔沙巴布
日期:2015-10-03 08:57:39程序设计版块每日发帖之星
日期:2015-10-05 06:20:00每日论坛发贴之星
日期:2015-10-05 06:20:002015年亚冠纪念徽章
日期:2015-10-06 10:06:482015亚冠之塔什干棉农
日期:2015-10-19 19:43:35程序设计版块每日发帖之星
日期:2015-10-21 06:20:00每日论坛发贴之星
日期:2015-09-14 06:20:00
42 [报告]
发表于 2012-03-27 17:10 |只看该作者
pmerofc 发表于 2012-03-27 16:51
回复 40# MMMIX
我觉得有效数字在这里的意义不大
主要检查谁和谁相同就可以了


关键是,C/C++ 标准要求这种相同么?

论坛徽章:
2
程序设计版块每日发帖之星
日期:2015-06-17 22:20:00每日论坛发贴之星
日期:2015-06-17 22:20:00
43 [报告]
发表于 2012-03-27 17:32 |只看该作者
提示: 作者被禁止或删除 内容自动屏蔽

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
44 [报告]
发表于 2012-03-27 19:35 |只看该作者
回复 10# bruceteen

有错误……

对printf来说,double应该用%f,%g等等,而long double使用%Lf,%Lg等等。 float没有对应的,因为默认参数提升规则,printf不会接受到float实参。
lf与llf是很混乱的……  貌似都是未定义行为,除了c99允许lf与f同义。

论坛徽章:
95
程序设计版块每日发帖之星
日期:2015-09-05 06:20:00程序设计版块每日发帖之星
日期:2015-09-17 06:20:00程序设计版块每日发帖之星
日期:2015-09-18 06:20:002015亚冠之阿尔艾因
日期:2015-09-18 10:35:08月度论坛发贴之星
日期:2015-09-30 22:25:002015亚冠之阿尔沙巴布
日期:2015-10-03 08:57:39程序设计版块每日发帖之星
日期:2015-10-05 06:20:00每日论坛发贴之星
日期:2015-10-05 06:20:002015年亚冠纪念徽章
日期:2015-10-06 10:06:482015亚冠之塔什干棉农
日期:2015-10-19 19:43:35程序设计版块每日发帖之星
日期:2015-10-21 06:20:00每日论坛发贴之星
日期:2015-09-14 06:20:00
45 [报告]
发表于 2012-03-27 19:36 |只看该作者
pmerofc 发表于 2012-03-27 17:32
必须的
语义相同结果却不同是绝对无法接受的


就我的經驗來看,並不是這麼簡單。

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
46 [报告]
发表于 2012-03-27 19:46 |只看该作者
回复 29# sonicling

>> 因为C++标准明确规定了每个operator的操作数类型和表达式类型,并且明确说了左右操作数类型的提升关系,而C标准语焉不详,只说 * 的操作数必须是算术类型,结果必须是算术类型,结果的值是两边的乘积,没有说类型该如何处理。

C标准有说类型该如何处理。你引用的
>> 3 The usual arithmetic conversions are performed on the operands.
那个 "usual arithmetic conversions" 就是……

C99在 6.3.1.8 Usual arithmetic conversions
C89在 3.2.1.5 Usual arithmetic conversions

按照这个规则, f*f 应该是float*float。
不过就在这小节后面有一句很狗血的话……

C89
The values of operands and of the results of expressions may be
represented in greater precision and range than that required by the
type; the types are not changed thereby.

C99
2 The values of floating operands and of the results of floating expressions may be
represented in greater precision and range than that required by the type; the types are not
changed thereby.52)


C99特别还强调了floating expressions。。。

也就是说, f*f 可以用double计算, 只是它的类型依然是 float*float->float。

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
47 [报告]
发表于 2012-03-27 20:06 |只看该作者
回复 1# pmerofc

你的意思是不是:

#define F ...

float f = F;
double d1 = f * f;
double d2 = (double)f * (double)f;
double d3 = F * F;
double d4 = (double)F * (double)F;

d2与d4应该相同, d1与d3应该相同?
而实际结果是 d2与d4与d1相同, d3与它们不同?


但 d1 与 d3 应该相同的根据又是什么?
F * F 是一个常量表达式,允许在编译时求值(由编译器), 但 f * f不是常量表达式, 要在运行时求值(由被编译出的程序)。


我这里的一些测试结果(F是 1+1/2^12,你给的1.234567f计算太麻烦了……)是 d1,d2,d4 是按double计算的,没有丢失精度(但printf输出时丢失了)。
而d3是按float计算的……


也许标准想表达的是:
1. float*float 应该只期待它有float的精度
也许换个平台 f*f, F*F 就都会丢失精度

2. float*float 也允许按更高精度计算,只是这应该作为bonus。
如果确实需要double*double,应该(double)f * f 或者 (double)F * F。

例如我这里测试的运行时就是这样的行为, f * f没有丢失精度。

3. 我测试的编译器对编译时计算就没有这样的bonus……  于是 F * F 就丢失精度了。

但编译器也有一些选项可以保持精度。 VC6有个/Op , VC8+有 /fp:

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
48 [报告]
发表于 2012-03-27 20:44 |只看该作者
回复 47# OwnWaterloo

说一下测试的细节吧……

1. #define F 1.000244140625f
它其实是  1 + 1/2^12 , 这个值计算方便……

2. 赋值到float,然后memcpy到char buf并输出内存内容是 00 08 80 3f

因为是小端,所以应该解释为 3f 80 08 00。

  1. 再按ieee754就是: 0 - 011 1111  1 - 000 0000  0000 1000  0000 0000
  2. fraction部分是                    1.000 0000  0000 1 即 1 + 1/2^12
  3. sign部分是        +
  4. exponent部分是       ( 11 1111  1)2 - 127(10) = 0
复制代码
它表示的值就是 + (1.000000000001) * 2^0 = 1.000244140625 , 没有丢失精度。


2. 计算 1.000244140625 的平方的精确值
a = 1.000244140625 = 1 + 1/2^12
b = a * 2^12 = 2^12 + 1 = 4097
b*b = 4097^2 = a*a * 2^24
a*a = 4097^2 / 2^24 =  (1.000488340854644775390625)10 =  (1.000000000010000000000001)2


3. 按那4种方式计算 f * f, 只有编译时求值的结果不同。

00 00 00 00 00 02 f0 3f  1.00048828125000000000000 // 这个是编译时求值的
00 00 00 10 00 02 f0 3f  1.00048834085464480000000 // 这个是运行时求值的

输出的结果看上去都不对,但其实运行时计算得到的值是正确的:

  1. (00 00 00 10 00 02 f0 3f)memory
  2. = (3ff0020010000000)hex-le
  3. =        (0 - 01111111111 - 0000000000100000000000010000000000000000000000000000)ieee754
  4. fraction                  1.000000000010000000000001
  5. exponent      (1111111111)2 - 1023(10) = 0
  6. sign      +
复制代码
而编译时得到的结果:

  1. (00 00 00 00 00 02 f0 3f)memory
  2. = (3ff0020000000000)hex-le
  3. =        (0 - 01111111111 - 0000000000100000000000000000000000000000000000000000)ieee754
  4. fraction                  1.00000000001
  5. exponent      (1111111111)2 - 1023(10) = 0
  6. sign      +
复制代码
就是 (1.00000000001)2 = (1.00048828125)10 精度就丢了。

论坛徽章:
2
程序设计版块每日发帖之星
日期:2015-06-17 22:20:00每日论坛发贴之星
日期:2015-06-17 22:20:00
49 [报告]
发表于 2012-03-27 21:07 |只看该作者
提示: 作者被禁止或删除 内容自动屏蔽

论坛徽章:
2
程序设计版块每日发帖之星
日期:2015-06-17 22:20:00每日论坛发贴之星
日期:2015-06-17 22:20:00
50 [报告]
发表于 2012-03-27 21:08 |只看该作者
提示: 作者被禁止或删除 内容自动屏蔽
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP