如何使我的扩展范围浮点数倍增效率?

时间:2011-06-03 15:32:37

标签: c# performance floating-point

我正在进行一项经常涉及3.47493E + 17298等值的计算。这超出了double可以处理的范围,我不需要额外的精度,只需要额外的指数范围,所以我在C#中创建了自己的小结构。

我的struct使用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;
    }

3 个答案:

答案 0 :(得分:2)

这是一个完全不同的想法......

使用硬件的浮点机制,但用你自己的整数指数扩充它。换句话说,make BigFloat.significand是浮点数而不是整数。

然后你可以使用ldexpfrexp来保持浮点数上的实数指数等于零。这些应该是单机说明。

所以BigFloat乘以:

  • r.significand = left.significand * right.significand
  • r.exponent = left.exponent + right.exponent
  • tmp =(r.signific的实际指数和来自frexp的实际指数)
  • r.exponent += tmp
  • (使用ldexp从tmp的实际指数中减去r.significand

不幸的是,最后两个步骤需要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的值未定义,因此您可能也想检查这些情况。

答案 1 :(得分:1)

我不是一名C#程序员,但这里有一些一般性的想法。

首先,是否有适用于C#的分析工具?如果是这样,请从那些......

开始

时间很可能花在你的qshift()函数上;特别是循环。错误预测的分支是令人讨厌的。

我会把它重写为:

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

最后一步,请参阅this question and answer

然后不是通过qshift移动,而是除以2的幂。(C#是否“找到第一组”(又名.ffs)?如果是这样,你可以使用它来获得2的幂的移位数;它应该是一条指令。)

如果编译器不能为您执行此操作,则绝对内联此序列。

另外,我会抛弃特殊情况为零,除非你将零乘以 lot 。线性代码好;条件不好。

答案 2 :(得分:0)

如果您确定没有溢出,可以使用 unchecked block

这将删除溢出检查,并为您提供更多性能。