[英]Is this floating-point optimization allowed?
我試圖檢查float
失去了准確表示大整數的能力。 所以我寫了這個小片段:
int main() {
for (int i=0; ; i++) {
if ((float)i!=i) {
return i;
}
}
}
這段代碼似乎適用於除clang之外的所有編譯器。 Clang生成一個簡單的無限循環。 Godbolt 。
這是允許的嗎? 如果是,那是否是QoI問題?
請注意,內置運算符!=
要求其操作數具有相同的類型,並且如果需要,將使用促銷和轉換實現該操作數。 換句話說,您的條件相當於:
(float)i != (float)i
這應該永遠不會失敗,所以代碼最終會溢出i
,給你的程序Undefined Behavior。 因此任何行為都是可能的。
要正確檢查要檢查的內容,應將結果轉換回int
:
if ((int)(float)i != i)
正如@Angew指出的那樣 , !=
運算符在兩側都需要相同的類型。 (float)i != i
導致RHS的推廣也浮動,所以我們有(float)i != (float)i
。
g ++也會生成一個無限循環,但它並沒有優化它內部的工作。 你可以看到它用cvtsi2ss
轉換int-> float並且ucomiss xmm0,xmm0
來比較(float)i
和它自己。 (這是你的第一個線索,你的C ++源代碼並不意味着你的想法,就像@Angew的答案所解釋的那樣。)
x != x
僅在“無序”時才為真,因為x
為NaN。 ( INFINITY
在IEEE數學中與自身相等,但NaN沒有NAN == NAN
為假, NAN != NAN
為真)。
gcc7.4和更早版本正確地將你的代碼優化為jnp
作為循環分支( https://godbolt.org/z/fyOhW1 ):只要x != x
的操作數不是NaN,就保持循環。 (gcc8以及后來還會檢查je
是否突破循環,未能根據任何非NaN輸入始終為真的事實進行優化)。 x86 FP比較無序的設置PF。
而順便說一下,這意味着clang的優化也是安全的 :它只需要CSE (float)i != (implicit conversion to float)i
是一樣的,並且證明i -> float
對於int
的可能范圍永遠不會是NaN 。
(雖然假設這個循環會遇到有符號溢出的UB,但它允許按字面意思發出它想要的任何asm,包括一個ud2
非法指令,或者一個空的無限循環,無論循環體實際上是什么。)但忽略了有符號溢出UB,這種優化仍然100%合法。
即使使用-fwrapv
, GCC也無法優化掉循環體,以使有符號整數溢出定義明確 (作為2的補碼環繞)。 https://godbolt.org/z/t9A8t_
即使啟用-fno-trapping-math
也無濟於事。 ( 不幸的是,GCC的默認設置啟用了
-ftrapping-math
即使GCC的實現被破壞/錯誤 。)int-> float轉換可能導致FP不精確的異常(對於數字太大而無法准確表示),因此,如果異常可能未被掩蓋,則不合理地優化掉循環體。 (因為如果不屏蔽異常, 16777217
轉換為float可能會產生可觀察到的副作用。)
但是使用-O3 -fwrapv -fno-trapping-math
,它100%錯過了優化,而不是將其編譯為空的無限循環。 如果沒有#pragma STDC FENV_ACCESS ON
,則記錄屏蔽FP異常的粘性標記的狀態不是代碼的可觀察副作用。 沒有int
- > float
轉換會導致NaN,所以x != x
不能為true。
這些編譯器都針對使用IEEE 754單精度(binary32) float
和32位int
C ++實現進行了優化。
bugfixed (int)(float)i != i
循環在C ++實現上具有UB,具有窄的16位int
和/或更寬的float
,因為在到達第一個不是第一個整數之前你會遇到有符號整數溢出UB完全可以表示為float
。
但是,在使用x86-64 System V ABI編譯gcc或clang等實現時,UB在一組不同的實現定義選擇下沒有任何負面影響。
順便說一句,您可以靜態計算此循環的結果來自FLT_RADIX
和FLT_MANT_DIG
,在<climits>
定義。 或者至少你可以在理論上,如果float
實際上適合IEEE浮點數的模型,而不是像Posit / unum那樣的其他類型的實數表示。
我不確定ISO C ++標准對float
行為的重視程度以及不基於固定寬度指數和有效數字字段的格式是否符合標准。
在評論中:
@geza我很想聽到結果號碼!
@nada:這是16777216
你聲稱你有這個循環打印/返回16777216
?
更新:由於該評論已刪除,我認為不是。 可能OP只是在第一個整數之前引用float
,它不能完全表示為32位float
。 https://en.wikipedia.org/wiki/Single-precision_floating-point_format#Precision_limits_on_integer_values即他們希望用這個錯誤代碼驗證的內容。
bugfixed版本當然會打印16777217
,這是第一個不完全可表示的整數,而不是之前的值。
(所有較高的浮點值都是精確整數,但是對於高於有效數字寬度的指數值,它們是2的倍數,然后是4,然后是8等。可以表示許多更高的整數值,但最后一個位置是1個單位(有效數字)大於1所以它們不是連續的整數。最大的有限float
低於2 ^ 128,這對於偶數int64_t
來說太大了。)
如果任何編譯器確實退出原始循環並打印它,那將是編譯器錯誤。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.