1- // Suppress rounding mode information messages and NaN warnings for boundary angles
1+ // Suppress rounding mode information messages for irrational table values
22suppressmessage(184, 185, 186); // suppress info no rounding, round-up, round-down
3- suppressmessage(419 ); // expected nan when angle is at specific multiples causing derivative singularities
3+ suppressmessage(160 ); // inequality decided by faithful evaluation
44
55// Generates a 4-element lookup table for fast trigonometric function approximation.
6- //
7- // This procedure creates optimized lookup tables that store:
8- // - Function values split into high/low precision components
9- // - Derivative information with power-of-2 scaling for efficient interpolation
106//
117// The table format per angle is: [deriv, sigma, high, low]
128// where:
13- // deriv = actual_derivative - sigma (reduces storage requirements)
14- // sigma = 2^k, where k = ceil(log2(|actual_derivative|))
15- // high = main part of function value
16- // low = residual for extended precision
9+ // sigma + deriv = f'(angle) sigma = sign * 2^k nearest the derivative;
10+ // being a power of two, sigma*z is exact for any
11+ // small remainder z, confining the interpolation
12+ // rounding error to the tiny deriv*z term
13+ // high + low = f(angle) double-float split of the function value
14+ //
15+ // The values reproduce Intel SVML's AVX-512 high-accuracy sin breakpoint
16+ // tables byte-for-byte (SVML packs the fields in a different, per-precision
17+ // order; this table keeps one order for both precisions). Two conventions
18+ // are per-precision, pinned down by byte comparison against SVML:
19+ // - low rounding: float RN full precision / double RZ 24-bit
20+ // - sigma cut: bump 2^k -> 2^(k+1) when |derivative| > cut * 2^k,
21+ // cut = sqrt(2) for float / 1.5 for double
22+ //
23+ // Two structural tricks keep every rounding and comparison decidable
24+ // (Sollya brackets irrational values but can never prove one is exactly
25+ // zero, nor decide a comparison whose sides are exactly equal):
26+ // - Quadrant reduction: evaluate sin/cos only in [0, pi/2) and select by
27+ // quadrant. Where f or f' is exactly 0/+-1 (angle a multiple of pi/2)
28+ // the reduced angle is the exact rational 0, so sin(0)/cos(0) evaluate
29+ // symbolically and those entries store clean +0.0.
30+ // - Cut nudge: scaling the float cut by (1 - 2^-200) resolves the exact
31+ // tie |derivative| = 2^(-1/2) upward like SVML does; no other entry on
32+ // either grid comes anywhere near a cut point.
1733//
1834// Parameters:
19- // pT - Type descriptor with .kSize, .kDigits, .kRound
20- // pFunc - Function to approximate (sin or cos)
21- // pFuncDriv - Derivative of pFunc (cos or -sin)
22- procedure ApproxLut4_(pT, pFunc, pFuncDriv) {
35+ // pT - Type descriptor with .kSize, .kDigits, .kRound
36+ // pQuad - Quadrant offset of the function to approximate:
37+ // 0 for sin (derivative cos), 1 for cos (derivative -sin),
38+ // using cos(x) = sin(x + pi/2)
39+ procedure ApproxLut4_(pT, pQuad) {
2340 var r, i, $;
2441 // Table size: 512 entries for 64-bit, 256 for 32-bit
25- // More entries for double precision to maintain accuracy
2642 // These sizes balance table memory usage with interpolation accuracy:
2743 // - 256 entries = 1.4° spacing for float (sufficient for 24-bit mantissa)
2844 // - 512 entries = 0.7° spacing for double (needed for 53-bit mantissa)
2945 $.num_lut = match pT.kSize
3046 with 64: (2^9)
3147 default: (2^8);
32-
33- // Low part rounding configuration:
34- // - 64-bit: Round to 24 bits with round-to-zero (faster, sufficient for residual)
35- // - 32-bit: Use full precision with round-to-nearest
36- $.low_round = match pT.kSize
48+
49+ // Low part rounding: full precision RN for float, truncated 24-bit for double
50+ $.low_round = match pT.kSize
3751 with 64: ([|24, RZ|])
3852 default: ([|pT.kDigits, RN|]);
39-
40- // Scale factor to convert table index to angle in radians
41- $.scale = 2.0 * pi / $.num_lut;
42-
53+
54+ // Sigma cut: geometric nearest power of two for float, linear for double,
55+ // nudged below the float tie point (see header)
56+ $.cut = match pT.kSize
57+ with 64: (3 / 2)
58+ default: (sqrt(2));
59+ $.cut = $.cut * (1 - 2^(-200));
60+
4361 r = [||];
4462 for i from 0 to $.num_lut - 1 do {
45- // Sample angle uniformly distributed from 0 to 2π
46- $.angle = i * $.scale;
47-
48- // Compute exact function value
49- $.exact = pFunc($.angle);
50-
51- // Split into high and low parts for extended precision
52- // High part gets the main value rounded to type precision
63+ // Quadrant reduction: angle = quad*pi/2 + reduced with reduced in [0, pi/2)
64+ $.quadrant = floor(4 * i / $.num_lut);
65+ $.reduced = 2 * pi * (i - $.quadrant * $.num_lut / 4) / $.num_lut;
66+ $.sin_red = sin($.reduced);
67+ $.cos_red = cos($.reduced);
68+ $.sin_by_quad = [| $.sin_red, $.cos_red, -$.sin_red, -$.cos_red |];
69+ $.cos_by_quad = [| $.cos_red, -$.sin_red, -$.cos_red, $.sin_red |];
70+
71+ // Shift by the function's quadrant offset:
72+ // f(angle) = sin(angle + pQuad*pi/2), f'(angle) = cos(angle + pQuad*pi/2)
73+ $.quadrant = $.quadrant + pQuad;
74+ if ($.quadrant > 3) then $.quadrant = $.quadrant - 4;
75+
76+ // Exact function value, split into high and low parts:
77+ // value ≈ high + low with high rounded to type precision
78+ $.exact = $.sin_by_quad[$.quadrant];
5379 $.high = pT.kRound($.exact);
54-
55- // Low part stores the residual, rounded to reduced precision
56- // This allows accurate reconstruction: value ≈ high + low
5780 $.low = pT.kRound(round($.exact - $.high, $.low_round[0], $.low_round[1]));
58-
59- // Compute derivative for interpolation
60- $.deriv_exact = pFuncDriv($.angle);
61-
62- // Find power-of-2 scale factor closest to derivative magnitude
63- // This allows efficient storage and reconstruction
64- $.k = ceil(log2(abs($.deriv_exact)));
65- if ($.deriv_exact < 0) then $.k = -$.k;
66-
67- // Sigma is the power-of-2 scale factor
68- $.sigma = 2.0^$.k;
69-
70- // Store derivative minus sigma (typically a small value)
71- // Actual derivative = sigma + stored_deriv
72- $.deriv = pT.kRound($.deriv_exact - $.sigma);
73-
81+
82+ // Exact derivative for interpolation
83+ $.deriv_exact = $.cos_by_quad[$.quadrant];
84+
85+ if ($.deriv_exact == 0) then {
86+ // Derivative term vanishes (angle at pi/2 or 3pi/2 relative to f)
87+ $.sigma = 0;
88+ $.deriv = 0;
89+ } else {
90+ // Power-of-2 scale factor nearest the derivative under the cut rule;
91+ // actual derivative = sigma + stored deriv
92+ $.k = floor(log2(abs($.deriv_exact)));
93+ $.sigma = 2.0^$.k;
94+ if (abs($.deriv_exact) > $.cut * $.sigma) then $.sigma = 2 * $.sigma;
95+ if ($.deriv_exact < 0) then $.sigma = -$.sigma;
96+ $.deriv = pT.kRound($.deriv_exact - $.sigma);
97+ };
98+
7499 r = r @ [|$.deriv, $.sigma, $.high, $.low|];
75100 };
76-
101+
77102 // Format as C array with 4 elements per table entry
78103 return CArrayT(pT, r, 4) @ ";";
79104};
@@ -84,17 +109,17 @@ procedure ApproxLut4_(pT, pFunc, pFuncDriv) {
84109Append(
85110 "template <typename T> inline constexpr char kSinApproxTable[] = {};",
86111 "template <> inline constexpr float kSinApproxTable<float>[] = ",
87- ApproxLut4_(Float32, sin(x), cos(x) ), // sin table with cos derivative
112+ ApproxLut4_(Float32, 0 ), // sin table with cos derivative
88113 "",
89114 "template <> inline constexpr double kSinApproxTable<double>[] = ",
90- ApproxLut4_(Float64, sin(x), cos(x) ),
115+ ApproxLut4_(Float64, 0 ),
91116 "",
92117 "template <typename T> inline constexpr char kCosApproxTable[] = {};",
93118 "template <> inline constexpr float kCosApproxTable<float>[] = ",
94- ApproxLut4_(Float32, cos(x ), -sin(x)), // cos table with -sin derivative
119+ ApproxLut4_(Float32, 1 ), // cos table with -sin derivative
95120 "",
96121 "template <> inline constexpr double kCosApproxTable<double>[] = ",
97- ApproxLut4_(Float64, cos(x), -sin(x) ),
122+ ApproxLut4_(Float64, 1 ),
98123 ""
99124);
100125
0 commit comments