簡體   English   中英

如何使我的擴展范圍浮點乘法更有效率?

[英]How to make my extended range floating point multiply more efficient?

我正在做一個經常涉及像 3.47493E+17298 這樣的值的計算。 這超出了雙精度可以處理的范圍,我不需要額外的精度,只需要額外的指數范圍,所以我在 C# 中創建了自己的小結構。

我的結構使用 long 作為有效數和符號,使用 int 作為指數,所以我實際上有:

1 個符號位 32 個指數位(正則 2 的補碼指數) 63 個有效位

我很好奇可以采取哪些步驟來使我的乘法程序更有效率。 我正在運行這些擴展范圍值的大量乘法,而且速度非常快,但我一直在尋找使其更快的提示。

我的乘法例程:

    public static BigFloat Multiply(BigFloat left, BigFloat right)
    {
        long shsign1;
        long shsign2;

        if (left.significand == 0)
        {
            return bigZero;
        }

        if (right.significand == 0)
        {
            return bigZero;
        }

        shsign1 = left.significand;
        shsign2 = right.significand;

        // scaling down significand to prevent overflow multiply

        // s1 and s2 indicate how much the left and right 
        // significands need shifting.
        // The multLimit is a long constant indicating the
        // max value I want either significand to be
        int s1 = qshift(shsign1, multLimit);
        int s2 = qshift(shsign2, multLimit);

        shsign1 >>= s1;
        shsign2 >>= s2;

        BigFloat r;

        r.significand = shsign1 * shsign2;
        r.exponent = left.exponent + right.exponent + s1 + s2;

        return r;
    }

和 qshift:

它只是找出將 val 移動多少以使其絕對值小於限制。

    public static int qshift(long val, long limit)
    {
        long q = val;
        long c = limit;
        long nc = -limit;

        int counter = 0;

        while (q > c || q < nc)
        {
            q >>= 1;
            counter++;
        }

        return counter;
    }

這是一個完全不同的想法......

使用硬件的浮點機制,但使用您自己的 integer 指數對其進行擴充。 換句話說,使BigFloat.significand成為浮點數,而不是 integer。

然后您可以使用ldexpfrexp來保持浮點數上的實際指數為零。 這些應該是單機指令。

所以 BigFloat 乘法變為:

  • r.significand = left.significand * right.significand
  • r.exponent = left.exponent + right.exponent
  • tmp = (r.significand 的實際指數,來自 frexp)
  • r.exponent += tmp
  • (使用 ldexp 從r.significand的實際指數中減去tmp

不幸的是,最后兩個步驟需要frexpldexp ,搜索表明在 C# 中不可用。 所以你可能不得不在 C 中寫這個位。

...

或者,實際上...

對有效數字使用浮點數,但只需將它們歸一化在 1 和 2 之間。同樣,對有效數字使用浮點數,並像這樣相乘:

r.significand = left.significand * right.significand;
r.exponent = left.exponent + right.exponent;
if (r.significand >= 2) {
    r.significand /= 2;
    r.exponent += 1;
}
assert (r.significand >= 1 && r.significand < 2);  // for debugging...

只要您保持 assert() 中提到的不變量,這應該可以工作。 (因為如果 x 介於 1 和 2 之間並且 y 介於 1 和 2 之間,則 x*y 介於 1 和 4 之間,因此標准化步驟只需要檢查有效數字乘積何時介於 2 和 4 之間。)

您還需要標准化加法等的結果,但我懷疑您已經在這樣做了。

盡管您畢竟需要特殊情況為零:-)。

[編輯, frexp版本]

BigFloat BigFloat::normalize(BigFloat b)
{
    double temp = b.significand;
    double tempexp = b.exponent;
    double temp2, tempexp2;
    temp2 = frexp(temp, &tempexp2);
    // Need to test temp2 for infinity and NaN here
    tempexp += tempexp2;
    if (tempexp < MIN_EXP)
        // underflow!
    if (tempexp > MAX_EXP)
        // overflow!
    BigFloat r;
    r.exponent = tempexp;
    r.significand = temp2;
}

換句話說,我建議將其分解為“標准化”例程,因為您可能想在加法、減法、乘法和除法之后使用它。

然后還有所有的角落案例需要擔心......

您可能希望通過返回零來處理下溢。 溢出取決於您的口味; 應該是錯誤或 +-infinity。 最后,如果 frexp() 的結果是無窮大或 NaN,則tempexp2的值是未定義的,因此您可能也需要檢查這些情況。

我不是 C# 程序員,但這里有一些一般的想法。

首先,有沒有針對 C# 的分析工具? 如果是這樣,從那些開始...

時間很可能花在您的 qshift() function 上; 特別是循環。 錯誤預測的分支是令人討厭的。

我會將其重寫為:

long q = abs(val);
int x = q/nc;
(find next power of 2 bigger than x)

對於最后一步,請參閱此問題和答案

然后,不要按 qshift 移位,只需除以 2 的冪。(C# 是否有“查找第一組”(又名 ffs)?如果是這樣,您可以使用它從 2 的冪中獲取移位計數;它應該成為一條指令。)

如果編譯器不會為您執行此操作,則絕對內聯此序列。

另外,我會放棄零的特殊情況,除非你乘以零很多 線性碼好; 條件不好。

如果你確定不會溢出,你可以使用unchecked block

這將刪除溢出檢查,並為您提供更多性能。

暫無
暫無

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

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