2014-11-01 68 views
1

我目前正在嘗試深入瞭解浮點數表示法,所以我玩了一下。在這樣做的時候,我偶然發現了一些奇怪的行爲;我無法弄清楚發生了什麼事情,我會非常感謝一些見解。如果這已被回答道歉,我發現它很難谷歌!比較cast float爲零時的奇怪行爲

#include <iostream> 
#include <cmath> 
using namespace std; 

int main(){ 

  float minVal = pow(2,-149); // set to smallest float possible 
   
  float nextCheck = static_cast<float>(minVal/2.0f); // divide by two 
  bool isZero = (static_cast<float>(minVal/2.0f) == 0.0f); // this evaluates to false 
  bool isZero2 = (nextCheck == 0.0f); // this evaluates to true 
  cout << nextCheck << " " << isZero << " " << isZero2 << endl; 
  // this outputs 0 0 1 
   
  return 0; 

} 

本質上發生的事情是:

  • 我設置MINVAL是可以使用 單精度
  • 除以2應產生0表示的最小浮動 - 我們在最小
  • 確實,isZero2確實返回true,但isZero返回false。

發生了什麼 - 我會認爲它們是相同的?編譯器是否很聰明,說劃分任何數字都不可能產生零?

感謝您的幫助!

+1

你怎麼確定'pow(2,-149)'返回最小可能的浮點數?有很多計算不準確的問題。 – deepmax 2014-11-01 14:32:47

+0

它似乎工作。二進制表示返回1,這確實是最小可能的浮點數: Sign = 0; Exponent = 0; Mantissa = 1,因此: 2 ^( - 23)* 2 ^( - 126)= 2 ^( - 149) – noctilux 2014-11-01 14:39:28

+0

我在g ++ 4.8.2上得到您的預期輸出。 '0 1 1'。 – 2014-11-01 14:40:43

回答

5

原因isZeroisZero2可以評估爲不同的值,並且isZero可以是假的,是C++編譯器被允許實現具有比表達將指示的類型更精確中間浮點運算,但額外的精度必須在分配時被放棄。

通常,爲387歷史FPU生成代碼時,生成的指令可以在80位擴展精度類型上工作,或者,如果FPU設置爲53位有效數(例如在Windows上),則奇怪的浮點類型,53位有效數和15位指數。

無論哪種方式,minVal/2.0f都是因爲指數範圍允許表示它而被評估的,但將其分配給nextCheck將其舍入爲零。

如果您使用的是GCC,還有一個額外的問題,即-fexcess-precision=standard尚未針對C++前端實現,這意味着由g ++生成的代碼並未完全實現標準推薦的內容。

+0

非常感謝,我覺得我現在幾乎可以理解。只是爲了澄清:在我的Windows編譯器(GCC)中,-fexcess-precision = standard尚未實現,所以即使我將其轉換爲float,它可能會使用其奇怪的內部53位表示。另一方面,在Ubuntu的g ++中,它會進行轉換,然後將其視爲預期的浮動方式。是對的嗎? – noctilux 2014-11-01 15:03:53

+1

@noctilux更可能的是,在Ubuntu上,它使用SSE2浮點指令,並生成'minVal/2.0f'的單精度分割,以期望的精度產生預期的結果。您可以通過對GCC使用'-S'選項來解決問題,以便以可讀形式生成彙編代碼。以'f'開始的指令是歷史387條指令。如果編譯器生成SSE2代碼,'divss'就是你將看到的指令。 – 2014-11-01 15:07:52

+0

帕斯卡,你是對的。感謝您提供非常有見地的答案! – noctilux 2014-11-01 15:17:40