Reduce the argument of sin, cos and tan exactly and round once - #1475
Conversation
- The reduction used pi with only the digits of the type and an unsigned quotient, thus results lost digits from x = 1 and became garbage, inf or NaN above about 1e9. - The decimal32 and decimal64 sine polynomials were not accurate, and the kernels were chains of fma, which are slow. - sin and cos of an infinity gave an infinity, but they must give NaN. - The new impl/trig_reduce.hpp reduces with the digits of 2/pi in base-1e9 integer words. One table of 720 words covers the full range of all types. - For |x| < 10^19, a second reduction uses 64-bit words of 2/pi, which is 10 to 30 percent faster. It gives r in fixed point, thus the kernel does not convert it. Its tables have 352 bytes, and the guard words come from the worst cases below 2^64: 27, 58 and 117 leading zero bits. - The new impl/trig_fixed_point.hpp computes the kernels in binary fixed point: one word for decimal32, one or two words for decimal64 and up to three words for decimal128. The result is rounded once in the current rounding mode. - The checks for 0 and for |x| < pi/4 use the integer significand, not decimal compares. tan uses one reduction, and its reciprocal is in the new impl/tan_impl.hpp. - The results are correctly rounded for the 32-bit and 64-bit types, and all three functions are faster for all types. - The new test test_trig_rounding compares with exact values, also for the worst cases below 10^19 and the inputs on each side of 10^19. It walks chains of adjacent inputs in three rounding modes to test that the results do not go against the exact direction. It skips the checks that need constant evaluation or that fast math turns off.
86382f8 to
2ab9315
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #1475 +/- ##
=========================================
- Coverage 98.6% 98.6% -0.0%
=========================================
Files 308 312 +4
Lines 25727 26265 +538
Branches 2205 2264 +59
=========================================
+ Hits 25362 25892 +530
- Misses 365 373 +8
... and 3 files with indirect coverage changes Continue to review full report in Codecov by Harness.
🚀 New features to boost your workflow:
|
|
You have this note in your description: "test_format_fmtlib does not build with the local fmt 12, but this PR does not change it." Our {fmt} support should work from 11 so could you please investigate that when you have a chance? We install a number of different versions in the CI system for testing |
Looks like the |
See #1471.
Summary
On develop,
sin,cosandtanlose digits from x = 1. Above about 1e9 they return values larger than 1, inf or NaN. This PR replaces the argument reduction and the kernels. The results are correctly rounded for the 32-bit and 64-bit types in all five rounding modes. All three functions are 1.8 to 32 times faster, and the code is less than half the size.Problems on develop
k = unsigned(2x / pi_v<T>).pi_v<T>has only the digits of the type, then the error of the remainder grows with k. The cast tounsignedoverflows above x = 6.7e9.fmacalls. One decimal64fmatakes about 137 ns.tancallssinandcos, then it reduces the argument three times. Near odd multiples of pi/2 it divides by zero.sin(±inf)andcos(±inf)return inf. IEEE 754 (9.2.1) and C Annex F require NaN.Examples (decimal64_t unless noted). The correct values come from MPFR at 600 bits.
The tests did not find these problems.
test_sin_cos.cppcompares withfloatstd::sinon [-2pi, 2pi] with an absolute tolerance of 4.8e-6. No test uses an argument above 22, a directed rounding mode, or a reference with more digits than the type.Changes
impl/trig_reduce.hppdoes a Payne-Hanek reduction (an exact reduction with the digits of 2/pi) on integer words.impl/trig_fixed_point.hppcomputes the kernels in binary fixed point. It uses one word for decimal32, one or two words for decimal64 and up to three words for decimal128. The result has one rounding, in the current rounding mode.tandoes one reduction. The newimpl/tan_impl.hpphas its kernel and its reciprocal.sin(±inf)andcos(±inf)return NaN.test_sin_cos.cppandtest_edges_and_behave.cppnow expect NaN.test_trig_rounding.cppcompares with exact values from MPFR. It also has the worst cases below 10^19 and the inputs on each side of 10^19. It walks chains of adjacent inputs in three rounding modes. The results must not go against the direction of the exact function. The test skips the parts that need constant evaluation or that fast math turns off.IEEE 754 (9.2) recommends these functions but does not require them. A version that conforms must be correctly rounded in the current rounding direction. C Annex F makes the rounding direction implementation-defined for
sin,cosandtan. This PR meets the IEEE 754 level for the 32-bit and 64-bit types.Accuracy
The sweep uses 4000 random full-precision inputs per range and per rounding mode, with 10 ranges per function from below epsilon to the largest value. The reference is MPFR.
The 12 decimal128_t cases are near halfway points: 5 in sin on [1, 10), 2 in sin above 1e40, 3 in cos and 2 in tan near k*pi/2. The error is at most 1 ulp in these cases.
Monotonicity: for each function and type, the walk tests 147,900 pairs of adjacent inputs in three rounding modes.
Speed
Google Benchmark, median ns per call, one core, GCC
-O3 -march=native. The fast types give similar numbers.Above 1e20, develop returns wrong values after a short computation. This PR does the full reduction there, then the gain is smaller.
Code size
Bytes that
sin,cosandtanadd to a program (.text and .rodata), GCC.The tables add about 2 to 3 KB of .rodata. The removal of the
fmachains makes the code smaller than this.Tests
All single-file tests in
test/Jamfilepass with GCC in C++17 and-Werror.test_format_fmtlibdoes not build with the local fmt 12, but this PR does not change it. The trig tests also pass with GCC and Clang in C++14, C++17 and C++20. Constant evaluation gives the same values in all compilers.