本文共 4401 字,大约阅读时间需要 14 分钟。
n由于lua用double作为number类型的底层数据中转类型。而实际应用中多以int类型作为函数调用的参数(特别是C实现的API)。因而,double/int/unsigend int之间的数值转换在接入lua的项目中应用十分广泛。
实际项目发现,double/int/unsigend int之间的数值转换存在一个严重且极容易被忽视的”雷区”。根据IEEE二进制浮点数算术标准(IEEE 754)定义,浮点数只是一个近似值。
测试原由: 近日发现一个奇葩的问题,在lua中,传一个大于INT_MAX的整数给lua,或者在C++中用最高位为1的unsigned int u 采用如下方式返回值 Lua_PushNumber(L, u); lua将产生一个“异常状态的nunmber对象”,对该对象执行%X取值可以取到正确的十六进制值,执行%d取值,将只能取到-2147483648(0x80000000) 更让人纠结的是这个现象只发生在linux下,windows下是正常的,大于INT_MAX的值%d提取将取到对应的负数值,在需要的地方传值给对应的unsigned int,仍然是正确的值。 看到这个现象,第一反应是lua本身的bug,于是研究了lua的源码,发现lua除了采用double存储和传递number对象,没有其他不规矩操作。 而lua在%X和%d对number取值时执行的操作分别如下: ((int)luaL_check_number(L, n)) //%d ((unsigned int)luaL_check_number(L, n)) //%X 于是怀疑到C++double类型到int和unsigned int类型转换出了问题,于是写下了如下测试代码:以下是测试代码和测试结果
func TestFloat1(t *testing.T) { tt := []uint32{ 0x7FFFFFFE, 0x7FFFFFFF, 0x80000000, 0x80000001, 0xFF000000, } for _, u := range tt { oki := int32(u) f := float64(u) fi := int32(f) err := oki != fi fmt.Printf("x=0x%08X u=%10d oki=%11d f=%12.1f fi=%11d err=%v\n", u, u, oki, f, fi, err) } //x=0x7FFFFFFE u=2147483646 oki= 2147483646 f=2147483646.0 fi= 2147483646 err=false //x=0x7FFFFFFF u=2147483647 oki= 2147483647 f=2147483647.0 fi= 2147483647 err=false //x=0x80000000 u=2147483648 oki=-2147483648 f=2147483648.0 fi=-2147483648 err=false //x=0x80000001 u=2147483649 oki=-2147483647 f=2147483649.0 fi=-2147483648 err=true //x=0xFF000000 u=4278190080 oki= -16777216 f=4278190080.0 fi=-2147483648 err=true}func TestFloat2(t *testing.T) { tt := []float64{ 0x7FFFFFFE, 0x7FFFFFFF, -1, -2, 0x80000000, 0x80000001, 0x80000002, 0x880000002, 0xFF000000, 0xFFFFFFFE, 0xFFFFFFFF, } for _, f := range tt { fi := int32(f) u := uint32(f) oki := int32(u) err := fi != oki fmt.Printf("x=0x%08X f=%13.1f u=%10d fi=%11d oki=%11d err=%v\n", u, f, u, fi, oki, err) } //x=0x7FFFFFFE f= 2147483646.0 u=2147483646 fi= 2147483646 oki= 2147483646 err=false //x=0x7FFFFFFF f= 2147483647.0 u=2147483647 fi= 2147483647 oki= 2147483647 err=false //x=0xFFFFFFFF f= -1.0 u=4294967295 fi= -1 oki= -1 err=false //x=0xFFFFFFFE f= -2.0 u=4294967294 fi= -2 oki= -2 err=false //x=0x80000000 f= 2147483648.0 u=2147483648 fi=-2147483648 oki=-2147483648 err=false //x=0x80000001 f= 2147483649.0 u=2147483649 fi=-2147483648 oki=-2147483647 err=true //x=0x80000002 f= 2147483650.0 u=2147483650 fi=-2147483648 oki=-2147483646 err=true //x=0x80000002 f=36507222018.0 u=2147483650 fi=-2147483648 oki=-2147483646 err=true //x=0xFF000000 f= 4278190080.0 u=4278190080 fi=-2147483648 oki= -16777216 err=true //x=0xFFFFFFFE f= 4294967294.0 u=4294967294 fi=-2147483648 oki= -2 err=true //x=0xFFFFFFFF f= 4294967295.0 u=4294967295 fi=-2147483648 oki= -1 err=true}
结论如下:
1. 无论在linux还是在windows下,将一个超出int值域范围[-2147483648,2147483647]的doulbe值,转换为int时,将只能取到-2147483648(0x80000000) 2. 将一个超出超出unsigned int值域范围[0, 4294967295]的double类型,转换为unsigned int,将安全的取到对应16进制值的低32位 3. windows优先将常量表达式计算为int,linux优先将常量表达式结果计算为unsigned int(不知为何,这个差异在这个测试用例中没能体现出来) 4. (int)doubleValue操作在C++中是极度危险的“雷区”,应当在编码规范层次严格禁止。 5. (unsigned int)doubleValue操作在C++中是安全的 6. 想从double得到int,必须使用(int)(unsigned int)doubleValue这样的操作经验教训:
由于lua采用double存储和传递number对象,这个问题必须得到重视,并且需要在编码规范的层次,严格禁止这种unsigned int->double, double->int的行为在C++代码中大量使用的如下操作将是危险的:
1. int nIntValue = (int)Lua_ValueToNumber(L, 1); //Danger!!! 对不在int范围内的number,只能取到-2147483648(0x80000000) 2. Lua_PushNumber(L, unsignedIntValue); //Danger!!!如果unsignedIntValue最高位为1,将产生一个超出int范围的异常number对象以上两种用法必须修改为
1. int nIntValue = (int)(unsigned int)Lua_ValueToNumber(L, 1); 2. Lua_PushNumber(L, (int)unsignedIntValue);以下结论必须在日常编码中引起重视:
1. (int)doubleValue操作在C++中是极度危险的“雷区”,应当在编码规范层次严格禁止。 2. (unsigned int)doubleValue操作在C++中是安全的 3. int/unsigned int相互转换是安全的 3. 想从double得到int,必须使用(int)(unsigned int)doubleValue这样的操作 4. 无论在linux还是在windows下,将一个超出int值域范围[-2147483648,2147483647]的doulbe值,转换为int时,将只能取到-2147483648(0x80000000) 5. 将一个超出超出unsigned int值域范围[0, 4294967295]的double类型,转换为unsigned int,将安全的取到对应16进制值的低32位 6. windows优先将常量表达式计算为int,linux优先将常量表达式结果计算为unsigned int(不知为何,这个差异在这个测试用例中没能体现出来)我将以上测试代码放在这里:
参考资料:
IEEE二进制浮点数算术标准(IEEE 754)转载地址:http://gcjl.baihongyu.com/