快速定点pow,log,exp和sqrt

时间:2011-01-11 12:12:41

标签: c++ c logarithm fixed-point exp

我有一个固定点类(10.22),我需要一个pow,一个sqrt,一个exp和一个日志函数。

唉,我不知道从哪里开始。任何人都可以向我提供一些有用的文章链接,或者,还提供一些代码吗?

我认为,一旦我有了exp函数,那么实现pow和sqrt就变得相对容易了。

pow(x,y)=> exp(y * log(x))    sqrt(x)=> pow(x,0.5)

它只是那些我发现很难的exp和log函数(好像我记得我的一些日志规则,我记不起其他的很多了。)

据推测,顺便说一句,对于sqrt和pow也会有一个更快的方法,所以即使它只是说使用我在上面概述的方法,也会欣赏前面的任何指针:)

请注意:这是跨平台和纯C / C ++代码,所以我不能使用任何汇编程序优化。

4 个答案:

答案 0 :(得分:22)

一个非常简单的解决方案是使用适当的表驱动近似。如果正确减少输入,实际上并不需要大量数据。 exp(a)==exp(a/2)*exp(a/2),这意味着您只需要为exp(x)计算1 < x < 2。在该范围内,runga-kutta近似将给出合理的结果,其中约有16个条目IIRC。

同样,sqrt(a) == 2 * sqrt(a/4)表示您只需要1 < a < 4的表条目。 Log(a)有点难:log(a) == 1 + log(a/e)。这是一个相当慢的迭代,但是log(1024)只有6.9,所以你不会有很多次迭代。

你会对pow使用类似的“整数优先”算法:pow(x,y)==pow(x, floor(y)) * pow(x, frac(y))。这是有效的,因为pow(double, int)是微不足道的(分而治之)。

[edit]对于log(a)的整数组件,存储表1, e, e^2, e^3, e^4, e^5, e^6, e^7可能很有用,这样您就可以通过该表中a的简单硬编码二进制搜索来减少log(a) == n + log(a/e^n) 。从7步增加到3步的改进并不是很大,但这意味着您只需按e^n而不是n分次e

[编辑2] 对于最后一个log(a/e^n)项,您可以使用log(a/e^n) = log((a/e^n)^8)/8 - 每次迭代通过表查找再生成3个位。这使您的代码和表大小变小。这通常是嵌入式系统的代码,并且它们没有大型缓存。

[编辑3] 这不是我的聪明才智。 log(a) = log(2) + log(a/2)。您可以只存储定点值log2=0.30102999566,计算前导零的数量,将a移动到用于查找表的范围内,并将该移位(整数)乘以定点常量log2。可以低至3条指令。

使用e进行缩减步骤只会为您提供一个“漂亮的”log(e)=1.0常量,但这是错误的优化。 0.30102999566和1.0一样好;两者都是10.22固定点的32位常数。使用2作为范围缩减的常量允许您使用位移来进行除法。

你仍然可以从编辑2 log(a/2^n) = log((a/2^n)^8)/8获得技巧。基本上,这会得到一个结果(a + b/8 + c/64 + d/512) * 0.30102999566 - b,c,d在[0,7]范围内。 a.bcd实际上是一个八进制数。因为我们使用8作为动力,所以不足为奇。 (这个技巧同样适用于2号,4号或16号电源。)

[编辑4] 还有一个开放的结局。 pow(x, frac(y)只是pow(sqrt(x), 2 * frac(y)),我们有一个不错的1/sqrt(x)。这为我们提供了更有效的方法。说frac(y)=0.101二进制,即1/2加1/8。那么这意味着x^0.101(x^1/2 * x^1/8)。但x^1/2只是sqrt(x)x^1/8(sqrt(sqrt(sqrt(x)))。保存一个操作,Newton-Raphson NR(x)给我们1/sqrt(x),因此我们计算1.0/(NR(x)*NR((NR(NR(x)))。我们只反转最终结果,不要直接使用sqrt函数。

答案 1 :(得分:8)

下面是Clay S. Turner的定点对数基数2算法[1]的示例C实现。该算法不需要任何类型的查找表。这对于内存限制紧张且处理器缺少FPU的系统非常有用,例如许多微控制器就是这种情况。然后还使用对数属性支持日志库 e 和日志库10,对于任何基础 n

          log (x)
             y
log (x) = _______
   n      log (n)
             y

其中,对于此算法, y 等于2.

这个实现的一个很好的特性是它支持变量精度:精度可以在运行时确定,但代价是范围。我实现它的方式,处理器(或编译器)必须能够进行64位数学运算以保持一些中间结果。它可以很容易地适应不需要64位支持,但范围将会减少。

使用这些函数时,x应该是根据的缩放比例定义的定点值 指定precision。例如,如果precision为16,则x应缩放2 ^ 16(65536)。结果是一个定点值,其比例因子与输入相同。返回值INT32_MIN表示负无穷大。返回值INT32_MAX表示错误,errno将设置为EINVAL,表示输入精度无效。

#include <errno.h>
#include <stddef.h>

#include "log2fix.h"

#define INV_LOG2_E_Q1DOT31  UINT64_C(0x58b90bfc) // Inverse log base 2 of e
#define INV_LOG2_10_Q1DOT31 UINT64_C(0x268826a1) // Inverse log base 2 of 10

int32_t log2fix (uint32_t x, size_t precision)
{
    int32_t b = 1U << (precision - 1);
    int32_t y = 0;

    if (precision < 1 || precision > 31) {
        errno = EINVAL;
        return INT32_MAX; // indicates an error
    }

    if (x == 0) {
        return INT32_MIN; // represents negative infinity
    }

    while (x < 1U << precision) {
        x <<= 1;
        y -= 1U << precision;
    }

    while (x >= 2U << precision) {
        x >>= 1;
        y += 1U << precision;
    }

    uint64_t z = x;

    for (size_t i = 0; i < precision; i++) {
        z = z * z >> precision;
        if (z >= 2U << precision) {
            z >>= 1;
            y += b;
        }
        b >>= 1;
    }

    return y;
}

int32_t logfix (uint32_t x, size_t precision)
{
    uint64_t t;

    t = log2fix(x, precision) * INV_LOG2_E_Q1DOT31;

    return t >> 31;
}

int32_t log10fix (uint32_t x, size_t precision)
{
    uint64_t t;

    t = log2fix(x, precision) * INV_LOG2_10_Q1DOT31;

    return t >> 31;
}

此实现的代码也位于Github,以及一个示例/测试程序,该程序说明如何使用此函数计算和显示从标准输入读取的数字的对数。

[1] C. S. Turner,"A Fast Binary Logarithm Algorithm" IEEE Signal Processing Mag。,pp.124,140,2010年9月。

答案 2 :(得分:5)

一个很好的起点是Jack Crenshaw's book, "Math Toolkit for Real-Time Programming"。它对各种超越函数的算法和实现进行了很好的讨论。

答案 3 :(得分:3)

仅使用整数运算检查我的定点sqrt实现。 发明很有趣。现在相当老。

https://groups.google.com/forum/?hl=fr%05aacf5997b615c37&fromgroups#!topic/comp.lang.c/IpwKbw0MAxw/discussion

否则请检查CORDIC算法集。这是实现列出的所有功能和三角函数的方法。

编辑:我在GitHub here

上发布了经审核的来源