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)
for _, u := range tt
//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)
for _, f := range tt
//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)
float,double在記憶體中的儲存方式
將17.625換算成 float型。首先,將17.625換算成二進位制位 10001.101 0.625 0.5 0.125,0.5即 1 2,0.125即 1 8 如果不會將小數部分轉換成二進位制,請參考其他書籍。再將 10001.101 向右移,直到小數點前只剩一位 成了 1.0001101 x...
float,double資料型別在記憶體中的儲存方式
float在記憶體中用四個byte表示 符號位 sign 指數字 exponent 尾數 mantissa 1 bit 8 bits 23 bits 符號位 1正0負 指數字 範圍從0 255,但實際的指數等於這裡的指數減去127,所以真正的指數範圍從 127 128。尾數 23bit的尾數實際上表...
用陣列作為函式引數
我們都知道,可以用變數作為函式的引數,而陣列中的每乙個元素也是變數,因此我們也可以用陣列元素來作為函式引數。另外,用陣列名也可以作為實參和形參,傳遞的是陣列的首位址。一 用陣列元素作為函式實參 這與用變數作為實參一樣,是單向傳遞,取 值傳遞 的方式。二 用陣列名作為函式引數 此時,實參和形參都要用陣...