![](/img/trans.png)
[英]Algorithm for integer rounding of result after division of one integer by another whose values may be negative
[英]Integer arithmetic produces a strange result (rounding after division?)
在Linux上使用gcc版本4.8.4,short是16位,int是32位。
#include "stdio.h"
int main( void ){
unsigned short u = 0xaabb;
unsigned int v = 0xaabb;
printf ("%08x %08x\n", u, (unsigned short)((u*0x10001)/0x100));
printf ("%08x %08x\n", v, (unsigned short)((v*0x10001)/0x100));
return 0;
}
結果:
0000aabb 0000bbab
0000aabb 0000bbaa
這可以改變,例如,通過除以0x10,其為第一種情況產生類似的結果(+1)。 如果/0x100
截斷的字節小於0x80,則不會產生這種效果。 第一種情況( short u
)的機器代碼看起來好像有些舍入(加上0xFF)。
像0x10001
這樣的文字將是int
類型(如果它可以適合int,在這種情況下為true)。 int
是簽名類型。
由於變量u
是一個小整數類型,因此只要在表達式中使用,它就會將整數提升為int
。
0xaabb * 0x10001
應該給出結果0xAABBAABB
。 但是,結果是太大,超出內部int
32位二進制補體系統,其中一個最大數量的int
是0x7FFFFFFF
。 所以你得到一個有符號整數的溢出,因此調用未定義的行為 - 任何事情都可能發生。
在進行任何形式的二進制算術時,切勿使用有符號整數!
此外,最終轉換為(unsigned short)
是徒勞的,因為printf參數無論如何都會將傳遞的值提升為int
。 嚴格來說這也是不正確的,因為%x
意味着printf
需要unsigned int
。
為了避免C中不可預測和有限的默認整數類型的所有問題,請改用stdint.h
。 此外,使用unsigned int literals解決了許多隱式類型提升錯誤。
例:
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
int main( void ){
uint16_t u = 0xaabb;
uint16_t v = 0xaabb;
printf ("%08" PRIx16 " %08" PRIx16 "\n", u, (uint16_t)(u*0x10001u/0x100u));
printf ("%08" PRIx16 " %08" PRIx16 "\n", v, (uint16_t)(v*0x10001u/0x100u));
return 0;
}
(此代碼也有參數提升,但是通過使用PRIx16
格式說明符,您告訴printf
現在編譯器的業務是使代碼工作,而不管函數調用中可能存在哪種類型的促銷。)
通常的算術轉換 。
u
在乘法之前轉換為int
。 由於int
是有符號的,因此它在除法上的行為不同。
printf("%08x\n", (u*0x10001)/0x100);
printf("%08x\n", (v*0x10001)/0x100);
返回
ffaabbab
00aabbaa
嚴格來說,有符號整數上的乘法溢出已經是未定義的行為,因此即使在除法之前結果也是無效的。
u*0x10001
的結果是int
=導致有signed
類型的溢出,從而導致未定義的行為。
假定16位short
和32位int
(典型的x86,ARM和大多數其他32位系統):
您的代碼中有兩種類型的未定義行為 (UB)。 首先,在格式字符串中使用錯誤的類型說明符。 %x
期望unsigned int
,而將unsigned short
擴展傳遞給signed int
。
第二個 - 你在這里看到的是第一個計算: u
被轉換為int
(整數提升) - 不是unsigned int
用於乘法,因為常量0x10001
也是int
。 乘法調用UB,因為它生成有符號整數溢出。 一旦你調用UB,你就會迷失方向,任何進一步的解釋都是無用的。
說,我們現在推測:發生的是,在乘法之后,你可能有一個負值,並且隨着除法向零舍入(這是標准要求),你得到更高的負值。 但是當你打印為無符號時,你會看到一個更大的原始(無符號)值。 這是因為負值的2的補碼內部表示。
請注意,此結果超出了C標准。 事實上,編譯器可能會生成代碼來格式化您的硬盤驅動器,或者您的計算機可能會跳出窗口或出現鼻子守護程序 。 所以,糾正錯誤:
%hx
打印unsigned short int
u * 0x10001U
強制轉換為unsigned int
進行乘法。 通常,如果使用無符號值,建議始終使用U
(無符號)后綴。 我稍微擴展了你的代碼來解釋:
#include "stdio.h"
int main( void ){
unsigned short u = 0xaabb;
unsigned int v = 0xaabb;
printf ("not casted:\n");
printf ("%08x %08x\n", u, ((u*0x10001)/0x100));
printf ("%08x %08x\n", v, ((v*0x10001)/0x100));
printf ("unsigned short casted:\n");
printf ("%08x %08x\n", u, (unsigned short)((u*0x10001)/0x100));
printf ("%08x %08x\n", v, (unsigned short)((v*0x10001)/0x100));
printf ("u*0x10001:\n");
printf ("x=%08x d=%d\n", u*0x10001, u*0x10001);
// Solution
printf ("Solution:\n");
printf (">>> %08x %08x\n", u, (unsigned short)((u*0x10001UL)/0x100UL));
printf (">>> %08x %08x\n", v, (unsigned short)((v*0x10001UL)/0x100UL));
return 0;
}
這導致以下輸出:
not casted:
0000aabb ffaabbab
0000aabb 00aabbaa
unsigned short casted:
0000aabb 0000bbab
0000aabb 0000bbaa
u*0x10001:
x=aabbaabb d=-1430541637
Solution:
>>> 0000aabb 0000bbaa
>>> 0000aabb 0000bbaa
所以你看到操作u*0x10001
將生成一個有signed int
(32位)值,因此你的結果是d=-1430541637
。 如果將此值除以0x100
您將得到0xFFAABBAB
的結果。 如果您使用unsigned short
轉換此值,則得到結果= 0x0000BBAB
。 如果要防止這種情況,編譯器會對此操作使用無符號值,則必須將UL
編寫為數字的擴展名。
所以你看到編譯器正在按預期工作。 你可以在這里自己編譯代碼[^] 。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.