使用 const 非整数指数优化 pow() ?
我的代码中有一些热点,其中 pow()
占用了大约 10-20% 的执行时间。
我对 pow(x,y) 的输入非常具体,所以我想知道是否有一种方法可以滚动两个 pow()
近似值(每个指数一个)更高的性能:
- 我有两个常数指数:2.4 和 1/2.4。
- 当指数为 2.4 时,x 将在 (0.090473935, 1.0] 范围内。
- 当指数为 1/2.4 时,x 将在 (0.0031308, 1.0) 范围内]。
- 我正在使用 SSE/AVX
float
向量。
最大错误率约为 0.01% 是理想的,尽管我也对全精度(对于 float
)算法感兴趣,
我已经在使用快速 pow()
。 近似,但它没有考虑这些限制是否可以做得更好?
I have hot spots in my code where I'm doing pow()
taking up around 10-20% of my execution time.
My input to pow(x,y)
is very specific, so I'm wondering if there's a way to roll two pow()
approximations (one for each exponent) with higher performance:
- I have two constant exponents: 2.4 and 1/2.4.
- When the exponent is 2.4, x will be in the range (0.090473935, 1.0].
- When the exponent is 1/2.4, x will be in the range (0.0031308, 1.0].
- I'm using SSE/AVX
float
vectors. If platform specifics can be taken advantage of, right on!
A maximum error rate around 0.01% is ideal, though I'm interested in full precision (for float
) algorithms as well.
I'm already using a fast pow()
approximation, but it doesn't take these constraints into account. Is it possible to do better?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(10)
另一个答案,因为这与我之前的答案非常不同,而且速度非常快。相对误差为3e-8。想要更高的准确性吗?添加更多切比雪夫项。最好保持顺序为奇数,因为这会导致 2^n-epsilon 和 2^n+epsilon 之间出现小的不连续性。
附录:这是怎么回事?
根据请求,下面解释了上述代码的工作原理。
概述
上面的代码定义了两个函数,
double pow512norm (double x)
和double pow512 (double x)
。后者是套件的入口点;这是用户代码应该调用来计算 x^(5/12) 的函数。函数pow512norm(x)
使用切比雪夫多项式来近似 x^(5/12),但仅适用于 [1,2] 范围内的 x。 (对超出该范围的 x 值使用pow512norm(x)
,结果将是垃圾。)函数
pow512(x)
拆分传入的x 转换为一对
(double s, int n)
,使得x = s * 2^n
且 1≤s
2.将
n
进一步划分为(int q, unsigned int r)
,使得n = 12*q + r
和r< /code> 小于 12 让我将查找 x^(5/12) 的问题分成几部分:
x^(5/12)=(s^(5/12))*((2^n)^(5/12))
通过 (uv)^a=( u^a)(v^a) 表示正 u,v 和实数 a。s^(5/12)
通过pow512norm(s)
计算。(2^n)^(5/12)=(2^(12*q+r))^(5/12)
通过替换。2^(12*q+r)=(2^(12*q))*(2^r)
通过u^(a+b)=(u^a)* (u^b)
表示正数 u,实数 a,b。(2^(12*q+r))^(5/12)=(2^(5*q))*((2^r)^(5/12))
通过一些更多的操纵。(2^r)^(5/12)
通过查找表pow2_512
计算。pow512norm(s)*pow2_512[qr.rem]
我们就快到了。这里的qr.rem
是上面第3步计算出的r
值。只需将其乘以2^(5*q)
即可得到所需的结果。函数逼近
这里的目标是提出一个易于计算的 f(x)=x^(5/12) 近似值,对于当前的问题来说“足够好”。从某种意义上说,我们的近似值应该接近 f(x)。反问句:“接近”是什么意思?两种相互竞争的解释是最小化均方误差与最小化最大绝对误差。
我将使用股票市场的类比来描述它们之间的区别。假设您想为最终的退休储蓄。如果你二十几岁,最好的办法就是投资股票或股市基金。这是因为在足够长的时间内,股市平均胜过任何其他投资计划。然而,我们都见过把钱投入股票是一件非常糟糕的事情的时候。如果您五六十岁(或者四十多岁,如果您想年轻退休),您需要更加保守地投资。这些经济衰退可能会对您的退休投资组合造成影响。
回到函数逼近:作为某种逼近的使用者,您通常担心最坏情况的错误,而不是“平均”性能。使用一些近似值来提供“平均”的最佳性能(例如最小二乘法),墨菲定律规定您的程序将花费大量时间在性能远低于平均水平的地方使用近似值。您想要的是一个极小极大近似,可以最小化某个域上的最大绝对误差。一个好的数学库将采用极小极大方法而不是最小二乘方法,因为这可以让数学库的作者为其库提供一些有保证的性能。
数学库通常使用多项式或有理多项式来近似某个域 a≤x≤b 上的某个函数 f(x)。假设函数 f(x) 在此域上解析,并且您希望通过某个 N 次多项式 p(x) 来近似该函数。对于给定的 N 次,存在一些神奇的、唯一的多项式 p(x) 使得 p( x)-f(x) 在 [a,b] 上具有 N+2 个极值,并且这些 N+2 极值的绝对值都彼此相等。找到这个神奇的多项式 p(x) 是函数逼近器的圣杯。
我没有找到适合你的圣杯。我改为使用切比雪夫近似。第一类切比雪夫多项式是一组正交(但不是正交)多项式,在函数逼近方面具有一些非常好的特征。切比雪夫近似通常非常接近那个神奇的多项式 p(x)。 (事实上,Remez 交换算法确实发现了圣杯多项式通常以切比雪夫近似开始。)
pow512norm(x)
此函数使用切比雪夫近似来查找近似 x^(5/12) 的多项式 p*(x)。在这里,我使用 p*(x) 来区分切比雪夫近似和上述神奇的多项式 p(x)。切比雪夫近似 p*(x) 很容易求得;发现 p(x) 是一只熊。切比雪夫近似 p*(x) 为 sum_i Cn[i]*Tn(i,x),其中 Cn[i] 是切比雪夫系数,Tn(i,x) 是在 x 处计算的切比雪夫多项式。
我使用 Wolfram alpha 为我找到切比雪夫系数
Cn
。例如,此计算Cn[1]
。输入框后面的第一个框包含所需的答案,在本例中为 0.166658。这没有我想要的那么多数字。单击“更多数字”,瞧,您会得到更多的数字。 Wolfram alpha 是免费的;它执行的计算量是有限制的。它在高阶项上达到了该限制。 (如果您购买或有权访问 mathematica,您将能够高精度地计算这些高阶系数。)切比雪夫多项式 Tn(x) 在数组
Tn
中计算。除了给出非常接近神奇多项式 p(x) 的值之外,使用切比雪夫近似的另一个原因是这些切比雪夫多项式的值很容易计算:从Tn[0]=1
和开始Tn[1]=x
,然后迭代计算Tn[i]=2*x*Tn[i-1] - Tn[i-2]
。 (我在代码中使用“ii”作为索引变量而不是“i”。我从不使用“i”作为变量名。英语中有多少个单词中有一个“i”?有多少个单词有两个连续的“i”?)pow512(x)
pow512
是用户代码应该调用的函数。我已经在上面描述了这个函数的基础知识。更多细节:数学库函数frexp(x)
返回输入x
的有效数字s
和指数iexp
代码>. (小问题:我希望s
介于 1 和 2 之间,以便与pow512norm
一起使用,但frexp
返回 0.5 到 1 之间的值。) 数学库函数div
在一次 swell foop 中返回整数除法的商和余数。最后,我使用数学库函数 ldexp 将这三个部分组合在一起形成最终答案。Another answer because this is very different from my previous answer, and this is blazing fast. Relative error is 3e-8. Want more accuracy? Add a couple more Chebychev terms. It's best to keep the order odd as this makes for a small discontinuity between 2^n-epsilon and 2^n+epsilon.
Addendum: What's going on here?
Per request, the following explains how the above code works.
Overview
The above code defines two functions,
double pow512norm (double x)
anddouble pow512 (double x)
. The latter is the entry point to the suite; this is the function that user code should call to calculate x^(5/12). The functionpow512norm(x)
uses Chebyshev polynomials to approximate x^(5/12), but only for x in the range [1,2]. (Usepow512norm(x)
for values of x outside that range and the result will be garbage.)The function
pow512(x)
splits the incomingx
into a pair(double s, int n)
such thatx = s * 2^n
and such that 1≤s
<2. A further partitioning ofn
into(int q, unsigned int r)
such thatn = 12*q + r
andr
is less than 12 lets me split the problem of finding x^(5/12) into parts:x^(5/12)=(s^(5/12))*((2^n)^(5/12))
via (uv)^a=(u^a)(v^a) for positive u,v and real a.s^(5/12)
is calculated viapow512norm(s)
.(2^n)^(5/12)=(2^(12*q+r))^(5/12)
via substitution.2^(12*q+r)=(2^(12*q))*(2^r)
viau^(a+b)=(u^a)*(u^b)
for positive u, real a,b.(2^(12*q+r))^(5/12)=(2^(5*q))*((2^r)^(5/12))
via some more manipulations.(2^r)^(5/12)
is calculated by the lookup tablepow2_512
.pow512norm(s)*pow2_512[qr.rem]
and we're almost there. Hereqr.rem
is ther
value calculated in step 3 above. All that is needed is to multiply this by2^(5*q)
to yield the desired result.ldexp
does.Function Approximation
The goal here is to come up with an easily computable approximation of f(x)=x^(5/12) that is 'good enough' for the problem at hand. Our approximation should be close to f(x) in some sense. Rhetorical question: What does 'close to' mean? Two competing interpretations are minimizing the mean square error versus minimizing the maximum absolute error.
I'll use a stock market analogy to describe the difference between these. Suppose you want to save for your eventual retirement. If you are in your twenties, the best thing to do is to invest in stocks or stock market funds. This is because over a long enough span of time, the stock market on average beats any other investment scheme. However, we've all seen times when putting money into stocks is a very bad thing to do. If you are in your fifties or sixties (or forties if you want to retire young) you need to invest a bit more conservatively. Those downswings can wreak have on your retirement portfolio.
Back to function approximation: As the consumer of some approximation, you are typically worried about the worst-case error rather than the performance "on average". Use some approximation constructed to give the best performance "on average" (e.g. least squares) and Murphy's law dictates that your program will spend a whole lot of time using the approximation exactly where the performance is far worse than average. What you want is a minimax approximation, something that minimizes the maximum absolute error over some domain. A good math library will take a minimax approach rather than a least squares approach because this lets the authors of the math library give some guaranteed performance of their library.
Math libraries typically use a polynomial or a rational polynomial to approximate some function f(x) over some domain a≤x≤b. Suppose the function f(x) is analytic over this domain and you want to approximate the function by some polynomial p(x) of degree N. For a given degree N there exists some magical, unique polynomial p(x) such that p(x)-f(x) has N+2 extrema over [a,b] and such that the absolute values of these N+2 extrema are all equal to one another. Finding this magical polynomial p(x) is the holy grail of function approximators.
I did not find that holy grail for you. I instead used a Chebyshev approximation. The Chebyshev polynomials of the first kind are an orthogonal (but not orthonormal) set of polynomials with some very nice features when it comes to function approximation. The Chebyshev approximation oftentimes is very close to that magical polynomial p(x). (In fact, the Remez exchange algorithm that does find that holy grail polynomial typically starts with a Chebyshev approximation.)
pow512norm(x)
This function uses Chebyshev approximation to find some polynomial p*(x) that approximates x^(5/12). Here I'm using p*(x) to distinguish this Chebyshev approximation from the magical polynomial p(x) described above. The Chebyshev approximation p*(x) is easy to find; finding p(x) is a bear. The Chebyshev approximation p*(x) is sum_i Cn[i]*Tn(i,x), where the Cn[i] are the Chebyshev coefficients and Tn(i,x) are the Chebyshev polynomials evaluated at x.
I used Wolfram alpha to find the Chebyshev coefficients
Cn
for me. For example, this calculatesCn[1]
. The first box after the input box has the desired answer, 0.166658 in this case. That's not as many digits as I would like. Click on 'more digits' and voila, you get a whole lot more digits. Wolfram alpha is free; there is a limit on how much computation it will do. It hits that limit on higher order terms. (If you buy or have access to mathematica you will be able to calculate those high-order coefficients to a high degree of precision.)The Chebyshev polynomials Tn(x) are calculated in the array
Tn
. Beyond giving something very close to magical polynomial p(x), another reason for using Chebyshev approximation is that the values of those Chebyshev polynomials are easily calculated: Start withTn[0]=1
andTn[1]=x
, and then iteratively calculateTn[i]=2*x*Tn[i-1] - Tn[i-2]
. (I used 'ii' as the index variable rather than 'i' in my code. I never use 'i' as a variable name. How many words in the English language have an 'i' in the word? How many have two consecutive 'i's?)pow512(x)
pow512
is the function that user code should be calling. I already described the basics of this function above. A few more details: The math library functionfrexp(x)
returns the significands
and exponentiexp
for the inputx
. (Minor issue: I wants
between 1 and 2 for use withpow512norm
butfrexp
returns a value between 0.5 and 1.) The math library functiondiv
returns the quotient and remainder for integer division in one swell foop. Finally, I use the math library functionldexp
to put the three parts together to form the final answer.在 IEEE 754 黑客脉络中,这是另一种更快且不那么“神奇”的解决方案。它在大约十几个时钟周期内实现了 0.08% 的误差范围(对于 p=2.4 的情况,在 Intel Merom CPU 上)。
浮点数最初是作为对数的近似值而发明的,因此您可以使用整数值作为
log2
的近似值。这可以通过将整数转换指令应用于浮点值来获得另一个浮点值来实现。要完成
pow
计算,您可以乘以一个常数因子并使用转换为整数指令将对数转换回来。在SSE上,相关指令是cvtdq2ps
和cvtps2dq
。但事情并不那么简单。 IEEE 754 中的指数字段是有符号的,偏差值为 127,表示指数为零。在乘对数之前必须消除这种偏差,并在求幂之前重新添加偏差。此外,通过减法进行的偏差调整在零时不起作用。幸运的是,这两种调整都可以通过预先乘以一个常数因子来实现。
exp2( 127 / p - 127 )
是常数因子。这个函数相当特殊:它不适用于小分数指数,因为常数因子随指数的倒数呈指数增长,并且会溢出。它不适用于负指数。大指数会导致高错误,因为乘法时尾数位与指数位混合在一起。但是,它只有 4 条快速指令长。预乘,从“整数”转换(到对数),幂乘,转换到“整数”(从对数)。在 SSE 的实施中,转换速度非常快。我们还可以在第一个乘法中压缩一个额外的常数系数。
一些指数 = 2.4 的试验表明,这一结果始终高估了约 5%。 (该例程总是会高估。)您可以简单地乘以 0.95,但更多的指令将使我们获得大约 4 位小数的精度,这对于图形来说应该足够了。
关键是将高估与低估相匹配,并取平均值。
mulps
。mulps
。addps
,1 个mulps
。指令计数:十四个,包括两个延迟 = 5 的转换和两个吞吐量 = 4 的倒数平方根估计。
为了正确地取平均值,我们希望根据预期误差对估计进行加权。低估将误差提高到 0.6 与 0.4 的幂,因此我们预计误差为 1.5 倍。加权不添加任何说明;它可以在前置因素中完成。称系数a为:a^0.5 = 1.5 a^-0.75,a = 1.38316186。
最终误差约为 0.015%,比初始
fastpow
结果好 2 个数量级。对于具有易失性源和目标变量的繁忙循环,运行时间大约是十几个周期……尽管它与迭代重叠,但实际使用也将看到指令级并行性。考虑到 SIMD,这是每 3 个周期一个标量结果的吞吐量!嗯……抱歉我没能早点发布这个。将其扩展到 x^1/2.4 留作练习;v) 。
使用统计数据更新
我实现了一个小测试工具和两个与上述相对应的 x(5⁄12) 案例。
输出:
我怀疑更准确的 5/12 的准确性受到 rsqrt 操作的限制。
In the IEEE 754 hacking vein, here is another solution which is faster and less "magical." It achieves an error margin of .08% in about a dozen clock cycles (for the case of p=2.4, on an Intel Merom CPU).
Floating point numbers were originally invented as an approximation to logarithms, so you can use the integer value as an approximation of
log2
. This is somewhat-portably achievable by applying the convert-from-integer instruction to a floating-point value, to obtain another floating-point value.To complete the
pow
computation, you can multiply by a constant factor and convert the logarithm back with the convert-to-integer instruction. On SSE, the relevant instructions arecvtdq2ps
andcvtps2dq
.It's not quite so simple, though. The exponent field in IEEE 754 is signed, with a bias value of 127 representing an exponent of zero. This bias must be removed before you multiply the logarithm, and re-added before you exponentiate. Furthermore, bias adjustment by subtraction won't work on zero. Fortunately, both adjustments can be achieved by multiplying by a constant factor beforehand.
exp2( 127 / p - 127 )
is the constant factor. This function is rather specialized: it won't work with small fractional exponents, because the constant factor grows exponentially with the inverse of the exponent and will overflow. It won't work with negative exponents. Large exponents lead to high error, because the mantissa bits are mingled with the exponent bits by the multiplication.But, it's just 4 fast instructions long. Pre-multiply, convert from "integer" (to logarithm), power-multiply, convert to "integer" (from logarithm). Conversions are very fast on this implementation of SSE. We can also squeeze an extra constant coefficient into the first multiplication.
A few trials with exponent = 2.4 show this consistently overestimates by about 5%. (The routine is always guaranteed to overestimate.) You could simply multiply by 0.95, but a few more instructions will get us about 4 decimal digits of accuracy, which should be enough for graphics.
The key is to match the overestimate with an underestimate, and take the average.
rsqrtps
. (This is quite accurate enough, but does sacrifice the ability to work with zero.)mulps
.rsqrtps
.mulps
.mulps
.mulps
. This is the overestimate.mulps
. This is the underestimate.addps
, onemulps
.Instruction tally: fourteen, including two conversions with latency = 5 and two reciprocal square root estimates with throughput = 4.
To properly take the average, we want to weight the estimates by their expected errors. The underestimate raises the error to a power of 0.6 vs 0.4, so we expect it to be 1.5x as erroneous. Weighting doesn't add any instructions; it can be done in the pre-factor. Calling the coefficient a: a^0.5 = 1.5 a^-0.75, and a = 1.38316186.
The final error is about .015%, or 2 orders of magnitude better than the initial
fastpow
result. The runtime is about a dozen cycles for a busy loop withvolatile
source and destination variables… although it's overlapping the iterations, real-world usage will also see instruction-level parallelism. Considering SIMD, that's a throughput of one scalar result per 3 cycles!Well… sorry I wasn't able to post this sooner. And extending it to x^1/2.4 is left as an exercise ;v) .
Update with stats
I implemented a little test harness and two x(5⁄12) cases corresponding to the above.
Output:
I suspect accuracy of the more accurate 5/12 is being limited by the
rsqrt
operation.Ian Stephenson 编写了这段代码,他声称该代码的性能优于
pow().他描述了这个想法如下:
Ian Stephenson wrote this code which he claims outperforms
pow()
. He describes the idea as follows:首先,在当今的大多数机器上使用浮子并不会带来太多好处。事实上,双打可以更快。您的力量 1.0/2.4 是 5/12 或 1/3*(1+1/4)。尽管这调用了 cbrt(一次)和 sqrt(两次!),但它仍然比使用 pow() 快两倍。 (优化:-O3,编译器:i686-apple-darwin10-g++-4.2.1)。
First off, using floats isn't going to buy much on most machines nowadays. In fact, doubles can be faster. Your power, 1.0/2.4, is 5/12 or 1/3*(1+1/4). Even though this is calling cbrt (once) and sqrt (twice!) it is still twice as fast as using pow(). (Optimization: -O3, compiler: i686-apple-darwin10-g++-4.2.1).
这可能无法回答您的问题。
2.4f
和1/2.4f
让我非常怀疑,因为这些正是用于在 sRGB 和线性 RGB 色彩空间之间进行转换的能力。因此,您实际上可能正在尝试具体优化这一点。我不知道,这就是为什么这可能无法回答您的问题。如果是这种情况,请尝试使用查找表。例如:
如果您使用 16 位数据,请进行适当的更改。无论如何,我都会将表设置为 16 位,以便在处理 8 位数据时如有必要,您可以抖动结果。如果您的数据一开始就是浮点型,这显然不会很好地工作 - 但以浮点型存储 sRGB 数据并没有真正意义,因此您最好先转换为 16 位/8 位然后进行从线性到 sRGB 的转换。
(sRGB 作为浮点没有意义的原因是 HDR 应该是线性的,而 sRGB 只方便在磁盘上存储或在屏幕上显示,但不方便操作。)
This might not answer your question.
The
2.4f
and1/2.4f
make me very suspicious, because those are exactly the powers used to convert between sRGB and a linear RGB color space. So you might actually be trying to optimize that, specifically. I don't know, which is why this might not answer your question.If this is the case, try using a lookup table. Something like:
If you are using 16-bit data, change as appropriate. I would make the table 16 bits anyway so you can dither the result if necessary when working with 8-bit data. This obviously won't work very well if your data is floating point to begin with -- but it doesn't really make sense to store sRGB data in floating point, so you might as well convert to 16-bit / 8-bit first and then do the conversion from linear to sRGB.
(The reason sRGB doesn't make sense as floating point is that HDR should be linear, and sRGB is only convenient for storing on disk or displaying on screen, but not convenient for manipulation.)
我将回答您真正想问的问题,即如何快速实现 sRGB <->线性 RGB 转换。为了精确有效地做到这一点,我们可以使用多项式近似。以下多项式近似值是用 sollya 生成的,最坏情况下的相对误差为 0.0144%。
以及用于生成多项式的 sollya 输入:
I shall answer the question you really wanted to ask, which is how to do fast sRGB <-> linear RGB conversion. To do this precisely and efficiently we can use polynomial approximations. The following polynomial approximations have been generated with sollya, and have a worst case relative error of 0.0144%.
And the sollya input used to generate the polynomials:
二项式级数确实考虑了常数指数,但只有在可以的情况下才能使用它将所有输入标准化为范围 [1,2)。 (请注意,它计算 (1+x)^a)。您必须进行一些分析来决定需要多少项才能达到所需的准确性。
Binomial series does account for a constant exponent, but you will be able to use it only if you can normalize all your input to the range [1,2). (Note that it computes (1+x)^a). You'll have to do some analysis to decide how many terms you need for your desired accuracy.
对于 2.4 的指数,您可以为所有 2.4 值和 lirp 或高阶函数创建一个查找表,如果该表不够准确(基本上是一个巨大的日志表),则可以填充中间值。
或者,值平方 * 值到 2/5s,可以从函数的前半部分取初始平方值,然后对其进行 5 次方根。对于五次方根,您可以使用牛顿法或做一些其他快速逼近器,但老实说,一旦您达到这一点,您可能最好自己使用适当的缩写级数函数来执行 exp 和 log 函数。
For exponents of 2.4, you could either make a lookup table for all your 2.4 values and lirp or perhaps higher-order function to fill in the in-betweem values if the table wasn't accurate enough (basically a huge log table.)
Or, value squared * value to the 2/5s which could take the initial square value from the first half of the function and then 5th root it. For the 5th root, you could Newton it or do some other fast approximator, though honestly once you get to this point, your probably better off just doing the exp and log functions with the appropriate abbreviated series functions yourself.
以下是您可以与任何快速计算方法一起使用的想法。它是否有助于加快速度取决于数据的到达方式。您可以利用以下事实:如果您知道
x
和pow(x, n)
,则可以使用功率的变化率来计算的合理近似值>pow(x + delta, n)
用于较小的 delta,具有单个乘法和加法(或多或少)。如果您为幂函数提供的连续值足够接近,则这将分摊多个函数调用的精确计算的全部成本。请注意,您不需要额外的 pow 计算来获得导数。您可以扩展它以使用二阶导数,这样您就可以使用二次函数,这将增加您可以使用的增量,并且仍然获得相同的精度。The following is an idea you can use with any of the fast calculation methods. Whether it helps things go faster depends on how your data arrives. You can use the fact that if you know
x
andpow(x, n)
, you can use the rate of change of the power to compute a reasonable approximation ofpow(x + delta, n)
for smalldelta
, with a single multiply and add (more or less). If successive values you feed your power functions are close enough together, this would amortize the full cost of the accurate calculation over multiple function calls. Note that you don't need an extra pow calculation to get the derivative. You could extend this to use the second derivative so you can use a quadratic, which would increase thedelta
you could use and still get the same accuracy.因此,传统上,powf(x, p) = x^p 是通过将
x
重写为x=2^(log2(x))
来解决的使得powf(x,p) = 2^(p*log2(x))
,将问题转化为两个近似值exp2()
&log2()
。这样做的优点是可以使用较大的功率p
,但缺点是这不是恒定功率p
和指定输入范围的最佳解决方案0 ≤ x ≤ 1。
当功率
p>时1
,答案是在界限0 ≤ x ≤ 1
上的一个平凡极小极大多项式,p = 12/5 = 2.4
就是这种情况,可以如下所示:然而,当
p < 1
边界0 ≤ x ≤ 1
上的极小极大近似没有适当地收敛到所需的精度。一种选择[不是真的]是重写问题y=x^p=x^(p+m)/x^m
其中m=1,2,3
是一个正整数,使得新的幂近似p > 1
但这引入了本质上较慢的除法。然而,还有另一种选择是将输入
x
分解为其浮点指数和尾数形式:mx^(5/12)
在1 上的极小极大近似≤mx< 2
现在收敛速度比以前快得多,无需除法,但2^(k/12)
需要 12 点 LUT。代码如下:So traditionally the
powf(x, p) = x^p
is solved by rewritingx
asx=2^(log2(x))
makingpowf(x,p) = 2^(p*log2(x))
, which transforms the problem into two approximationsexp2()
&log2()
. This has the advantage of working with larger powersp
, however the downside is that this is not the optimal solution for a constant powerp
and over a specified input bound0 ≤ x ≤ 1
.When the power
p > 1
, the answer is a trivial minimax polynomial over the bound0 ≤ x ≤ 1
, which is the case forp = 12/5 = 2.4
as can be seen below:However when
p < 1
the minimax approximation over the bound0 ≤ x ≤ 1
does not appropriately converge to the desired accuracy. One option [not really] is to rewrite the problemy=x^p=x^(p+m)/x^m
wherem=1,2,3
is a positive integer, making the new power approximationp > 1
but this introduces division which is inherently slower.There's however another option which is to decompose the input
x
as its floating point exponent and mantissa form:The minimax approximation of
mx^(5/12)
over1 ≤ mx < 2
now converges much faster than before, without division, but requires 12 point LUT for the2^(k/12)
. The code is below: