關於x86下VB、C#、VC中的整數運算需要注意的地方

來源:互聯網
上載者:User

關於x86下VB、C#、VC中的整數運算需要注意的地方

 

請大家看這段代碼:

 

using System;namespace IntegerArithmetic{    class Program    {        static void Main(string[] args)        {            Int32 a = (-1) / 8;                   //0            Int32 b = (-1) % 8;                   //-1            Int32 c = 1 << 32;                    //1            UInt32 d = 1U << 32;                  //1              Int32 e = (-1) / (-8);                //0            Int32 f = (-1) % (-8);                //-1            Int32 g = (-1) << 32;                 //-1            Int32 h;                              //-1            Int32 i = Math.DivRem(-1, 8, out h);  //0            Console.WriteLine(String.Format("a = {0}", a));            Console.WriteLine(String.Format("b = {0}", b));            Console.WriteLine(String.Format("c = {0}", c));            Console.WriteLine(String.Format("d = {0}", d));            Console.WriteLine(String.Format("e = {0}", e));            Console.WriteLine(String.Format("f = {0}", f));            Console.WriteLine(String.Format("g = {0}", g));            Console.WriteLine(String.Format("h = {0}", h));            Console.WriteLine(String.Format("i = {0}", i));        }    }}

 

這些結果中不少是反常識的。

1.整數除法和模運算

在x86下的VB、C#、VC中,整數除法和模運算的定義為

x DIV y = TruncToZero(x / y)x MOD y = x - (x DIV y) * y

其中
MOD表示模運算(VB中為Mod, C#和C++中為%);
DIV表示整數除法(VB中為\,C#和C++中為/);
TruncToZero(r)是指取一個符號與r相同,且絕對值不大於|r|的絕對值最大的整數;
TruncToZero(r) = sign(r) * max{|x|: |x| <= |r|}
/表示實數除法。

這種定義導致的問題是(負數 MOD 正數)的結果為負數。
例如[1]:

bool is_odd(int n) {    return n % 2 == 1;}

這個函數在傳入任意負數n時會返回false。

 

這幾種語言在x86下的表現,可能是編譯器考慮到運行效率直接使用x86機器指令IDIV實現的緣故。

有兩種修正的定義,請參閱[1]:

floored division:模得的值的符號與模數一致

x DIV y = floor(x / y)x MOD y = x - (x DIV y) * y

Euclidean definition: 模得的值始終為正

x DIV y = if y > 0              floor(x / y)          else              ceil(x / y)x MOD y = x - (x DIV y) * y


我們可以在C#中實現採用floored division方式修正的代碼。

 

/// <summary>modulo from Knuth's floored division</summary>public static Int32 Mod(this Int32 a, Int32 m) {    Int32 s = Math.Sign(m);    Int32 pm = Math.Abs(m);    return s * (((s * a) % pm) + pm) % pm;}/// <summary>Knuth's floored division</summary>public static Int32 Div(this Int32 a, Int32 b){    return (a - a.Mod(b)) / b;}

 

不過這個方法可能會出現整數溢出。特別是C#預設沒有開啟整數溢出異常,可能導致計算出錯。
下面是沒有整數溢出的版本。不過正確是有代價的,邏輯很複雜。 

public static Int32 Mod(this Int32 a, Int32 m){    Int32 r = a % m;    if (((r < 0) && (m > 0)) || ((r > 0) && (m < 0))) { r += m; }    return r;}public static Int32 Div(this Int32 a, Int32 b){    if (b == 0) { throw new DivideByZeroException(); }    Int32 r = a.Mod(b);    if ((a > 0) && (r < 0))    {        if (a - Int32.MaxValue > r) { return (a - Math.Abs(b) - r) / b + Math.Sign(b); }    }    else if ((a < 0) && (r > 0))    {        if (a - Int32.MinValue < r) { return (a + Math.Abs(b) - r) / b - Math.Sign(b); }    }    return (a - r) / b;}


2.移位元運算
在x86下的VB、C#、VC中,移位元運算的定義為

 

Int32 x, Int32 yx << y = x SAL (y MOD 32)x >> y = x SAR (y MOD 32)UInt32 x, Int32 yx << y = x SHL (y MOD 32)x >> y = x SHR (y MOD 32)

其中SAR是最高位補原最高位的算術右移,SHR是最高位補0的邏輯右移,SAL、SHL是左移。
y MOD 32 = y AND 0x1F

這應該是x86指令集所決定的。

不過需要注意到VC編譯器對常數和變數的處理不一致。
在y為常數且超過0..31的範圍時,會出現“shift count negative or too big, undefined behavior”的警告。
當x也為常數時,常量會按常識正確計算。

修正:

public static UInt32 SHL(this UInt32 a, Int32 n){    if (n >= 32) { return 0; }    if (n < 0) { return a.SHR(-n); }    return a << n;}public static UInt32 SHR(this UInt32 a, Int32 n){    if (n >= 32) { return 0; }    if (n < 0) { return a.SHL(-n); }    return a >> n;}public static Int32 SAL(this Int32 a, Int32 n){    if (n >= 32) { return 0; }    if (n < 0) { return a.SAR(-n); }    return a << n;}public static Int32 SAR(this Int32 a, Int32 n){    if (n >= 32)    {        if (Convert.ToBoolean(a & Int32.MinValue))        {            return -1;        }        else        {            return 0;        }    }    if (n < 0) { return a.SAL(-n); }    return a >> n;}

 

3.修正的使用時機

前述的兩個修正是完備的。但是不能很好的融入文法,且效能損失是可以預測到的。
因此,下面給出使用的時機判斷方法。

1)整數除法和模運算修正的使用時機是:
被除數x和除數y中有一個可能為負數的時候。

通常除數是正數,而被除數有時候是負數。
但是,有時被除數看起來可能會出現負數,卻可以較容易的修正為正數運算式,如:

(n - 1) MOD m

其中n為非負整數,m為正整數。
這裡n = 0時不修正會出現問題。
但是我們可以寫成

(n + m -1) MOD m

這個就不會出現問題。

 

2)移位元運算修正的使用時機

在移位的位元y為變數時使用。
例如我們需要獲得一個掩碼。

Int32 Mask = 1 << n - 1

這裡n為Int32變數。
則我們必須使用

Int32 Mask = 1.SAL(n) - 1

否則,在n = 32時會出現問題。

4.結論

x86下的整數運算遠比人們所想象的複雜。
稍不注意,就會導致出現無法察覺的bug。

參考:
[1] http://en.wikipedia.org/wiki/Modulo_operation

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.