[英]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.8f
為byte
時,結果實際為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.