3 ms·
Re: centers on the octave [2/3, 4/3] but the error is actually more balanced across [1/sqrt(2), sqrt(2)] For the computation of the natural logarithm using bin
by njuffa 2y ago
Re: centers on the octave [2/3, 4/3] but the error is actually more balanced across [1/sqrt(2), sqrt(2)]
For the computation of the natural logarithm using binary floating-point arithmetic there is no practical difference, i.e. the overall accuracy that can be achieved is basically the same. Here is a worked example based on minimax core approximations extracted from my personal code collection (design and implementation are my own; no guarantee of fitness for any particular purpose).
/* natural logarithm */
float my_logf (float a)
{
float m, r, s, t, i, f;
int32_t e;
#if LOG_VARIANT == 1
// max ulp err = 0.86280
const float cutoff_f = 0.707106781f;
#elif LOG_VARIANT == 3
// max ulp err = 0.85089
const float cutoff_f = 0.666666667f;
#endif // LOG_VARIANT
if ((a > 0.0f) && (a <= 0x1.fffffep+127f)) { // 3.40282347e+38f
m = frexpf (a, &e);
if (m < cutoff_f) {
m = m + m;
e = e - 1;
}
i = (float)e;
f = m - 1.0f;
s = f * f;
#if LOG_VARIANT == 1
/* Compute log1p(m) for m in [sqrt(0.5)-1, sqrt(2.0)-1] */
r = -0x1.384000p-4f; // -0.076232910
t = 0x1.084000p-3f; // 0.129028320
r = fmaf (r, s, -0x1.0f218cp-3f); // -0.132388204
t = fmaf (t, s, 0x1.226b04p-3f); // 0.141805679
r = fmaf (r, s, -0x1.5427a4p-3f); // -0.166091233
t = fmaf (t, s, 0x1.99a35ap-3f); // 0.200018600
r = fmaf (r, s, -0x1.000416p-2f); // -0.250015587
r = fmaf (t, f, r);
r = fmaf (r, f, 0x1.55555cp-2f); // 0.333333433
r = fmaf (r, f, -0x1.fffffap-2f); // -0.499999911
#elif LOG_VARIANT == 3
/* Compute log1p(f) for f in [-1/3, 1/3] */
r = -0x1.0ae000p-3f; // -0.130310059
t = 0x1.208000p-3f; // 0.140869141
r = fmaf (r, s, -0x1.f1988ap-4f); // -0.121483363
t = fmaf (t, s, 0x1.1e5740p-3f); // 0.139814854
r = fmaf (r, s, -0x1.55b36ep-3f); // -0.166846141
t = fmaf (t, s, 0x1.99d8b2p-3f); // 0.200120345
r = fmaf (r, s, -0x1.fffe02p-3f); // -0.249996200
r = fmaf (t, f, r);
r = fmaf (r, f, 0x1.5554fap-2f); // 0.333331972
r = fmaf (r, f, -0x1.000000p-1f); // -0.500000000
#endif // LOG_VARIANT
r = fmaf (r, s, f);
r = fmaf (i, 0x1.62e430p-01f, r); // 0.693147182 // log(2)
} else {
r = a + a; // silence NaNs if necessary
if (a < 0.0f) r = 0.0f/0.0f; // QNaN INDEFINITE
if (a == 0.0f) r = -INFINITY; // -INF
}
return r;
}
- cb321 2y agoThanks for your code post, but your fused-multiply-add Horner coeffs seem like they are not from the same power series I was discussing / the article was promoting. The effects I mention are pretty clear if you run my code with both `x = 2/3.0` and with `x = math.sqrt(0.5)`, even with a much smaller `m` / coarser granularity. For example { for the python &| copy-paste impaired &| lazy :-) }: $ python3 j.py | headtail 3 3 # [2/3..4/3], m=300 0.6666666666666666 3.7747748309890916e-06 0.6688888888888889 3.5668329603222304e-06 0.6711111111111111 3.3693446750415745e-06 --- 1.3266666666666544 -3.120628879194065e-07 1.3288888888888766 -3.2510972441945896e-07 1.3311111111110987 -3.3859621345611046e-07 $ python3 k.py | headtail 3 3 # [rt1/2..rt2], m=300 0.7071067811865476 1.279804798859363e-06 0.7094638037905028 1.19727672415193e-06 0.711820826394458 1.1195756742798935e-06 --- 1.4071424945612245 -1.1572535666970474e-06 1.4094995171651796 -1.1970089550117358e-06 1.4118565397691347 -1.2378520898859158e-06 Note in the 2/3..4/3 case the exponent changes from e-06 to e-07 - a whole order of magnitude difference in |error|, while in the rt1/2..rt2 case the nearness of |error|. This same pattern manifests for higher numbers of terms like 8 or 9 or higher resolution like m=3840.