繁体   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