簡體   English   中英

符合IEEE-754標准的半圓到偶數

[英]IEEE-754 compliant round-half-to-even

C標准庫在C99中提供roundlroundllround系列函數。 但是,這些功能不符合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中提供roundlroundllround系列函數。 但是,這些功能不符合IEEE-754標准,因為它們沒有按照IEEE的要求實現半個到偶數的“銀行家舍入”...

談論個別功能是否符合“IEEE-754”是沒有意義的。 IEEE-754合規性要求具有定義語義的一組數據類型操作可用。 它不要求這些類型或操作具有特定名稱,也不要求只有那些操作可用。 實現可以提供它想要的任何附加功能並且仍然是兼容的。 如果一個實現想要提供舍入到奇數,舍入隨機,舍入為零和陷阱 - 如果不精確,它可以這樣做。

什么IEEE-754實際需要四舍五入提供了以下六種操作:

convertToIntegerTiesToEven (x)

convertToIntegerTowardZero (x)

convertToIntegerTowardPositive (x)

convertToIntegerTowardNegative (x)

convertToIntegerTiesToAway (x)

convertToIntegerExact (x)

在C和C ++中,這些操作的最后五個分別綁定到truncceilfloorroundrint函數。 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綁定,您將能夠使用roundevenfromfp函數來執行此操作,而無需控制舍入模式。

使用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

  1. 數= 2.5
  2. temp = floor(2.5)%2 = 2%2 = 0
  3. x = -1 + temp = -1
  4. x *錯誤+數字= 2.40009
  5. 圓(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將執行此操作。

std :: nearbyint的C ++參考

暫無
暫無

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

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