|
| 1 | +// f32 Low trig coefficients: sin(x) ~= x + x^3*P(x^2), degree 9, odd. |
| 2 | +// 333/346/368 are recoverable Remez warnings the constrained refits below |
| 3 | +// always trip; the driver would treat them as errors. |
| 4 | +suppressmessage(346, 333, 368); |
| 5 | + |
| 6 | +I = [0x1p-20; pi/2]; |
| 7 | +vFree = fpminimax(sin(x), [|3, 5, 7, 9|], [|single...|], I, relative, floating, x); |
| 8 | + |
| 9 | +// Relative error in ULP at 1 (= 2^-24); guards regeneration against a worse fit. |
| 10 | +procedure ErrULP(pP) { return dirtyinfnorm(pP(x)/sin(x) - 1, I) * 0x1p24; }; |
| 11 | +Assert(ErrULP(vFree) < 0.12, "f32 Low free fit error regressed: " @ ErrULP(vFree)); |
| 12 | + |
| 13 | +// One f32 ULP at a coefficient's scale. No coefficient sits within a step of |
| 14 | +// a power of two, so the constant step is exact. |
| 15 | +procedure Ulp(pC) { return 2^(floor(log2(abs(pC))) - 23); }; |
| 16 | + |
| 17 | +// Pin c3 (then c5, then optionally c7) an integer number of ULPs off the free |
| 18 | +// fit and refit the rest under the pin. Pin indices come from exhaustive |
| 19 | +// measurement of the compiled kernel over [0, 1e4); no analytic criterion |
| 20 | +// sees the eval DAG's per-operation rounding. |
| 21 | +procedure Stage2(pK3, pJ5) { |
| 22 | + var vC3, vP5, vC5, vP7; |
| 23 | + vC3 = round(coeff(vFree, 3) + pK3 * Ulp(coeff(vFree, 3)), single, RN); |
| 24 | + vP5 = fpminimax(sin(x), [|5, 7, 9|], [|single...|], I, relative, floating, |
| 25 | + x + vC3 * x^3); |
| 26 | + vC5 = round(coeff(vP5, 5) + pJ5 * Ulp(coeff(vP5, 5)), single, RN); |
| 27 | + vP7 = fpminimax(sin(x), [|7, 9|], [|single...|], I, relative, floating, |
| 28 | + x + vC3 * x^3 + vC5 * x^5); |
| 29 | + return [| vC3, vC5, vP7 |]; |
| 30 | +}; |
| 31 | + |
| 32 | +procedure CheckPinned(pSet, pTag) { |
| 33 | + Assert(ErrULP(pSet[0]*x^3 + pSet[1]*x^5 + pSet[2]*x^7 + pSet[3]*x^9 + x) < 1.0, |
| 34 | + "pinned set " @ pTag @ " analytic error drifted"); |
| 35 | + return pSet; |
| 36 | +}; |
| 37 | + |
| 38 | +// c7/c9 come jointly from the stage-two fit. |
| 39 | +procedure Pinned(pK3, pJ5) { |
| 40 | + var vS; |
| 41 | + vS = Stage2(pK3, pJ5); |
| 42 | + return CheckPinned([| vS[0], vS[1], coeff(vS[2], 7), coeff(vS[2], 9) |], |
| 43 | + "(" @ pK3 @ "," @ pJ5 @ ")"); |
| 44 | +}; |
| 45 | + |
| 46 | +// Additionally pin c7 and refit c9 alone. Only FMA cos gains from it. |
| 47 | +procedure Pinned3(pK3, pJ5, pL7) { |
| 48 | + var vS, vC7, vP9; |
| 49 | + vS = Stage2(pK3, pJ5); |
| 50 | + vC7 = round(coeff(vS[2], 7) + pL7 * Ulp(coeff(vS[2], 7)), single, RN); |
| 51 | + vP9 = fpminimax(sin(x), [|9|], [|single...|], I, relative, floating, |
| 52 | + x + vS[0] * x^3 + vS[1] * x^5 + vC7 * x^7); |
| 53 | + return CheckPinned([| vS[0], vS[1], vC7, coeff(vP9, 9) |], |
| 54 | + "(" @ pK3 @ "," @ pJ5 @ "," @ pL7 @ ")"); |
| 55 | +}; |
| 56 | + |
| 57 | +// Exhaustive [0, 1e4) worst case, pinned vs free fit, in ULP: |
| 58 | +// FMA: sin 1.886 (free 2.191) cos 1.815 (free 2.110) |
| 59 | +// non-FMA: sin 1.912 (free 1.939) cos 1.948 (free 1.966) |
| 60 | +// Non-FMA is eval-rounding bound (~1.9 of the ~1.95 total), so pins only |
| 61 | +// shift which argument realizes the worst alignment; the whole (k3, j5, l7) |
| 62 | +// grid stays within 0.02 of the sets below. |
| 63 | +vFmaSin = Pinned(4, 1); |
| 64 | +vFmaCos = Pinned3(2, 1, 8); |
| 65 | +vNoFmaSin = Pinned(1, 1); |
| 66 | +vNoFmaCos = Pinned(1, 5); |
| 67 | + |
| 68 | +Append( |
| 69 | + "// Degree-9 odd minimax sin(x) = x + x^3*P(x^2) on [2^-20, pi/2].", |
| 70 | + "// Coefficients {c3, c5, c7, c9} for the f32 PolyLow in npsr/trig/low-inl.h.", |
| 71 | + "// See poly.h.sol for the recipe, pins and measured worst cases.", |
| 72 | + "template <bool FMA> inline constexpr float kSinPolyLowF32[] = " @ CArrayT(Float32, vFmaSin, 4) @ ";", |
| 73 | + "template <bool FMA> inline constexpr float kCosPolyLowF32[] = " @ CArrayT(Float32, vFmaCos, 4) @ ";", |
| 74 | + "template <> inline constexpr float kSinPolyLowF32<false>[] = " @ CArrayT(Float32, vNoFmaSin, 4) @ ";", |
| 75 | + "template <> inline constexpr float kCosPolyLowF32<false>[] = " @ CArrayT(Float32, vNoFmaCos, 4) @ ";", |
| 76 | + "" |
| 77 | +); |
| 78 | + |
| 79 | +// f32 High coefficients, held and evaluated in double by npsr/trig/high-inl.h. |
| 80 | +// |
| 81 | +// c1 is fitted, not pinned to 1: MulAdd(r2, poly, c1) + Mul(poly, r) closes in |
| 82 | +// the same two ops as the pinned x + x^3*P(x^2), so the free parameter is free |
| 83 | +// -- 0.1099 -> 0.0965 ulp. No pinning stage either: eval is in double (~2^-52), |
| 84 | +// so unlike the single Low sets above there is no rounding for pins to steer. |
| 85 | +// |
| 86 | +// The interval tracks the cutover in npsr/trig/inl.h and must move with it: n |
| 87 | +// comes from a f32 magic-round, so |r| overshoots pi/2 by up to |x|*1.7e-7. |
| 88 | +// 0.012 is the exhaustive ceiling below both cutovers, and widening costs |
| 89 | +// accuracy fast: 0.0892 ulp at pi/2, 0.0965 here, 0.1073 at 0.028. |
| 90 | +IHigh = [0x1p-30; pi/2 + 0.012]; |
| 91 | +vHigh = fpminimax(sin(x), [|1, 3, 5, 7, 9|], [|double...|], IHigh, relative, floating); |
| 92 | + |
| 93 | +// Leaves High<> at 0.5 (final f32 rounding) + 0.097, the f32 ceiling: the |
| 94 | +// Extended kernel above the cutover measures 0.5031, so this fit -- not the |
| 95 | +// Payne-Hanek reduction -- is what keeps sin/cos off correct rounding. |
| 96 | +errHigh = dirtyinfnorm(vHigh(x)/sin(x) - 1, IHigh) * 0x1p24; |
| 97 | +Assert(errHigh < 0.0970, "f32 High fit error regressed: " @ errHigh); |
| 98 | +// The driver leaves display in hexadecimal, which is unreadable for an error. |
| 99 | +display = decimal!; |
| 100 | +errHighStr = "" @ round(errHigh, 24, RN); |
| 101 | +display = hexadecimal!; |
| 102 | + |
| 103 | +Append( |
| 104 | + "// Degree-9 odd minimax sin(r) on [2^-30, pi/2 + 0.012] for the f32 High path", |
| 105 | + "// in trig/high-inl.h; c1 is fitted, not pinned to 1. See poly.h.sol.", |
| 106 | + "// Relative error " @ errHighStr @ " ulp(f32). Coefficients {c1, c3, c5, c7, c9}.", |
| 107 | + "inline constexpr double kSinPolyHighF32[] = " @ |
| 108 | + CArrayT(Float64, [| coeff(vHigh, 1), coeff(vHigh, 3), coeff(vHigh, 5), |
| 109 | + coeff(vHigh, 7), coeff(vHigh, 9) |], 3) @ ";", |
| 110 | + "" |
| 111 | +); |
| 112 | + |
| 113 | +WriteCPPHeader("npsr::trig::data"); |
0 commit comments