![](/img/trans.png)
[英]What is the fastest way to write a IEEE-754 compliant double/float division in C++?
[英]IEEE-754 compliant round-half-to-even
C標准庫在C99中提供round
, lround
和llround
系列函數。 但是,這些功能不符合IEEE-754標准,因為它們沒有實現IEEE規定的半個到偶數的“銀行家舍入”。 如果小數分量恰好為0.5,則半到均勻舍入要求將結果四舍五入到最接近的偶數值。 相反,如cppreference.com上所述,C99標准要求半個零距離
1-3)計算最接近arg的整數值(浮點格式),將中間情況舍入為零,不管當前的舍入模式如何。
在C中實現舍入的通常的臨時方法是表達式(int)(x + 0.5f)
,盡管在嚴格的IEEE-754數學中不正確 ,但通常由編譯器將其轉換為正確的cvtss2si
指令。 然而,這肯定不是一個可移植的假設。
如何實現一個函數,它將使用半對偶語義對任何浮點值進行舍入? 如果可能,該函數應僅依賴於語言和標准庫語義,以便它可以在非IEEE浮點類型上運行。 如果這不可能,則根據IEEE-754位表示定義的答案也是可接受的。 請根據<limits.h>
或<limits>
來表征任何常量。
舍入數字x,如果x和round(x)之間的差值恰好是+0.5或-0.5,而round(x)是奇數,則round(x)在錯誤的方向上舍入,因此您減去與X。
C標准庫在C99中提供
round
,lround
和llround
系列函數。 但是,這些功能不符合IEEE-754標准,因為它們沒有按照IEEE的要求實現半個到偶數的“銀行家舍入”...
談論個別功能是否符合“IEEE-754”是沒有意義的。 IEEE-754合規性要求具有定義語義的一組數據類型操作可用。 它不要求這些類型或操作具有特定名稱,也不要求只有那些操作可用。 實現可以提供它想要的任何附加功能並且仍然是兼容的。 如果一個實現想要提供舍入到奇數,舍入隨機,舍入為零和陷阱 - 如果不精確,它可以這樣做。
什么IEEE-754實際需要四舍五入是提供了以下六種操作:
convertToIntegerTiesToEven (x)
convertToIntegerTowardZero (x)
convertToIntegerTowardPositive (x)
convertToIntegerTowardNegative (x)
convertToIntegerTiesToAway (x)
convertToIntegerExact (x)
在C和C ++中,這些操作的最后五個分別綁定到trunc
, ceil
, floor
, round
和rint
函數。 C11和C ++ 14沒有第一個綁定,但未來的修訂將使用roundeven
。 如您所見, round
實際上是必需的操作之一。
但是, roundeven
在當前的實現中不可用,這將我們帶到您問題的下一部分:
在C中實現舍入的通常的臨時方法是表達式
(int)(x + 0.5f)
,盡管在嚴格的IEEE-754數學中不正確,但通常由編譯器將其轉換為正確的cvtss2si
指令。 然而,這肯定不是一個可移植的假設。
該表達式的問題遠遠超出了“嚴格的IEEE-754數學”。 對於負x
,它是完全不正確的,為nextDown(0.5)
給出了錯誤的答案,並將2 ** 23 binade中的所有奇數整數變為偶數整數。 任何將它轉換為cvtss2si
編譯器都是可怕的,可怕的破壞。 如果你有一個這樣的例子,我很樂意看到它。
如何實現一個函數,它將使用半對偶語義對任何浮點值進行舍入?
正如njuffa在評論中指出的那樣,你可以確保設置默認的舍入模式並使用rint
(或lrint
,因為它聽起來你實際上想要一個整數結果),或者你可以通過調用round
然后修復來實現自己的舍入功能像gnasher729這樣的中途案例表明。 一旦采用了針對C的n1778綁定,您將能夠使用roundeven
或fromfp
函數來執行此操作,而無需控制舍入模式。
使用C標准庫中的remainder(double x, 1.0)
。 這與當前的舍入模式無關。
余數函數計算IEC 60559要求的余數x REM y
remainder()
在這里很有用,因為它符合OP與甚至要求的關系。
double round_to_nearest_ties_to_even(double x) {
x -= remainder(x, 1.0);
return x;
}
測試代碼
void rtest(double x) {
double round_half_to_even = round_to_nearest_ties_to_even(x);
printf("x:%25.17le z:%25.17le \n", x, round_half_to_even);
}
void rtest3(double x) {
rtest(nextafter(x, -1.0/0.0));
rtest(x);
rtest(nextafter(x, +1.0/0.0));
}
int main(void) {
rtest3(-DBL_MAX);
rtest3(-2.0);
rtest3(-1.5);
rtest3(-1.0);
rtest3(-0.5);
rtest(nextafter(-0.0, -DBL_MAX));
rtest(-0.0);
rtest(0.0);
rtest(nextafter(0.0, +DBL_MAX));
rtest3(0.5);
rtest3(1.0);
rtest3(1.5);
rtest3(2.0);
rtest3(DBL_MAX);
rtest3(0.0/0.0);
return 0;
}
產量
x: -inf z: -inf
x:-1.79769313486231571e+308 z:-1.79769313486231571e+308
x:-1.79769313486231551e+308 z:-1.79769313486231551e+308
x: -2.00000000000000044e+00 z: -2.00000000000000000e+00
x: -2.00000000000000000e+00 z: -2.00000000000000000e+00
x: -1.99999999999999978e+00 z: -2.00000000000000000e+00
x: -1.50000000000000022e+00 z: -2.00000000000000000e+00
x: -1.50000000000000000e+00 z: -2.00000000000000000e+00 tie to even
x: -1.49999999999999978e+00 z: -1.00000000000000000e+00
x: -1.00000000000000022e+00 z: -1.00000000000000000e+00
x: -1.00000000000000000e+00 z: -1.00000000000000000e+00
x: -9.99999999999999889e-01 z: -1.00000000000000000e+00
x: -5.00000000000000111e-01 z: -1.00000000000000000e+00
x: -5.00000000000000000e-01 z: 0.00000000000000000e+00 tie to even
x: -4.99999999999999944e-01 z: 0.00000000000000000e+00
x:-4.94065645841246544e-324 z: 0.00000000000000000e+00
x: -0.00000000000000000e+00 z: 0.00000000000000000e+00
x: 0.00000000000000000e+00 z: 0.00000000000000000e+00
x: 4.94065645841246544e-324 z: 0.00000000000000000e+00
x: 4.99999999999999944e-01 z: 0.00000000000000000e+00
x: 5.00000000000000000e-01 z: 0.00000000000000000e+00 tie to even
x: 5.00000000000000111e-01 z: 1.00000000000000000e+00
x: 9.99999999999999889e-01 z: 1.00000000000000000e+00
x: 1.00000000000000000e+00 z: 1.00000000000000000e+00
x: 1.00000000000000022e+00 z: 1.00000000000000000e+00
x: 1.49999999999999978e+00 z: 1.00000000000000000e+00
x: 1.50000000000000000e+00 z: 2.00000000000000000e+00 tie to even
x: 1.50000000000000022e+00 z: 2.00000000000000000e+00
x: 1.99999999999999978e+00 z: 2.00000000000000000e+00
x: 2.00000000000000000e+00 z: 2.00000000000000000e+00
x: 2.00000000000000044e+00 z: 2.00000000000000000e+00
x: 1.79769313486231551e+308 z: 1.79769313486231551e+308
x: 1.79769313486231571e+308 z: 1.79769313486231571e+308
x: inf z: inf
x: nan z: nan
x: nan z: nan
x: nan z: nan
float
數據類型可以表示所有整數,但不包含分數,范圍在8388608.0f到16777216.0f之間。 任何float
號碼這比8388607.5f較大是整數,並且沒有舍入將是必要的。 將8388608.0f添加到任何小於該值的非負float
將產生一個整數,該數字將根據當前的舍入模式(通常為半舍入到偶數)進行舍入。 減去8388608.0f然后將產生原始的正確圓形版本(假設它在合適的范圍內)。
因此,應該可以做類似的事情:
float round(float f)
{
if (!(f > -8388608.0f && f < 8388608.0f)) // Return true for NaN
return f;
else if (f > 0)
return (float)(f+8388608.0f)-8388608.0f;
else
return (float)(f-8388608.0f)+8388608.0f;
}
並利用添加的自然舍入行為,而不必使用任何其他“舍入到整數”設施。
以下是遵循IEEE舍入標准的半圓到偶數程序的簡單實現。
邏輯:錯誤= 0.00001
- 數= 2.5
- temp = floor(2.5)%2 = 2%2 = 0
- x = -1 + temp = -1
- x *錯誤+數字= 2.40009
- 圓(2.40009)= 2
注意:此處的錯誤為0.00001,即如果發生2.500001,則它將舍入為2而不是3
Python 2.7實現:
temp = (number)
rounded_number = int( round(-1+ temp%2)*0.00001 + temp )
C ++實現:(使用math.h作為floor函數)
float temp = (number)
int rounded_number = (int)( (-1+ temp%2)*0.00001 + temp + 0.5)
這將給出的輸出如下。 標准:
(3.5) - > 4
(2.5) - > 2
編輯1:正如@Mark Dickinson在評論中指出的那樣。 可以根據代碼中的要求修改錯誤以使其標准化。 對於python,要將其轉換為可能的最小浮點值,您可以執行以下操作。
import sys
error = sys.float_info.min
從C ++ 11開始,STL中有一個函數可以進行半舍入舍入。 如果浮點舍入模式設置為FE_TONEAREST
(默認值),則std::nearbyint
將執行此操作。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.