我想在 verilog A 中计算 ln(1+x)。 我知道代码是
<块引用>y = ln(1+x);
但如果 x 值变得非常小(例如 x=3.52e-18),则 y 值变为零。 在 MATLAB 中,我可以像这样计算
<块引用>y=log1p(x);
我想像那个代码一样计算。 如何计算更准确?
答案 0 :(得分:2)
添加不属于 IEEE 标准的数学函数的唯一方法是使用 C 接口。
在 SystemVerilog 中,您可以添加这一行,而不必编写任何 C 代码
import "DPI-C" function real log1p(real arg);
但在 Verilog 中,您必须调用一个 VPI 包装函数,该函数从您必须用 C 编写的代码中调用 log1p
。
答案 1 :(得分:1)
当然,您始终可以编写多项式或有理数或 table-driven 对 log1p 的近似,或者找到其他人已经实现它的库。
但是如果您已经有计算 log(?) 的函数,那么在紧要关头给定 ? 时,有一个计算 log(1 + ?) 的廉价技巧!
1 + x = 1
——也就是说,如果fl(1 + ?) = 1——那么就返回?。x*log(1 + x)/((1 + x) − 1)
(在分母中使用括号)。为什么会这样?
对于|?|的简单情况> 1/2,这是有效的,因为 fl(1 + ?) 在最坏的情况下只会从 ? 损失一点精度,所以商 ?/(fl(1 + ?) − 1) 基本上取消(或完全取消,如果 ? ≤ −1/2 或 ? ≥ 1) 基本上只剩下 log(fl(1 + ?)) ≈ log(1 + ?)。
对于 ulp(1)/2 < |?| 的有趣情况≤ 1/2,这是有效的,因为我们的近似因子为 ?⋅?(?) 其中 ?(?) := log(1 + ?)/? 在接近零的条件下非常好,因此我们仍然可以很好地近似于 ? (?) 即使我们实际评估 ?(fl(1 + ?) − 1)。
因此,虽然 log(1 + x)
单独放大了 1 + x
中的舍入误差,
x*log(1 + x)/((1 + x) - 1)
在极少数 ulps 内给出了对 log(1 + ?) 的一个很好的近似值(假设您的 log
函数有很小的误差)。
详情:
因此根据均值定理,? 和 fl(1 + ?) − 1 之间存在一些 ? 使得
|?(?) − ?(fl(1 + ?) − 1)| = |?′(?)⋅(? − [fl(1 + ?) − 1])| ≤ 2⋅|1 + ? − fl(1 + ?)| ≤ 2⋅ulp(1)/2 = ulp(1),
所以?(fl(1 + ?) − 1) 与?(?) 的相对误差是|?(?) − ?(fl(1 + ?) − 1)|/|?(?) | ≤ 2⋅ulp(1)。
而 ?(fl(1 + ?) − 1) 是 log(1 + x)/((1 + x) - 1)
的近似值。
(对于正输入,|?′(?)| ≤ 1/2 所以界限提高到 ulp(1)/2。)
1 + x = 1
发生的边缘情况当且仅当 |?| ≤ ulp(1)/2。
当这成立时,由于 log(1 + ?) = ? + O(?²) 序列截断的误差(具体来说,最多 |?|/2 ≤ ulp(1)/4,如果你看一下series) 并不比舍入误差差。
这种情况必须特别处理,以避免除以由 (1 + x) - 1
给出的零。
(这种技术在戈德堡关于 What Every Computer Scientist Should Know about Floating-Point Arithmetic 的臭名昭著的笔记中作为定理 4 出现。)