簡體   English   中英

為什么除法結果會根據演員類型而有所不同? (跟進)

[英]Why does a division result differ based on the cast type? (Followup)

這是這個問題的后續問題: 為什么除法結果會根據演員類型而有所不同?

快速摘要:

byte b1 = (byte)(64 / 0.8f); // b1 is 79
int b2 = (int)(64 / 0.8f); // b2 is 79
float fl = (64 / 0.8f); // fl is 80

問題是:為什么結果會根據演員類型而有所不同? 在制定答案的過程中遇到了一個我無法解釋的問題。

var bytes = BitConverter.GetBytes(64 / 0.8f).Reverse(); // Reverse endianness
var bits = bytes.Select(b => Convert.ToString(b, 2).PadLeft(8, '0'));
Console.WriteLine(string.Join(" ", bits));

這輸出如下:

01000010 10100000 00000000 00000000

以IEEE 754格式分解:

0 10000101 01000000000000000000000

標志:

0 => Positive

指數:

10000101 => 133 in base 10

尾數:

01000000000000000000000 => 0*2^-1 + 1*2^-2 + 0*2^-3 ... = 1/4 = 0.25

十進制表示:

(1 + 0.25) * 2^(133 - 127) (Subtract single precision bias)

這恰好導致80.那么為什么鑄造結果有所不同呢?

我在另一個線程中的答案並不完全正確: 實際上,在運行時計算時, (byte)(64 / 0.8f) 為80

在運行時將包含64 / 0.8f結果的float 64 / 0.8fbyte時,結果實際為80.但是,當轉換作為賦值的一部分完成時,情況並非如此:

float f1 = (64 / 0.8f);

byte b1 = (byte) f1;
byte b2 = (byte)(64 / 0.8f);

Console.WriteLine(b1); //80
Console.WriteLine(b2); //79

當b1包含預期結果時,b2關閉。 根據反匯編,b2分配如下:

mov         dword ptr [ebp-48h],4Fh 

因此,編譯器似乎在運行時計算結果的不同結果。 但是,我不知道這是否是預期的行為。

編輯 :也許是Pascal Cuoq描述的效果:在編譯期間,C#編譯器使用double來計算表達式。 這導致79,xxx被截斷為79(因為double包含足夠的精度以引起問題,這里)。
但是,使用float,我們實際上並沒有遇到問題,因為浮點“錯誤”不在float的范圍內。

在運行時,這個也打印79:

double d1 = (64 / 0.8f);
byte b3 = (byte) d1;
Console.WriteLine(b3); //79

EDIT2:根據Pascal Cuoq的要求,我運行了以下代碼:

int sixtyfour = Int32.Parse("64");
byte b4 = (byte)(sixtyfour / 0.8f);
Console.WriteLine(b4); //79

結果是79.所以上面的語句表明編譯器和運行時計算出不同的結果是不正確的。

編輯3 :當將以前的代碼更改為(再次歸功於Pascal Cuoq)時,結果為80:

byte b5 = (byte)(float)(sixtyfour / 0.8f);
Console.WriteLine(b5); //80

但請注意,寫作時並非如此(結果為79):

byte b6 = (byte)(float)(64 / 0.8f);
Console.WriteLine(b6); //79

所以這里似乎正在發生:( (byte)(64 / 0.8f)不被評估為float ,而是被計算為double (在將其轉換為byte之前)。 這會導致舍入誤差(使用float完成計算時不會發生)。 在轉換為double之前浮動的顯式轉換(由ReSharper,BTW標記為冗余)“解決”此問題。 但是,當在編譯期間完成計算時(可能僅在使用常量時),顯式轉換為float似乎被忽略/優化掉了。

TLDR:浮點計算比最初看起來更復雜。

C#語言規范允許以高於類型的精度計算中間浮點結果 這很可能發生在這里。

而計算到更高精度的64 / 0.8略低於80(因為0.8無法在二進制浮點中精確表示),並且在截斷為整數類型時轉換為79 ,如果除法的結果轉換為float ,則四舍五入到80.0f

(從浮點到浮點的轉換是最接近的 - 技術上,它們是根據FPU的舍入模式完成的,但C#不允許將FPU的舍入模式從“更接近”更改為默認值。從浮點到整數類型的轉換截斷。)

盡管C#遵循Java(恕我直言,不幸)領導要求顯式轉換,但任何時候指定為double都存儲到float ,C#編譯器生成的代碼允許.NET Runtime執行double計算並使用這些double值在許多情況下,根據語言規則,表達式的類型應該是float

幸運的是,C#編譯器確實提供了至少一種方法來確保應該舍入到最接近的可表示float事物實際上是:將它們顯式地轉換為float

如果將表達式編寫為(byte)(float)(sixtyFour / 0.8f) ,則應該在截斷小數部分之前強制將結果舍入為最接近的可表示float值。 雖然轉換為float可能看起來是多余的(表達式的編譯時類型已經是float ),但是轉換會將“應該是float但實際上是double ”變成實際上是float東西。

從歷史上看,某些語言會指定所有浮點運算都在double類型上執行; float存在不是為了加速計算,而是為了減少存儲需求。 通常不需要將常量指定為float類型,因為除以0.800000000000000044( double值0.8)並不慢於除以0.800000011920929(值0.8f )。 有點煩人的C#不允許float1 = float2 / 0.8; 因為“精度損失”而是偏向於不太精確的float1 = float2 / 0.8f; 並且甚至不介意可能錯誤的double1 = float1 / 0.8f; float值之間執行操作這一事實並不意味着結果實際上是float - 但它只是意味着編譯器將允許它在某些上下文中以靜默方式舍入為float ,但不會強制它在其他上下文中。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM