Repository navigation
Conversation
Fix module rot and match other boost libraries implementations
Add new `cohort_preserving_fixed` charconv mode and fix non-rejection in `cohort_preserving_scientific`
Helpful doc updates
Fix the accuracy and monotonicity of asin, acos, atan and atan2
Reduce tols in sin/cos/tan tests
This PR follows the review of #1479. `test_cos()` in `test/test_sin_cos.cpp` now uses a tolerance of 16 float eps, the same value that #1479 uses for `test_sin()`. The old value was 35 eps. Over 200000 random points in [-2π, 2π], the largest cos error for all decimal types is 5 eps. `test_asin_edge()` in `test/test_asin.cpp` now seeds its generator with a fixed value. The old code seeded it from the clock, so a failure was not reproducible. This change also removes the unused `time_point()` helper, `std::random_device`, and `<chrono>`. This PR does not change `test/test_tan.cpp` because #1479 already changes the seed there. Both tests pass with GCC and Clang.
- The `decimal128_t` multiply has a separate path for an exponent sum below `-6142`. It does not expand the significands, so the product can have 1 to 68 digits. - The path rounded the product only above 38 digits, the `digits10` of the 128-bit significand type. A product of 35 to 38 digits then went to `pack_in_range` unrounded. The encoder wrote its extra digits into bits outside the significand field. - The limit is now `detail::precision_v<ReturnType>`, 34 digits for `decimal128_t`. - The new test `github_issue_1482.cpp` multiplies products of 35 to 38 digits in each rounding mode. Before the change, each check fails except one product that rounds to zero. A sweep of 20000 such products gives 962 wrong results in each of the five rounding modes on `develop`, and none after the change. The change needs #1477, which is in `develop`. Issue #1482 gives the values, the sweep, the comparison with `develop` and the benchmark. Fixes #1482
…r significand (#1486) - ceil and round gave +0 for a negative x that rounds to zero, e.g. ceil(-0.5), round(-0.4) - trunc and modf gave +0 too, because they use ceil - In the IEEE types, the result kept a negative exponent: ceil(1.5) was 2.0, and floor(2.0) was 2.0, not 2 - rint and nearbyint gave +-0 for each |x| < 0.1, which is wrong in the upward and the downward modes, e.g. rint(1e-30) was 0 in the upward mode - lrint and llrint gave 0 for each |x| < 0.1, with the same defect - lrint and llrint of +-9.223372036854776e18 (LLONG_MAX and LLONG_MIN rounded to 16 digits) wrapped to the other sign in decimal64_t and decimal_fast64_t - round used modf, a subtraction and a compare, and was 2.6-4.9x slower than floor - Now one division of the significand by 10^-e gives the integer and the remainder - The rounding mode picks the increment: a constant for ceil, floor, round, roundeven and trunc, and the current mode for rint, nearbyint, lrint and llrint - pack_in_range packs the result, which has the exponent max(q(x), 0) and the sign of x - lrint and llrint check the range on the integer significand, not with decimal compares - Add tests for -0 results, the quantum, rint, lrint and llrint in each mode, and the limits of llrint - The -0 tests in test_cmath.cpp made +0, so they now make -0 and read the sign Fixes #1485
frexp now gives the correctly rounded result for all finite inputs in the current rounding mode, and it is 16 to 39 times faster. The old frexp_impl has four defects: - The result is not correctly rounded. frexp_impl multiplies by 2^-t from pow_2_impl and then divides by powers of 2. Each step rounds, so the error is up to 852 ulp. - The loop does not stop below 1e-91 (decimal32), 1e-369 (decimal64) and 1e-6111 (decimal128). The fast types return 0. - A subnormal input gives 0. - -0 gives +0. <details> <summary>The new frexp_impl</summary> - Writes v * 10^n as s * 5^K * 2^K, and gets s * 5^K as a binary value of 64, 128 or 256 bits. The value comes from a table of 5^(+-d * 16^j), so that |K| needs at most four products. - Gets the digits with one shift. fenv_round rounds them once in the current rounding mode, so that the constructor gets p digits. - Uses n = p + 1 digits for decimal32 and decimal64. decimal128 uses n = p digits. fenv_round then gets the parity of the last digit and a digit for the bits below, so that it does not divide 128-bit digits by 10. - First computes s * 5^K in a word of 64 or 128 bits for decimal64 and decimal128. This is half of the width that an exact result can need. After nm products, that M is below the exact value by less than 6 * nm + 1 units. If the bits below the p digits are not that close to 0, to one half or to a carry, they give the result. Else the wide pass runs. - Knows that M is exact if s and the table entries fit in the word together. - Gets the products of decimal128 from umul256_hi and umul512_hi. For K > 0, the first narrow product of decimal128 uses 64 bits of the entry, because 5^1 to 5^15 are exact in 64 bits. A static_assert stops a radix with a digit above 27. - Removes the factors of 5 only when K >= -1.5 * p, because for a smaller K no result is exact. A product with the inverse of 5 modulo 2^w removes each of them without a division. - Finds NaN, infinity and zero without fpclassify, which counts the digits. A zero is returned as it is, so that it keeps its sign. - Makes the result with pack_in_range, because the result has p digits and an exponent in range. pack_in_range does not do the checks of the constructor. - Ends all finite paths in one pack_in_range. With one return for each path, GCC merges the results of the fast types with a memory copy, and that copy stalls on the stores of the single members. - Cuts the table entries to the word width at compile time, so that a program keeps only the widths that it uses. decimal32 keeps a table of 736 bytes and decimal64 a table of 1472 bytes instead of 2944 bytes. </details> <details> <summary>Other changes</summary> - countl_zero gives the correct count for types of fewer than 64 bits on compilers without __builtin_clz, for example MSVC. Before, the fallback counted the leading zeros of a 64-bit value, so frexp of decimal32 gave 0. - frexp computes in the type of the value, also when BOOST_DECIMAL_DEC_EVAL_METHOD is 1 or 2. Before, frexp computed in the wider evaluation type, and the cast back to the type of the value can round the fraction to 1. For example, frexp(9223372e12_DF) gave 1 * 2^63 and not 0.5 * 2^64. - A first commit adds umul256_hi, the high 128 bits of the product of two uint128_t, and computes umul512_hi with a schoolbook product of 64-bit limbs. The old umul512_hi used four umul256 products, with branches and carry compares. The value is the same. frexp uses both functions. </details> <details> <summary>Results</summary> - Accuracy: a GMP reference computes the correctly rounded result in the five rounding modes. The inputs are random values over the full range, powers of 2 and their neighbors, subnormals, short significands and significands with factors of 5. Seeds 7 and 11 give 1104710 results each. All results are correctly rounded, in the range [0.5, 1), and have the correct exponent. - Proof for the wide pass: its M is below the exact value by less than 6 * nm units. A search over all K and all s finds the exact values nearest to a rounding boundary. The nearest one is 38.7 units away for decimal32 (bound 13), 703.6 units for decimal64 (bound 19) and 169830 units for decimal128 (bound 25). A brute-force scan gives the same cases for all s of decimal32 and for s < 10^9 of decimal64. - Speed (GCC 15, -O3, |v| in [1e-20, 1e20], ns for each call, develop to this PR): decimal32 176.9 to 6.8, decimal64 272.0 to 9.8, decimal128 736.0 to 18.7, decimal_fast32 130.4 to 8.0, decimal_fast64 323.1 to 16.1, decimal_fast128 693.4 to 27.1. This is 16 to 39 times faster. - Speed of umul512_hi (GCC 15, -O3, ns for each call): divmod_pow10_u256, which rounds decimal128 results, goes from 74 to 78 ns to 63 to 64 ns. Multiplication, division and fma of decimal128 and decimal_fast128 do not change more than the noise. - Code size (GCC, -O2, .text of one frexp call): decimal32 10449 to 986 bytes, decimal64 15692 to 2243 bytes, decimal128 33508 to 3484 bytes. </details> Fixes #1487
Bumps the all-dependencies group in /doc with 3 updates: [@antora/cli](https://gitlab.com/antora/antora/tree/HEAD/packages/cli), [@antora/site-generator](https://gitlab.com/antora/antora/tree/HEAD/packages/site-generator) and [antora](https://gitlab.com/antora/antora/tree/HEAD/packages/antora). Updates `@antora/cli` from 3.2.0 to 3.2.1 <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://gitlab.com/antora/antora/blob/main/CHANGELOG.adoc">@antora/cli's changelog</a>.</em></p> <blockquote> <p>== 3.2.1 (2026-10-01)</p> <p>=== Changed</p> <ul> <li><em>logger</em>: Handle non-git origin when url is defined; print url as source</li> </ul> <p>=== Fixed</p> <ul> <li><em>logger</em>: Ignore unknown origin type (without url) when reshaping file for log (<a href="https://gitlab.com/antora/antora/tree/HEAD/packages/cli/-/work_items/1247">#1247</a>)</li> </ul> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://gitlab.com/antora/antora/commit/fcc5d507ecf5944a0ea3fc8048160cf4c26da7f7"><code>fcc5d50</code></a> release 3.2.1</li> <li><a href="https://gitlab.com/antora/antora/commit/2023710b5bf360500ba21d6aee92c77d3f34d8ff"><code>2023710</code></a> refresh dependency lock file</li> <li><a href="https://gitlab.com/antora/antora/commit/0d19349325164b2063825666c37404996fe91af5"><code>0d19349</code></a> handle non-git origin when url is defined; print url as source</li> <li><a href="https://gitlab.com/antora/antora/commit/de9467011b08cd9e5d53054d7be2dec52899cd8c"><code>de94670</code></a> add test to verify TERM variable is checked for color support in pretty log i...</li> <li><a href="https://gitlab.com/antora/antora/commit/5afad380eb084c5bb74949777efa272808d899dc"><code>5afad38</code></a> merge !1154</li> <li><a href="https://gitlab.com/antora/antora/commit/21ccb5f6a0b90d977b215a2e2c70b3032a835511"><code>21ccb5f</code></a> resolves <a href="https://gitlab.com/antora/antora/tree/HEAD/packages/cli/-/work_items/1247">#1247</a> ignore unknown origin type when reshaping file for log</li> <li><a href="https://gitlab.com/antora/antora/commit/29125a573d87e22ae03a37150eed9beb375a821c"><code>29125a5</code></a> ensure optional deps are enabled when installing biome [skip ci]</li> <li><a href="https://gitlab.com/antora/antora/commit/06cddb459a94e861a26c5237514e30d372e532b6"><code>06cddb4</code></a> npm fix doesn't allow blank lines in package.json</li> <li><a href="https://gitlab.com/antora/antora/commit/334ff565a1a4c2eb34ba4da644a4acb01300f97c"><code>334ff56</code></a> separate tasks in package file to make it easier to see pre/post tasks [skip ci]</li> <li><a href="https://gitlab.com/antora/antora/commit/9e31aea346b50b15178845fe749a5343e7514720"><code>9e31aea</code></a> slightly optimize heredoc function in test harness</li> <li>Additional commits viewable in <a href="https://gitlab.com/antora/antora/compare/v3.2.0...v3.2.1">compare view</a></li> </ul> </details> <br /> Updates `@antora/site-generator` from 3.2.0 to 3.2.1 <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://gitlab.com/antora/antora/blob/main/CHANGELOG.adoc">@antora/site-generator's changelog</a>.</em></p> <blockquote> <p>== 3.2.1 (2026-10-01)</p> <p>=== Changed</p> <ul> <li><em>logger</em>: Handle non-git origin when url is defined; print url as source</li> </ul> <p>=== Fixed</p> <ul> <li><em>logger</em>: Ignore unknown origin type (without url) when reshaping file for log (<a href="https://gitlab.com/antora/antora/tree/HEAD/packages/site-generator/-/work_items/1247">#1247</a>)</li> </ul> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://gitlab.com/antora/antora/commit/fcc5d507ecf5944a0ea3fc8048160cf4c26da7f7"><code>fcc5d50</code></a> release 3.2.1</li> <li><a href="https://gitlab.com/antora/antora/commit/2023710b5bf360500ba21d6aee92c77d3f34d8ff"><code>2023710</code></a> refresh dependency lock file</li> <li><a href="https://gitlab.com/antora/antora/commit/0d19349325164b2063825666c37404996fe91af5"><code>0d19349</code></a> handle non-git origin when url is defined; print url as source</li> <li><a href="https://gitlab.com/antora/antora/commit/de9467011b08cd9e5d53054d7be2dec52899cd8c"><code>de94670</code></a> add test to verify TERM variable is checked for color support in pretty log i...</li> <li><a href="https://gitlab.com/antora/antora/commit/5afad380eb084c5bb74949777efa272808d899dc"><code>5afad38</code></a> merge !1154</li> <li><a href="https://gitlab.com/antora/antora/commit/21ccb5f6a0b90d977b215a2e2c70b3032a835511"><code>21ccb5f</code></a> resolves <a href="https://gitlab.com/antora/antora/tree/HEAD/packages/site-generator/-/work_items/1247">#1247</a> ignore unknown origin type when reshaping file for log</li> <li><a href="https://gitlab.com/antora/antora/commit/29125a573d87e22ae03a37150eed9beb375a821c"><code>29125a5</code></a> ensure optional deps are enabled when installing biome [skip ci]</li> <li><a href="https://gitlab.com/antora/antora/commit/06cddb459a94e861a26c5237514e30d372e532b6"><code>06cddb4</code></a> npm fix doesn't allow blank lines in package.json</li> <li><a href="https://gitlab.com/antora/antora/commit/334ff565a1a4c2eb34ba4da644a4acb01300f97c"><code>334ff56</code></a> separate tasks in package file to make it easier to see pre/post tasks [skip ci]</li> <li><a href="https://gitlab.com/antora/antora/commit/9e31aea346b50b15178845fe749a5343e7514720"><code>9e31aea</code></a> slightly optimize heredoc function in test harness</li> <li>Additional commits viewable in <a href="https://gitlab.com/antora/antora/compare/v3.2.0...v3.2.1">compare view</a></li> </ul> </details> <br /> Updates `antora` from 3.2.0 to 3.2.1 <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://gitlab.com/antora/antora/blob/main/CHANGELOG.adoc">antora's changelog</a>.</em></p> <blockquote> <p>== 3.2.1 (2026-10-01)</p> <p>=== Changed</p> <ul> <li><em>logger</em>: Handle non-git origin when url is defined; print url as source</li> </ul> <p>=== Fixed</p> <ul> <li><em>logger</em>: Ignore unknown origin type (without url) when reshaping file for log (<a href="https://gitlab.com/antora/antora/tree/HEAD/packages/antora/-/work_items/1247">#1247</a>)</li> </ul> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://gitlab.com/antora/antora/commit/fcc5d507ecf5944a0ea3fc8048160cf4c26da7f7"><code>fcc5d50</code></a> release 3.2.1</li> <li><a href="https://gitlab.com/antora/antora/commit/2023710b5bf360500ba21d6aee92c77d3f34d8ff"><code>2023710</code></a> refresh dependency lock file</li> <li><a href="https://gitlab.com/antora/antora/commit/0d19349325164b2063825666c37404996fe91af5"><code>0d19349</code></a> handle non-git origin when url is defined; print url as source</li> <li><a href="https://gitlab.com/antora/antora/commit/de9467011b08cd9e5d53054d7be2dec52899cd8c"><code>de94670</code></a> add test to verify TERM variable is checked for color support in pretty log i...</li> <li><a href="https://gitlab.com/antora/antora/commit/5afad380eb084c5bb74949777efa272808d899dc"><code>5afad38</code></a> merge !1154</li> <li><a href="https://gitlab.com/antora/antora/commit/21ccb5f6a0b90d977b215a2e2c70b3032a835511"><code>21ccb5f</code></a> resolves <a href="https://gitlab.com/antora/antora/tree/HEAD/packages/antora/-/work_items/1247">#1247</a> ignore unknown origin type when reshaping file for log</li> <li><a href="https://gitlab.com/antora/antora/commit/29125a573d87e22ae03a37150eed9beb375a821c"><code>29125a5</code></a> ensure optional deps are enabled when installing biome [skip ci]</li> <li><a href="https://gitlab.com/antora/antora/commit/06cddb459a94e861a26c5237514e30d372e532b6"><code>06cddb4</code></a> npm fix doesn't allow blank lines in package.json</li> <li><a href="https://gitlab.com/antora/antora/commit/334ff565a1a4c2eb34ba4da644a4acb01300f97c"><code>334ff56</code></a> separate tasks in package file to make it easier to see pre/post tasks [skip ci]</li> <li><a href="https://gitlab.com/antora/antora/commit/9e31aea346b50b15178845fe749a5343e7514720"><code>9e31aea</code></a> slightly optimize heredoc function in test harness</li> <li>Additional commits viewable in <a href="https://gitlab.com/antora/antora/compare/v3.2.0...v3.2.1">compare view</a></li> </ul> </details> <br /> Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore <dependency name> major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore <dependency name> minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore <dependency name>` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore <dependency name>` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore <dependency name> <ignore condition>` will remove the ignore condition of the specified dependency and ignore conditions </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
+ Dragonbox's simple variant replaces the generic 128-bit Ryu for float, double and 64-bit long double + about 17 times faster for `double` and 12 times for `float` + fixes the last digit Ryu got wrong for some values, such as 2.8380164753330115e19 and 6.2946755e10f + 80-bit and 128-bit long doubles still go through Ryu, which Dragonbox does not cover uses code from https://github.com/jk-jeon/dragonbox
ldexp_impl uses the frexp machinery to write v * 2^e as s * 5^k5 * 2^(k5 + e). It gets the digits from the table of powers of 5 and rounds them once with fenv_round in the current rounding mode. The old code multiplied v by a rounded 2^e, so it rounded twice and gave infinity or zero when 2^e was out of range but v * 2^e was not. An estimate of log10(2) * (log2(s) + e) gives the number of digits before the decimal point, or one less. If the result has one more digit, ldexp drops the last digit before it rounds. decimal64 and decimal128 first try a word of half the width, as frexp does. For an IEEE type, a short significand and a small e give an exact result with one shift or one multiplication by a power of 5. frexp_impl and ldexp_impl share the helpers that remove factors of 5, multiply by a power of 5 and round the narrow word. The frexp table has two more rows, so that |k5| <= 16383, and a table of 5^0 to 5^27. ldexp keeps the sign of an infinity and computes in the type of the value, also when BOOST_DECIMAL_DEC_EVAL_METHOD is 1 or 2. Add tests for exact results, ties in both modes to nearest, subnormal results, overflow and underflow, and run them with each evaluation method. Fixes #1497
- The old `cbrt` used a Pade start and Newton steps in decimal arithmetic, and it multiplied by the rounded `cbrt10_v` for the exponent. Each step rounded, so many results were not correctly rounded, also for exact cubes and powers of ten. - The new `cbrt` finds the root in integer arithmetic. A binary fixed-point estimate puts the root within half a unit, and the sign of 8N - z^3 for the one boundary z near the estimate gives the result in each mode. - For decimal32_t, the Newton step uses 64-bit products of 32-bit words. For decimal64_t, the first step uses them, and the last step uses them for its small term. Only decimal128_t uses 128-bit products throughout. - For decimal128_t, when the boundary is more than 1/16 from the estimate, the estimate gives the result, and the 256-bit test does not run. - The code has no jumps on random values: a bias keeps the Newton error positive, the result comes from sums of the flags, and 10^(e mod 3) comes from a table. The scale comes from the bit length of the significand, so it does not wait for the multiply. - `u256` gets `operator-` with the same three paths as `operator+`, so that the 256-bit test of decimal128_t uses `u256`, `umul256` and the `u256` addition. - `test_cbrt_edge` now expects cbrt(10^n) rounded once. - The new test `test_cbrt_rounding.cpp` checks hard cases in all five modes for each type, and random decimal32_t values against exact bounds. - `test_big_uints.cpp` checks the borrow and the wrap of `operator-`, also in constant evaluation. Fixes #1500
+ it rounds the exact binary value to the decimal type, IEEE 754
convertFormat (5.4.2), like Intel's binary64_to_bid64
+ the constructors keep the shortest digits instead, so 0.07 gives 0.07
there and 0.07000000000000001 here
+ about as fast as Intel's conversion, and bit for bit the same for
every float and in every rounding mode
+ one name for float and double, IEEE 754's binary32 and binary64,
matching from_bid and from_dpd
```c++
#include <boost/decimal.hpp>
#include <iomanip>
#include <iostream>
int main()
{
using namespace boost::decimal;
std::cout << std::setprecision(16)
<< 99.99 << '\n' // 99.98999999999999
<< decimal64_t{99.99} << '\n' // 99.99
<< from_binary<decimal64_t>(99.99) << '\n'; // 99.98999999999999
}
```
This code shows how it can be used. The double `99.99` really holds
`99.98999999999999488...` `from_binary<decimal64_t>()` rounds that exact
value to decimal64's 16 digits, as IEEE 754 convertFormat specifies, and
gives the same result as Intel's `binary64_to_bid64`. The constructor
gives the shortest decimal that reads back as the same double.
`from_binary<decimal64_t>` is roughly 3x faster than the ctor, and about
as fast as `binary64_to_bid64` and produces identical results.
lround rounded the value with round(), and compared the result with the limits of long converted to the decimal type. In decimal64_t, LLONG_MAX and LLONG_MIN round away from zero, to 9223372036854776e3 and -9223372036854776e3. These values passed the compare, and the conversion to long overflowed to a value with the other sign. The compares and the conversion also made lround slow. lrint and llrint already round the integer significand and check the range on the integer. This code moves from rint.hpp to impl/round_integral.hpp as round_integral_int_impl, which takes the rounding mode like round_integral_impl. lrint and llrint give the current mode, and lround and llround give fe_dec_to_nearest_from_zero. Fixes #1504
…t is zero (#1508) `from_chars` and `strtod` parse `".05e0"` as `0.5` (instead of `0.05`). With an exponent of zero (e0, e+0, e-00), the parser returns early. That early return leaves out the zeros it skipped right after the decimal point, which every nonzero exponent adds back (exponent += leading_zero_powers). The early return now adds them too. For the same reason, "0.0e0" came out as 0e0 instead of 0e-1. ```c++ #include <boost/decimal.hpp> #include <cstdio> #include <cstring> int main() { using namespace boost::decimal; const char* inputs[] = {".05e0", "0.05e0", "0.05e+0", "0.05e1", "0.05"}; for (const char* input : inputs) { decimal64_t value; from_chars(input, input + std::strlen(input), value); char buffer[64]; *to_chars(buffer, buffer + sizeof(buffer) - 1, value, chars_format::scientific).ptr = '\0'; std::printf("%-7s -> %s\n", input, buffer); } } ``` Before: > .05e0 -> 5e-01 > 0.05e0 -> 5e-01 > 0.05e+0 -> 5e-01 > 0.05e1 -> 5e-01 > 0.05 -> 5e-02 After: > .05e0 -> 5e-02 > 0.05e0 -> 5e-02 > 0.05e+0 -> 5e-02 > 0.05e1 -> 5e-01 > 0.05 -> 5e-02
…rounding (#1509) `boost::decimal::from_chars` and `strtod32`/`strtod64`/`strtod128` (and so the `wcstod` versions) keep only the first 19 significant digits, or 38 for decimal128; later digits only shift the exponent. Usually that's harmless, because 19 digits are more than the 7 or 16 a type keeps. But when the kept digits form an exact tie between two neighbors, the dropped digits are what decide the rounding. Without them, a value just above the tie rounds to even, that is down, instead of up. At the bottom of the range, this sends a value above half the smallest subnormal to zero. The parser now notes whether any dropped digit is nonzero, and if so makes the last kept digit at least 1. That digit sits below the rounding position of every type (digit 19 of 19 against 7 or 16 kept, digit 38 of 38 against 34), so it changes nothing except telling the rounding that the value is above the tie. This works in every rounding mode. The block in the exponent branch that was meant to round here never runs: offset > significand_buffer_size can't be true, because the copy loops stop at the buffer size. Even if it ran, it would round half up from a single digit. It's removed. ```c++ #include <boost/decimal.hpp> #include <cstdio> #include <cstring> template <typename T> void show(const char* input) { T value; boost::decimal::from_chars(input, input + std::strlen(input), value); char buffer[64]; *boost::decimal::to_chars(buffer, buffer + sizeof(buffer) - 1, value).ptr = '\0'; std::printf("%-34s -> %s\n", input, buffer); } int main() { using namespace boost::decimal; // Each input is just above a tie between two neighbors, so it must round up show<decimal32_t>("1.00000050000000000000000001"); show<decimal32_t>(".50000000000000000000000001e-101"); show<decimal64_t>("1.0000000000000005000000001"); } ``` Before: > 1.00000050000000000000000001 -> 1 > .50000000000000000000000001e-101 -> 0 > 1.0000000000000005000000001 -> 1 After: > 1.00000050000000000000000001 -> 1.000001 > .50000000000000000000000001e-101 -> 1e-101 > 1.0000000000000005000000001 -> 1.000000000000001
… infinity (#1510) Converting a decimal to float, double or long double gives 0 for -0 and inf for -inf. to_float returns unsigned constants for zero and infinity before it looks at the sign. They now keep it. The roundtrip tests asserted the old result for -inf; they now expect -inf. A new check in test_float_conversion.cpp covers both zeros and both infinities, for all six decimal types and all three binary types. ```c++ #include <boost/decimal.hpp> #include <cstdio> #include <limits> int main() { using namespace boost::decimal; const decimal64_t negative_zero {-decimal64_t{0}}; const decimal64_t negative_inf {-std::numeric_limits<decimal64_t>::infinity()}; std::printf("double(-0) = %g\n", static_cast<double>(negative_zero)); std::printf("double(-inf) = %g\n", static_cast<double>(negative_inf)); std::printf("float(-inf) = %g\n", static_cast<double>(static_cast<float>(negative_inf))); } ``` Before: > double(-0) = 0 > double(-inf) = inf > float(-inf) = inf After: > double(-0) = -0 > double(-inf) = -inf > float(-inf) = -inf
operator> returned true and operator<= returned false for two positive infinities. Compare the right operand when the left operand is infinite. Add the same infinity tests for all types. Each type decodes an infinity as a value above its largest finite value, so the less functions order an infinity and a finite value without help. Two infinities need a test, because the payload of an infinity has no meaning, and the types decode an infinity as different values. Test for NaN and for two infinities only when an operand is not finite, and compare two infinities by sign. Write the decimal32_t operators as the decimal64_t and decimal128_t operators. Fixed #1512
This PR has 11 commits. Each commit builds, and the tests pass after each commit. 1. Expand local copies of the product operands: `mul_impl_dispatch` takes the components by reference and expands local copies. Otherwise MSVC 14.2 in C++14 mode multiplied the `lhs` significand from before `expand_significand`. 2. Count the digits of an integer without a branch: `num_digits` gets the number of digits from the bit length and one compare with the table of powers of ten. This applies to the 32-bit, 64-bit, `uint128_t` and `u256` versions. 3. Multiply two `uint128_t` values in fewer steps: `umul256` always calculates the four 64-bit products. `divmod_pow10_uint128` gets the high 128 bits of the product with the reciprocal from `umul256_hi`, in place of the full `umul256` product. 4. Pack fast type results directly with scalar padding: scalar members replace the `char` padding arrays of the fast types. The size and the alignment of each type do not change. New `pack_in_range` overloads for the fast types call `direct_init` for values in range. 5. Add fast type operands with exponents 0 to 3 apart in the aligned kernel, as the IEEE types do. 6. Round quotients and products in the current mode: the division finalizers and the multiplication finalizers round in the current mode. In the directed modes, all division and multiplication paths call the finalizers through out-of-line wrappers, and the constructor no longer rounds the result again. The new test `test_mul_div_rounding` has products and quotients of the six types in the five rounding modes, and products with an integer. 7. Add in the directed modes with the aligned kernel: `aligned_add_kernel` takes the rounding mode, and the additions with exponents 0 to 3 apart use it in each mode. This includes `decimal_fast32_t`. The new test `test_add_rounding` has sums in each rounding mode. 8. Add operands with exponents 4 or more apart with a band kernel: `band_add_kernel` divides the operand with the smaller exponent by `10^d` one time and rounds the sum from the rest. If the sum gains or loses a digit, the kernel keeps three guard digits and a sticky bit. 9. Divide to exactly p quotient digits: each division driver scales the dividend by `10^(p-1)` when `lhs.sig >= rhs.sig`, and by `10^p` in the other cases. Thus the quotient always has `p` digits, and the finalizers no longer divide a quotient of `p + 1` digits by 10. 10. Divide a decimal32 product by a constant power of ten: `mul_finalize_u64` divides the product by the constant `10^7` or `10^6`, in place of the variable `pow10(extra)`. The compiler changes a division by a constant into a multiplication. 11. Round without a branch: the round-half-to-even test `r > half || (r == half && q odd)` becomes `r + (q & 1) > half`, which is the same test for integers. The finalizers and the two addition kernels add the round-up bit to the quotient, in place of an increment under a branch. Fixes #1506 <details> <summary>Correctness</summary> - I compared the bits of `+`, `-`, `*` and `/` for 400000 pairs of each type in the five rounding modes with `develop`. No value is different. - The only differences are cohorts of exact sums of `decimal32_t`, `decimal64_t` and `decimal128_t` in the directed modes. `develop` expands short operands to full precision in these modes, so an exact sum gets the smallest exponent. With the fix, an exact sum keeps the smaller exponent of the two operands, as IEEE 754 prefers and as the default mode already does. - I compared 6000000 sums with exponents 4 to `p + 6` apart with Python `decimal`, in the five rounding modes and the six types. All sums are correct. - The new tests `test_add_rounding` and `test_mul_div_rounding` use results from Python `decimal`. They fail on mutants of the new rounding code that the existing tests do not find. - I built and ran each file in `test/` alone with GCC 16.2 and Clang 23.1, in C++14 and C++20, with `-Wall -Wextra -Werror`. 175 of 205 files pass. The other 30 fail in the same way on `develop`, because they are compile-fail tests, link tests with more than one file, benchmarks, or tests that need extra flags. The arithmetic tests (`random_*_math`, `test_add_rounding`, `test_mul_div_rounding`, `test_upward_rounding`, `test_fenv`) also pass with `-m32`. </details> <details> <summary>Speed</summary> Each set has 16384 pairs. `stock` has random values. `full` has significands with `p` digits and exponents 0 to 3 apart. `sameexp` has equal exponents. `band` has exponents 4 to `p + 3` apart. `short` has significands with few digits. The signs are random. The table gives ns for each operation. I used Google Benchmark with `-O3` on an AMD Ryzen 9 3900X, with GCC 16.2 and Clang 23.1. Each value is the median of 5 rounds with 3 repetitions each. | case | GCC develop | GCC fix | Clang develop | Clang fix | |---|---|---|---|---| | `add/decimal32_t/band/near` | 26.1 | 18.4 | 27.0 | 22.0 | | `add/decimal32_t/short/up` | 41.4 | 9.5 | 45.4 | 10.9 | | `add/decimal_fast32_t/full/near` | 25.1 | 17.2 | 24.9 | 15.6 | | `add/decimal64_t/band/near` | 43.5 | 17.1 | 45.0 | 22.1 | | `add/decimal64_t/short/up` | 46.2 | 9.9 | 47.9 | 11.9 | | `add/decimal_fast64_t/full/near` | 27.0 | 18.2 | 28.6 | 15.2 | | `mul/decimal_fast64_t/full/near` | 23.4 | 11.8 | 25.4 | 13.2 | | `div/decimal64_t/full/up` | 58.9 | 16.1 | 53.9 | 18.8 | | `add/decimal128_t/band/near` | 132.2 | 36.4 | 124.2 | 32.9 | | `add/decimal128_t/full/up` | 81.4 | 30.8 | 67.2 | 31.9 | | `add/decimal128_t/short/up` | 88.6 | 11.7 | 78.5 | 14.9 | | `add/decimal_fast128_t/short/near` | 48.6 | 31.8 | 45.8 | 32.0 | | `mul/decimal128_t/full/near` | 34.5 | 29.5 | 36.8 | 22.2 | | `mul/decimal128_t/full/up` | 109.4 | 30.2 | 89.2 | 26.0 | | `div/decimal128_t/full/near` | 64.2 | 56.7 | 67.1 | 54.8 | | `div/decimal128_t/full/up` | 94.9 | 59.4 | 95.9 | 54.9 | Over the 216 cases, the geometric mean of the time changes by -46% with GCC and by -44% with Clang. These cases become slower by more than 3%: - GCC, `decimal128_t` `add` of `short` operands in the default mode: +7% (0.8 ns). - GCC, `decimal128_t` `mul` of `short` operands: +8% to +10% (0.5 to 0.6 ns). - Clang, `decimal_fast128_t` `add` of `stock` values: +13% (0.5 ns). The full table follows. </details> <details> <summary>All cases</summary> | case | GCC develop | GCC fix | change | Clang develop | Clang fix | change | |---|---|---|---|---|---|---| | `add/decimal32_t/stock/near` | 7.1 | 6.4 | -10% | 9.1 | 8.8 | -3% | | `add/decimal_fast32_t/stock/near` | 8.9 | 7.3 | -18% | 5.5 | 5.0 | -9% | | `sub/decimal32_t/stock/near` | 9.4 | 8.4 | -11% | 9.2 | 9.1 | -1% | | `sub/decimal_fast32_t/stock/near` | 9.0 | 7.9 | -12% | 7.9 | 7.9 | +0% | | `mul/decimal32_t/stock/near` | 19.0 | 14.6 | -23% | 24.2 | 17.2 | -29% | | `mul/decimal_fast32_t/stock/near` | 14.8 | 8.0 | -46% | 17.1 | 11.5 | -33% | | `div/decimal32_t/stock/near` | 25.7 | 17.7 | -31% | 30.4 | 19.9 | -35% | | `div/decimal_fast32_t/stock/near` | 17.0 | 6.6 | -61% | 19.0 | 10.9 | -43% | | `add/decimal32_t/full/near` | 23.5 | 17.9 | -24% | 22.8 | 18.7 | -18% | | `add/decimal_fast32_t/full/near` | 25.1 | 17.2 | -31% | 24.9 | 15.6 | -37% | | `sub/decimal32_t/full/near` | 23.4 | 18.4 | -21% | 23.1 | 19.5 | -16% | | `sub/decimal_fast32_t/full/near` | 25.2 | 17.9 | -29% | 25.7 | 15.8 | -39% | | `mul/decimal32_t/full/near` | 12.0 | 8.6 | -28% | 16.0 | 8.7 | -46% | | `mul/decimal_fast32_t/full/near` | 12.5 | 4.0 | -68% | 16.0 | 6.9 | -57% | | `div/decimal32_t/full/near` | 18.6 | 11.7 | -37% | 25.8 | 13.4 | -48% | | `div/decimal_fast32_t/full/near` | 16.6 | 5.9 | -64% | 18.2 | 8.6 | -53% | | `add/decimal32_t/full/up` | 29.9 | 20.0 | -33% | 33.1 | 20.3 | -39% | | `add/decimal_fast32_t/full/up` | 22.8 | 17.7 | -22% | 24.7 | 17.4 | -30% | | `sub/decimal32_t/full/up` | 30.3 | 20.0 | -34% | 32.9 | 20.5 | -38% | | `sub/decimal_fast32_t/full/up` | 23.3 | 18.7 | -20% | 25.5 | 17.7 | -31% | | `mul/decimal32_t/full/up` | 20.6 | 9.4 | -54% | 22.7 | 10.1 | -56% | | `mul/decimal_fast32_t/full/up` | 14.7 | 4.7 | -68% | 18.6 | 8.8 | -53% | | `div/decimal32_t/full/up` | 30.2 | 12.1 | -60% | 38.7 | 15.0 | -61% | | `div/decimal_fast32_t/full/up` | 23.7 | 6.0 | -75% | 26.5 | 9.9 | -63% | | `add/decimal32_t/sameexp/near` | 14.0 | 11.7 | -16% | 13.1 | 11.5 | -12% | | `add/decimal_fast32_t/sameexp/near` | 17.4 | 12.1 | -30% | 17.2 | 11.6 | -33% | | `sub/decimal32_t/sameexp/near` | 14.2 | 11.5 | -19% | 13.4 | 12.0 | -10% | | `sub/decimal_fast32_t/sameexp/near` | 17.6 | 13.2 | -25% | 17.8 | 11.7 | -34% | | `mul/decimal32_t/sameexp/near` | 11.8 | 8.6 | -27% | 15.7 | 8.6 | -45% | | `mul/decimal_fast32_t/sameexp/near` | 12.4 | 4.0 | -68% | 16.0 | 6.9 | -57% | | `div/decimal32_t/sameexp/near` | 18.6 | 11.9 | -36% | 25.7 | 13.5 | -47% | | `div/decimal_fast32_t/sameexp/near` | 16.6 | 5.9 | -64% | 18.2 | 8.6 | -53% | | `add/decimal32_t/sameexp/up` | 22.8 | 13.2 | -42% | 22.4 | 12.8 | -43% | | `add/decimal_fast32_t/sameexp/up` | 18.4 | 11.7 | -36% | 18.9 | 12.7 | -33% | | `sub/decimal32_t/sameexp/up` | 22.7 | 13.1 | -42% | 22.2 | 13.4 | -40% | | `sub/decimal_fast32_t/sameexp/up` | 18.4 | 12.9 | -30% | 19.2 | 12.9 | -33% | | `mul/decimal32_t/sameexp/up` | 20.5 | 9.3 | -55% | 22.7 | 10.1 | -56% | | `mul/decimal_fast32_t/sameexp/up` | 14.6 | 4.6 | -68% | 18.6 | 8.8 | -53% | | `div/decimal32_t/sameexp/up` | 30.3 | 12.2 | -60% | 38.8 | 15.1 | -61% | | `div/decimal_fast32_t/sameexp/up` | 23.8 | 6.0 | -75% | 26.5 | 9.9 | -63% | | `add/decimal32_t/band/near` | 26.1 | 18.4 | -30% | 27.0 | 22.0 | -19% | | `add/decimal_fast32_t/band/near` | 24.8 | 15.6 | -37% | 23.5 | 15.7 | -33% | | `sub/decimal32_t/band/near` | 26.1 | 18.2 | -30% | 26.8 | 22.3 | -17% | | `sub/decimal_fast32_t/band/near` | 24.7 | 16.5 | -33% | 24.0 | 16.9 | -30% | | `mul/decimal32_t/band/near` | 12.0 | 8.7 | -28% | 15.7 | 8.7 | -45% | | `mul/decimal_fast32_t/band/near` | 12.4 | 4.0 | -68% | 16.0 | 6.9 | -57% | | `div/decimal32_t/band/near` | 18.7 | 11.8 | -37% | 25.8 | 13.5 | -48% | | `div/decimal_fast32_t/band/near` | 16.6 | 5.9 | -64% | 18.3 | 8.6 | -53% | | `add/decimal32_t/band/up` | 30.3 | 17.9 | -41% | 32.6 | 22.1 | -32% | | `add/decimal_fast32_t/band/up` | 23.9 | 16.1 | -33% | 23.0 | 15.8 | -31% | | `sub/decimal32_t/band/up` | 30.4 | 17.9 | -41% | 32.2 | 22.1 | -31% | | `sub/decimal_fast32_t/band/up` | 24.1 | 16.6 | -31% | 23.8 | 17.1 | -28% | | `mul/decimal32_t/band/up` | 20.6 | 9.5 | -54% | 22.7 | 10.1 | -56% | | `mul/decimal_fast32_t/band/up` | 14.7 | 4.6 | -69% | 18.6 | 8.8 | -53% | | `div/decimal32_t/band/up` | 30.2 | 12.2 | -60% | 39.0 | 15.1 | -61% | | `div/decimal_fast32_t/band/up` | 23.7 | 6.0 | -75% | 26.6 | 9.8 | -63% | | `add/decimal32_t/short/near` | 9.2 | 8.3 | -10% | 9.6 | 9.4 | -2% | | `add/decimal_fast32_t/short/near` | 21.3 | 17.6 | -17% | 21.3 | 16.0 | -25% | | `sub/decimal32_t/short/near` | 9.4 | 8.3 | -12% | 9.6 | 9.7 | +1% | | `sub/decimal_fast32_t/short/near` | 21.1 | 18.1 | -14% | 22.0 | 16.2 | -26% | | `mul/decimal32_t/short/near` | 14.1 | 9.2 | -35% | 18.1 | 11.6 | -36% | | `mul/decimal_fast32_t/short/near` | 10.6 | 4.1 | -61% | 12.0 | 7.0 | -42% | | `div/decimal32_t/short/near` | 30.9 | 16.0 | -48% | 35.0 | 18.2 | -48% | | `div/decimal_fast32_t/short/near` | 15.9 | 5.9 | -63% | 17.7 | 8.6 | -51% | | `add/decimal32_t/short/up` | 41.4 | 9.5 | -77% | 45.4 | 10.9 | -76% | | `add/decimal_fast32_t/short/up` | 21.7 | 18.2 | -16% | 24.8 | 17.7 | -29% | | `sub/decimal32_t/short/up` | 41.5 | 9.6 | -77% | 45.5 | 11.5 | -75% | | `sub/decimal_fast32_t/short/up` | 22.3 | 18.8 | -16% | 25.9 | 17.9 | -31% | | `mul/decimal32_t/short/up` | 17.7 | 9.2 | -48% | 20.9 | 12.4 | -41% | | `mul/decimal_fast32_t/short/up` | 15.9 | 4.7 | -70% | 20.4 | 8.9 | -56% | | `div/decimal32_t/short/up` | 43.7 | 16.5 | -62% | 50.3 | 19.7 | -61% | | `div/decimal_fast32_t/short/up` | 24.6 | 6.0 | -76% | 27.3 | 9.8 | -64% | | `add/decimal64_t/stock/near` | 6.6 | 5.4 | -18% | 7.5 | 6.6 | -12% | | `add/decimal_fast64_t/stock/near` | 8.0 | 6.8 | -15% | 5.0 | 3.8 | -24% | | `sub/decimal64_t/stock/near` | 10.0 | 7.9 | -21% | 7.7 | 6.7 | -13% | | `sub/decimal_fast64_t/stock/near` | 11.9 | 5.9 | -50% | 7.7 | 6.2 | -19% | | `mul/decimal64_t/stock/near` | 27.0 | 20.7 | -23% | 27.7 | 19.3 | -30% | | `mul/decimal_fast64_t/stock/near` | 24.6 | 15.8 | -36% | 25.8 | 15.4 | -40% | | `div/decimal64_t/stock/near` | 34.8 | 20.9 | -40% | 33.6 | 21.2 | -37% | | `div/decimal_fast64_t/stock/near` | 27.5 | 13.0 | -53% | 27.0 | 14.1 | -48% | | `add/decimal64_t/full/near` | 26.9 | 18.7 | -30% | 26.2 | 19.2 | -27% | | `add/decimal_fast64_t/full/near` | 27.0 | 18.2 | -33% | 28.6 | 15.2 | -47% | | `sub/decimal64_t/full/near` | 26.9 | 18.2 | -32% | 26.4 | 19.2 | -27% | | `sub/decimal_fast64_t/full/near` | 26.0 | 17.1 | -34% | 29.1 | 14.8 | -49% | | `mul/decimal64_t/full/near` | 25.1 | 17.0 | -32% | 24.6 | 15.7 | -36% | | `mul/decimal_fast64_t/full/near` | 23.4 | 11.8 | -50% | 25.4 | 13.2 | -48% | | `div/decimal64_t/full/near` | 29.7 | 16.0 | -46% | 28.8 | 18.0 | -38% | | `div/decimal_fast64_t/full/near` | 26.6 | 12.9 | -52% | 27.0 | 13.0 | -52% | | `add/decimal64_t/full/up` | 38.7 | 19.6 | -49% | 38.1 | 20.7 | -46% | | `add/decimal_fast64_t/full/up` | 30.5 | 21.0 | -31% | 30.8 | 20.0 | -35% | | `sub/decimal64_t/full/up` | 38.6 | 19.7 | -49% | 38.1 | 20.7 | -46% | | `sub/decimal_fast64_t/full/up` | 29.6 | 20.4 | -31% | 31.2 | 20.3 | -35% | | `mul/decimal64_t/full/up` | 34.4 | 18.8 | -45% | 32.6 | 17.7 | -46% | | `mul/decimal_fast64_t/full/up` | 33.1 | 14.3 | -57% | 30.6 | 15.7 | -49% | | `div/decimal64_t/full/up` | 58.9 | 16.1 | -73% | 53.9 | 18.8 | -65% | | `div/decimal_fast64_t/full/up` | 48.8 | 13.2 | -73% | 49.4 | 13.0 | -74% | | `add/decimal64_t/sameexp/near` | 15.1 | 12.1 | -20% | 14.4 | 12.2 | -15% | | `add/decimal_fast64_t/sameexp/near` | 17.4 | 12.9 | -26% | 19.3 | 11.3 | -41% | | `sub/decimal64_t/sameexp/near` | 15.1 | 11.7 | -23% | 14.5 | 12.4 | -14% | | `sub/decimal_fast64_t/sameexp/near` | 17.1 | 12.9 | -25% | 20.0 | 11.2 | -44% | | `mul/decimal64_t/sameexp/near` | 25.1 | 16.6 | -34% | 24.5 | 15.7 | -36% | | `mul/decimal_fast64_t/sameexp/near` | 23.4 | 11.7 | -50% | 25.4 | 13.2 | -48% | | `div/decimal64_t/sameexp/near` | 29.7 | 16.1 | -46% | 28.8 | 17.9 | -38% | | `div/decimal_fast64_t/sameexp/near` | 26.6 | 12.9 | -52% | 26.7 | 13.0 | -51% | | `add/decimal64_t/sameexp/up` | 25.7 | 12.6 | -51% | 24.5 | 13.8 | -44% | | `add/decimal_fast64_t/sameexp/up` | 21.9 | 14.2 | -35% | 21.3 | 15.5 | -27% | | `sub/decimal64_t/sameexp/up` | 25.4 | 12.8 | -50% | 24.9 | 14.0 | -44% | | `sub/decimal_fast64_t/sameexp/up` | 21.3 | 14.4 | -32% | 21.6 | 16.0 | -26% | | `mul/decimal64_t/sameexp/up` | 34.3 | 18.7 | -45% | 32.5 | 17.7 | -46% | | `mul/decimal_fast64_t/sameexp/up` | 33.1 | 14.3 | -57% | 30.6 | 15.7 | -49% | | `div/decimal64_t/sameexp/up` | 58.9 | 16.1 | -73% | 54.0 | 18.8 | -65% | | `div/decimal_fast64_t/sameexp/up` | 48.9 | 13.2 | -73% | 49.4 | 13.0 | -74% | | `add/decimal64_t/band/near` | 43.5 | 17.1 | -61% | 45.0 | 22.1 | -51% | | `add/decimal_fast64_t/band/near` | 47.4 | 15.7 | -67% | 41.3 | 15.1 | -63% | | `sub/decimal64_t/band/near` | 44.1 | 17.3 | -61% | 44.9 | 22.4 | -50% | | `sub/decimal_fast64_t/band/near` | 46.3 | 15.7 | -66% | 41.4 | 15.5 | -63% | | `mul/decimal64_t/band/near` | 25.0 | 16.7 | -33% | 24.6 | 15.7 | -36% | | `mul/decimal_fast64_t/band/near` | 23.4 | 11.8 | -50% | 25.4 | 13.2 | -48% | | `div/decimal64_t/band/near` | 29.7 | 16.1 | -46% | 28.8 | 18.0 | -38% | | `div/decimal_fast64_t/band/near` | 26.5 | 12.9 | -51% | 26.8 | 13.0 | -51% | | `add/decimal64_t/band/up` | 46.7 | 17.0 | -64% | 45.6 | 22.0 | -52% | | `add/decimal_fast64_t/band/up` | 41.5 | 15.7 | -62% | 38.9 | 15.2 | -61% | | `sub/decimal64_t/band/up` | 46.2 | 17.2 | -63% | 45.6 | 22.1 | -52% | | `sub/decimal_fast64_t/band/up` | 40.7 | 15.6 | -62% | 39.4 | 15.5 | -61% | | `mul/decimal64_t/band/up` | 34.3 | 18.8 | -45% | 32.6 | 17.7 | -46% | | `mul/decimal_fast64_t/band/up` | 33.0 | 14.3 | -57% | 30.7 | 15.7 | -49% | | `div/decimal64_t/band/up` | 58.9 | 16.1 | -73% | 53.9 | 18.7 | -65% | | `div/decimal_fast64_t/band/up` | 48.8 | 13.1 | -73% | 49.5 | 13.0 | -74% | | `add/decimal64_t/short/near` | 10.1 | 8.9 | -12% | 10.3 | 10.5 | +2% | | `add/decimal_fast64_t/short/near` | 22.9 | 18.6 | -19% | 24.9 | 15.9 | -36% | | `sub/decimal64_t/short/near` | 10.6 | 8.9 | -16% | 10.4 | 10.7 | +3% | | `sub/decimal_fast64_t/short/near` | 21.8 | 17.6 | -19% | 25.6 | 15.5 | -39% | | `mul/decimal64_t/short/near` | 4.5 | 4.6 | +2% | 6.8 | 6.7 | -1% | | `mul/decimal_fast64_t/short/near` | 22.2 | 12.0 | -46% | 20.7 | 13.2 | -36% | | `div/decimal64_t/short/near` | 38.6 | 23.8 | -38% | 37.7 | 25.4 | -33% | | `div/decimal_fast64_t/short/near` | 26.1 | 12.9 | -51% | 26.2 | 13.0 | -50% | | `add/decimal64_t/short/up` | 46.2 | 9.9 | -79% | 47.9 | 11.9 | -75% | | `add/decimal_fast64_t/short/up` | 30.8 | 21.2 | -31% | 31.7 | 20.2 | -36% | | `sub/decimal64_t/short/up` | 45.4 | 10.1 | -78% | 47.8 | 12.0 | -75% | | `sub/decimal_fast64_t/short/up` | 29.7 | 20.5 | -31% | 32.1 | 20.4 | -36% | | `mul/decimal64_t/short/up` | 4.5 | 4.6 | +2% | 6.8 | 6.7 | -1% | | `mul/decimal_fast64_t/short/up` | 32.7 | 14.5 | -56% | 30.4 | 15.7 | -48% | | `div/decimal64_t/short/up` | 69.2 | 24.8 | -64% | 64.4 | 26.5 | -59% | | `div/decimal_fast64_t/short/up` | 50.1 | 13.2 | -74% | 50.1 | 13.0 | -74% | | `add/decimal128_t/stock/near` | 4.2 | 3.9 | -7% | 6.8 | 6.4 | -6% | | `add/decimal_fast128_t/stock/near` | 8.3 | 3.8 | -54% | 3.9 | 4.4 | +13% | | `sub/decimal128_t/stock/near` | 6.6 | 6.1 | -8% | 6.8 | 6.5 | -4% | | `sub/decimal_fast128_t/stock/near` | 13.1 | 6.8 | -48% | 8.5 | 8.7 | +2% | | `mul/decimal128_t/stock/near` | 38.6 | 34.2 | -11% | 40.4 | 26.7 | -34% | | `mul/decimal_fast128_t/stock/near` | 42.8 | 38.4 | -10% | 41.1 | 32.8 | -20% | | `div/decimal128_t/stock/near` | 70.0 | 62.1 | -11% | 74.1 | 60.8 | -18% | | `div/decimal_fast128_t/stock/near` | 60.9 | 54.3 | -11% | 67.5 | 51.5 | -24% | | `add/decimal128_t/full/near` | 39.5 | 29.1 | -26% | 41.3 | 31.5 | -24% | | `add/decimal_fast128_t/full/near` | 48.0 | 31.1 | -35% | 45.8 | 30.9 | -33% | | `sub/decimal128_t/full/near` | 39.9 | 31.6 | -21% | 42.0 | 30.9 | -26% | | `sub/decimal_fast128_t/full/near` | 50.7 | 31.1 | -39% | 47.3 | 31.8 | -33% | | `mul/decimal128_t/full/near` | 34.5 | 29.5 | -14% | 36.8 | 22.2 | -40% | | `mul/decimal_fast128_t/full/near` | 31.0 | 26.9 | -13% | 31.9 | 21.2 | -34% | | `div/decimal128_t/full/near` | 64.2 | 56.7 | -12% | 67.1 | 54.8 | -18% | | `div/decimal_fast128_t/full/near` | 60.0 | 51.0 | -15% | 67.3 | 48.4 | -28% | | `add/decimal128_t/full/up` | 81.4 | 30.8 | -62% | 67.2 | 31.9 | -53% | | `add/decimal_fast128_t/full/up` | 71.6 | 37.4 | -48% | 67.7 | 34.9 | -48% | | `sub/decimal128_t/full/up` | 82.1 | 32.8 | -60% | 67.2 | 31.6 | -53% | | `sub/decimal_fast128_t/full/up` | 75.7 | 38.3 | -49% | 69.9 | 36.4 | -48% | | `mul/decimal128_t/full/up` | 109.4 | 30.2 | -72% | 89.2 | 26.0 | -71% | | `mul/decimal_fast128_t/full/up` | 92.7 | 29.9 | -68% | 82.7 | 24.8 | -70% | | `div/decimal128_t/full/up` | 94.9 | 59.4 | -37% | 95.9 | 54.9 | -43% | | `div/decimal_fast128_t/full/up` | 85.9 | 53.6 | -38% | 88.4 | 48.9 | -45% | | `add/decimal128_t/sameexp/near` | 18.9 | 16.1 | -15% | 21.3 | 18.8 | -12% | | `add/decimal_fast128_t/sameexp/near` | 31.7 | 18.5 | -42% | 27.3 | 19.0 | -30% | | `sub/decimal128_t/sameexp/near` | 19.1 | 16.1 | -16% | 21.8 | 18.5 | -15% | | `sub/decimal_fast128_t/sameexp/near` | 35.5 | 18.6 | -48% | 28.9 | 19.4 | -33% | | `mul/decimal128_t/sameexp/near` | 34.5 | 29.7 | -14% | 36.8 | 22.3 | -39% | | `mul/decimal_fast128_t/sameexp/near` | 31.3 | 27.0 | -14% | 32.0 | 21.3 | -33% | | `div/decimal128_t/sameexp/near` | 64.1 | 56.7 | -12% | 67.0 | 54.8 | -18% | | `div/decimal_fast128_t/sameexp/near` | 60.0 | 50.9 | -15% | 67.3 | 48.5 | -28% | | `add/decimal128_t/sameexp/up` | 42.3 | 16.8 | -60% | 37.1 | 19.4 | -48% | | `add/decimal_fast128_t/sameexp/up` | 35.5 | 24.5 | -31% | 40.9 | 22.6 | -45% | | `sub/decimal128_t/sameexp/up` | 42.6 | 17.0 | -60% | 37.8 | 19.4 | -49% | | `sub/decimal_fast128_t/sameexp/up` | 40.0 | 25.4 | -36% | 43.0 | 24.3 | -43% | | `mul/decimal128_t/sameexp/up` | 108.8 | 30.3 | -72% | 89.0 | 26.1 | -71% | | `mul/decimal_fast128_t/sameexp/up` | 93.5 | 30.1 | -68% | 82.6 | 24.9 | -70% | | `div/decimal128_t/sameexp/up` | 94.9 | 59.5 | -37% | 95.7 | 54.9 | -43% | | `div/decimal_fast128_t/sameexp/up` | 85.8 | 53.6 | -38% | 88.3 | 49.0 | -45% | | `add/decimal128_t/band/near` | 132.2 | 36.4 | -72% | 124.2 | 32.9 | -74% | | `add/decimal_fast128_t/band/near` | 128.4 | 35.0 | -73% | 118.6 | 34.6 | -71% | | `sub/decimal128_t/band/near` | 134.6 | 37.1 | -72% | 125.5 | 33.1 | -74% | | `sub/decimal_fast128_t/band/near` | 131.7 | 35.4 | -73% | 120.4 | 36.2 | -70% | | `mul/decimal128_t/band/near` | 34.4 | 29.5 | -14% | 36.8 | 22.2 | -40% | | `mul/decimal_fast128_t/band/near` | 31.2 | 27.0 | -13% | 31.9 | 21.2 | -34% | | `div/decimal128_t/band/near` | 64.1 | 56.7 | -12% | 67.0 | 55.0 | -18% | | `div/decimal_fast128_t/band/near` | 59.8 | 50.9 | -15% | 67.4 | 48.4 | -28% | | `add/decimal128_t/band/up` | 131.4 | 36.7 | -72% | 118.8 | 32.8 | -72% | | `add/decimal_fast128_t/band/up` | 127.3 | 35.3 | -72% | 113.5 | 34.4 | -70% | | `sub/decimal128_t/band/up` | 132.6 | 37.5 | -72% | 120.0 | 33.2 | -72% | | `sub/decimal_fast128_t/band/up` | 131.4 | 35.8 | -73% | 115.2 | 36.1 | -69% | | `mul/decimal128_t/band/up` | 108.8 | 30.2 | -72% | 89.1 | 26.1 | -71% | | `mul/decimal_fast128_t/band/up` | 93.0 | 29.9 | -68% | 82.7 | 24.8 | -70% | | `div/decimal128_t/band/up` | 95.0 | 59.3 | -38% | 96.1 | 54.9 | -43% | | `div/decimal_fast128_t/band/up` | 85.8 | 53.4 | -38% | 88.5 | 49.0 | -45% | | `add/decimal128_t/short/near` | 11.7 | 12.5 | +7% | 14.5 | 14.6 | +1% | | `add/decimal_fast128_t/short/near` | 48.6 | 31.8 | -35% | 45.8 | 32.0 | -30% | | `sub/decimal128_t/short/near` | 12.0 | 12.3 | +3% | 14.7 | 14.1 | -4% | | `sub/decimal_fast128_t/short/near` | 51.1 | 31.7 | -38% | 47.3 | 32.9 | -30% | | `mul/decimal128_t/short/near` | 6.2 | 6.8 | +10% | 9.3 | 9.2 | -1% | | `mul/decimal_fast128_t/short/near` | 27.8 | 27.0 | -3% | 28.1 | 20.9 | -26% | | `div/decimal128_t/short/near` | 78.9 | 70.8 | -10% | 82.2 | 63.9 | -22% | | `div/decimal_fast128_t/short/near` | 60.4 | 51.4 | -15% | 66.9 | 48.8 | -27% | | `add/decimal128_t/short/up` | 88.6 | 11.7 | -87% | 78.5 | 14.9 | -81% | | `add/decimal_fast128_t/short/up` | 72.9 | 34.5 | -53% | 68.9 | 35.7 | -48% | | `sub/decimal128_t/short/up` | 91.5 | 11.7 | -87% | 78.4 | 14.6 | -81% | | `sub/decimal_fast128_t/short/up` | 77.1 | 35.0 | -55% | 71.2 | 37.0 | -48% | | `mul/decimal128_t/short/up` | 6.2 | 6.7 | +8% | 9.3 | 9.2 | -1% | | `mul/decimal_fast128_t/short/up` | 93.1 | 26.8 | -71% | 82.4 | 24.7 | -70% | | `div/decimal128_t/short/up` | 111.5 | 73.1 | -34% | 110.4 | 64.4 | -42% | | `div/decimal_fast128_t/short/up` | 87.3 | 54.3 | -38% | 89.5 | 49.4 | -45% | </details> <details> <summary>Code size</summary> Bytes of `.text` in an object file with one function that returns `a + b`, `a * b` or `a / b`, `-O2`: | function | GCC develop | GCC fix | Clang develop | Clang fix | |---|---|---|---|---| | `decimal32_t` `+` | 8671 | 9625 | 5528 | 7153 | | `decimal32_t` `*` | 6883 | 5460 | 4468 | 4537 | | `decimal32_t` `/` | 7513 | 3580 | 5090 | 3831 | | `decimal64_t` `+` | 13368 | 12372 | 9466 | 9538 | | `decimal64_t` `*` | 8564 | 7432 | 6398 | 6069 | | `decimal64_t` `/` | 10237 | 6202 | 7041 | 4285 | | `decimal128_t` `+` | 20728 | 21239 | 13862 | 15963 | | `decimal128_t` `*` | 17308 | 16678 | 12497 | 13408 | | `decimal128_t` `/` | 16155 | 11665 | 10896 | 9440 | | `decimal_fast32_t` `+` | 4418 | 5753 | 3777 | 5729 | | `decimal_fast32_t` `*` | 3128 | 2848 | 3472 | 3770 | | `decimal_fast32_t` `/` | 3647 | 1716 | 3330 | 2480 | | `decimal_fast64_t` `+` | 9230 | 8515 | 6722 | 7698 | | `decimal_fast64_t` `*` | 4798 | 4137 | 4306 | 4275 | | `decimal_fast64_t` `/` | 5910 | 2192 | 4607 | 2377 | | `decimal_fast128_t` `+` | 15495 | 18184 | 11815 | 15005 | | `decimal_fast128_t` `*` | 12448 | 14180 | 9704 | 10565 | | `decimal_fast128_t` `/` | 10150 | 7212 | 7993 | 6804 | </details>
- The old `sqrt` found the root with Newton steps that divide 128-bit and 256-bit integers. It also converted the input and the result with decimal multiplications. - The new `sqrt` writes x as N * 10^j with an even j, so that sqrt(N) has p digits. - A table of 1 / sqrt with a parabola term gives a first estimate, as in `cbrt`. Goldschmidt steps that only multiply then give binary estimates of sqrt(N) and 1 / sqrt(N). - For `decimal128_t`, one Newton step on the root uses the reciprocal in place of a division. Its fraction gives the result unless the root is near an integer or a half. - In the other cases, the remainder N - z^2 gives the floor and the rounding in the current mode. The estimate is within one unit of the floor, so the remainder is small and the low word of N holds it. - The function builds the result one time, with `pack_in_range`, as `cbrt` does. A root that rounds up to 10^p loses one digit first. - The old table, the reciprocal approximations and the three implementation files go away, and also `mul128_by_64` in `u256.hpp`, because only the old `sqrt` used it. - `sqrt` and `cbrt` share `umul128` in `u256.hpp`, the 64-bit product that each had as its own copy. - With `BOOST_DECIMAL_FAST_MATH`, `sqrt` of a zero returns zero before the root search. Before, a zero went into the search, and a shift by 64 and the table index were out of range. `test_fast_math` checks it. - `test_sqrt_rounding` gets a `decimal128_t` case that starts from an estimate above the floor, and cases whose root rounds up to 10^p. - `doc/decimal_float_and_sqrt.md` sections 5 to 7 describe the new method, its files and its speed. <details> <summary>Correctness</summary> - The results are the same bits as on `develop` (f3e436f). I compared a hash of the results in the five rounding modes: - All `decimal32_t` significands from 1000000 to 9999999, with the exponents 0, 1, -101 and 90. - 1000000 random values of each of the six types in each mode, with random exponents. - For each random value, a square q^2 and its neighbors q^2 - 1 and q^2 + 1, also with random exponents. - Zeros, infinities, NaN, negative values, the smallest and largest values, and powers of ten. - For `decimal128_t`, y and t are within 2^-59.5. Then the Newton step is within 0.022 of the root, and the fast path needs a margin of 2^-5. A check of 10^9 random inputs gives 2^-59.7. - `test_sqrt`, `test_sqrt_rounding`, `test_sqrt_rounding_upward` and `test_fast_math` pass with GCC 16.2 and Clang 23.1, in C++14, C++17 and C++20, at `-O2` and `-O3`, with `-Wall -Wextra -Wconversion -Wsign-conversion -Wold-style-cast -Wshadow -Werror`. - `test_fast_math` also passes with `-fsanitize=undefined,address`. Without the zero test, UBSan stops it with "shift exponent 64 is too large". </details> <details> <summary>Speed</summary> The table gives ns for each call. The input has 1000000 random values with `p` digits and exponents from -40 to 40. The mode is the default mode. I used `-O3` on an AMD Ryzen 9 3900X, with GCC 16.2 and Clang 23.1, on one core. Each value is the best of 3 runs. | type | GCC develop | GCC fix | Clang develop | Clang fix | |---|---|---|---|---| | `decimal32_t` | 55.8 | 15.6 | 75.4 | 19.7 | | `decimal64_t` | 123.2 | 18.8 | 141.1 | 23.0 | | `decimal128_t` | 423.8 | 38.8 | 395.9 | 43.3 | | `decimal_fast32_t` | 49.3 | 11.3 | 55.1 | 13.9 | | `decimal_fast64_t` | 115.3 | 14.7 | 109.3 | 13.8 | | `decimal_fast128_t` | 412.6 | 33.3 | 389.1 | 38.8 | </details> <details> <summary>Size</summary> One function calls `sqrt` for `decimal32_t`, `decimal64_t` and `decimal128_t`. The table gives the `.text` size of its object file at `-O2`, in bytes. | compiler | develop | fix | |---|---|---| | GCC 16.2 | 79623 | 25226 | | Clang 23.1 | 65058 | 21521 | </details>
This PR has 2 commits. Each commit builds, and the tests pass after each commit. 1. Change only the exponent field in scalbn and scalbln: for a finite, canonical value with a new exponent in range, the result comes from a change to the exponent field only. A helper that is not inlined does the other cases. It clamps the exponent to a limit that gives the same overflow or underflow, it gives a canonical zero for a non-canonical significand, and it gives a quiet NaN for a signaling NaN. For zero, the exponent now changes too, as IEEE 754 gives for scaleB and as Intel libbid does. 2. Keep the largest exponent when a new exponent is too large: on exponent overflow with room in the coefficient, the constructors of `decimal32_t`, `decimal64_t` and `decimal128_t` add only the excess digits. This also corrects the quantum exponent of products and of parsed values. Fixes #1518 <details> <summary>Correctness</summary> - The reproducer of #1518 gives the same bits as Intel libbid 2.0 Update 4 for all rows, and UBSan reports no error. - I compared the bits of `scalbn` with `develop` for 18000000 calls in the five rounding modes, with exponents in the range that `develop` handles correctly. Only the exponent of zero is different. - I compared `scalbn` with Intel libbid on 3400000 special and non-canonical encodings and on 6000000 values near the largest exponent. No value is different. - I compared `+`, `-`, `*`, `/` and `fma` near the exponent limits with Intel libbid. With the second commit, the quantum exponent of `decimal32_t` and `decimal64_t` products on overflow is the same as Intel libbid (410 and 621 differences in 400000 on `develop`). - `test_cmath`, `test_construction_exponent_range` and `github_issue_805` pass with GCC 16.2 and Clang 23.1, in C++14, C++17 and C++20, with ASan and UBSan and `-Werror`. They also pass with `-m32`, with `BOOST_DECIMAL_FAST_MATH`, and with MSVC 19.4 x64 and x86 in C++14, C++17 and C++20 with `/W4 /WX`. - An NVCC kernel for the six types gives the same bits as the host. </details> <details> <summary>Speed</summary> ns for each `scalbn` call, 65536 inputs, `-O3`, AMD Ryzen 9 3900X. | type | GCC develop | GCC fix | Clang develop | Clang fix | Intel libbid | |---|---|---|---|---|---| | `decimal32_t` | 9.15 | 2.08 | 11.3 | 2.00 | 3.13 | | `decimal64_t` | 9.55 | 1.80 | 11.5 | 1.66 | 3.34 | | `decimal128_t` | 14.6 | 1.91 | 22.5 | 1.58 | 5.01 | | `decimal_fast32_t` | 3.19 | 1.82 | 2.39 | 1.11 | | | `decimal_fast64_t` | 11.8 | 1.23 | 1.90 | 1.09 | | | `decimal_fast128_t` | 15.1 | 1.49 | 6.11 | 1.30 | | MSVC 19.4 x64, ns for each call, develop to fix: `decimal32_t` 12.5 to 2.1, `decimal64_t` 13.5 to 2.1, `decimal128_t` 19.3 to 1.9, `decimal_fast32_t` 2.6 to 2.2, `decimal_fast64_t` 4.0 to 2.4, `decimal_fast128_t` 9.6 to 2.7. </details>
This PR has 1 commit. - `acosh` gives `-NaN` for zero, a subnormal and `-inf`, as it does for the other arguments below 1. - `atanh` gives NaN for an infinity, as it does for the other arguments outside `[-1, 1]`. - `test_atanh` expects NaN for an infinity, and a new test covers the edges of both domains for the six types. C17 F.10.2.1 and F.10.2.3 and IEEE 754-2019 9.2 give a NaN for an argument outside the domain. Intel libbid and glibc give NaN for the same arguments. Fixes #1520 <details> <summary>Correctness</summary> - The reproducer of #1520 gives NaN for all rows with the fix: ``` decimal32_t: acosh(denorm_min) = -nan(ind), acosh(-denorm_min) = -nan(ind), acosh(-inf) = -nan(ind), atanh(inf) = nan, atanh(-inf) = nan decimal64_t: acosh(denorm_min) = -nan(ind), acosh(-denorm_min) = -nan(ind), acosh(-inf) = -nan(ind), atanh(inf) = nan, atanh(-inf) = nan decimal128_t: acosh(denorm_min) = -nan(ind), acosh(-denorm_min) = -nan(ind), acosh(-inf) = -nan(ind), atanh(inf) = nan, atanh(-inf) = nan decimal_fast32_t: acosh(denorm_min) = -nan(ind), acosh(-denorm_min) = -nan(ind), acosh(-inf) = -nan(ind), atanh(inf) = nan, atanh(-inf) = nan decimal_fast64_t: acosh(denorm_min) = -nan(ind), acosh(-denorm_min) = -nan(ind), acosh(-inf) = -nan(ind), atanh(inf) = nan, atanh(-inf) = nan decimal_fast128_t: acosh(denorm_min) = -nan(ind), acosh(-denorm_min) = -nan(ind), acosh(-inf) = -nan(ind), atanh(inf) = nan, atanh(-inf) = nan ``` - `github_issue_1520` fails on `develop` with 30 errors and passes with the fix. - `github_issue_1520`, `test_acosh` and `test_atanh` pass with GCC 16.2 and Clang 23.1, in C++14 and C++20, with `-Wall -Wextra -Werror`. The new test also passes with `BOOST_DECIMAL_FAST_MATH`. - With `BOOST_DECIMAL_FAST_MATH`, the code paths do not change: `isinf` is always false, and `acosh` does not use the changed branch. </details> <details> <summary>Speed</summary> The change applies only to values that are not normal. Cycles for each call, 512 normal inputs, `-O2`, AMD Ryzen 9 3900X, core 3: | function | type | GCC develop | GCC fix | Clang develop | Clang fix | |---|---|---|---|---|---| | `acosh` | `decimal32_t` | 2716 | 2706 | 2922 | 2924 | | `acosh` | `decimal64_t` | 8798 | 8804 | 7725 | 7716 | | `acosh` | `decimal128_t` | 13594 | 13575 | 12695 | 12777 | | `atanh` | `decimal32_t` | 3189 | 3180 | 3465 | 3466 | | `atanh` | `decimal64_t` | 11626 | 11598 | 10120 | 10117 | | `atanh` | `decimal128_t` | 19139 | 19223 | 17609 | 17507 | The differences are below 1%, which is the noise of the measurement. </details>
Bumps [handlebars](https://github.com/handlebars-lang/handlebars.js) from 4.7.9 to 4.7.10. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/handlebars-lang/handlebars.js/releases">handlebars's releases</a>.</em></p> <blockquote> <h2>v4.7.10</h2> <ul> <li>security: Sanitize the source map URL when minifying - f37c599</li> <li>security: Iterate lazily in #each and strip whitespace in linear time - 812c226</li> <li>security: Escape <code><!--</code> and <code><script</code> in precompiled output - 609d1b1 GHSA-xw65-4hp5-5hc7</li> <li>security: Don't trust special properties on context data - ceec388 GHSA-p8wg-vrv2-v86f</li> <li>security: Only compile partials that are template strings - c28ee7a</li> <li>security: Only dispatch known node types in the Compiler and Visitor - 7d501a5</li> <li>security: Validate AST values in the compiler instead of the parser - 703fdcc GHSA-8r5x-fm3f-whwj</li> <li>Clarify that --root does not restrict filesystem access - 0fcf25c</li> <li>Bump minimist to ^1.2.8 - ea8ed82</li> <li>Fix Ruby component publishing documentation - d069c1c</li> <li>Fix Composer component definition - 6714e07</li> </ul> <p>Compatibility notes:</p> <ul> <li><code>{{#each}}</code> iterates iterables such as <code>Map</code>, <code>Set</code> and generators lazily, like <code>for...of</code>, instead of copying them into an array first. Values added to the iterable while the block renders are now visited too.</li> </ul> <p><a href="https://github.com/handlebars-lang/handlebars.js/compare/v4.7.9...v4.7.10">Commits</a></p> </blockquote> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/handlebars-lang/handlebars.js/blob/v4.7.10/release-notes.md">handlebars's changelog</a>.</em></p> <blockquote> <h2>v4.7.10 - October 5th, 2026</h2> <ul> <li>security: Sanitize the source map URL when minifying - f37c599</li> <li>security: Iterate lazily in #each and strip whitespace in linear time - 812c226</li> <li>security: Escape <code><!--</code> and <code><script</code> in precompiled output - 609d1b1 GHSA-xw65-4hp5-5hc7</li> <li>security: Don't trust special properties on context data - ceec388 GHSA-p8wg-vrv2-v86f</li> <li>security: Only compile partials that are template strings - c28ee7a</li> <li>security: Only dispatch known node types in the Compiler and Visitor - 7d501a5</li> <li>security: Validate AST values in the compiler instead of the parser - 703fdcc GHSA-8r5x-fm3f-whwj</li> <li>Clarify that --root does not restrict filesystem access - 0fcf25c</li> <li>Bump minimist to ^1.2.8 - ea8ed82</li> <li>Fix Ruby component publishing documentation - d069c1c</li> <li>Fix Composer component definition - 6714e07</li> </ul> <p>Compatibility notes:</p> <ul> <li><code>{{#each}}</code> iterates iterables such as <code>Map</code>, <code>Set</code> and generators lazily, like <code>for...of</code>, instead of copying them into an array first. Values added to the iterable while the block renders are now visited too.</li> </ul> <p><a href="https://github.com/handlebars-lang/handlebars.js/compare/v4.7.9...v4.7.10">Commits</a></p> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/handlebars-lang/handlebars.js/commit/45ce15255e0f430ac6d19742b18e316b7539264e"><code>45ce152</code></a> v4.7.10</li> <li><a href="https://github.com/handlebars-lang/handlebars.js/commit/e47b2365d5656ab5689ae226aef90a485a90c5d8"><code>e47b236</code></a> Update release notes</li> <li><a href="https://github.com/handlebars-lang/handlebars.js/commit/f37c599de9187715eb1e9afedcb2d80c175487a0"><code>f37c599</code></a> Sanitize the source map URL when minifying</li> <li><a href="https://github.com/handlebars-lang/handlebars.js/commit/812c2260cb8794ef4ab63a7f1d946431112560ca"><code>812c226</code></a> Iterate lazily in #each and strip whitespace in linear time</li> <li><a href="https://github.com/handlebars-lang/handlebars.js/commit/609d1b11c833c9a3e00f56f2f34d22f425446725"><code>609d1b1</code></a> Escape <code><!--</code> and <code><script</code> in precompiled output</li> <li><a href="https://github.com/handlebars-lang/handlebars.js/commit/ceec388abe1d1aac8f6369860d5f390fa71ef4fa"><code>ceec388</code></a> Don't trust special properties on context data</li> <li><a href="https://github.com/handlebars-lang/handlebars.js/commit/c28ee7adb312308e0fe26f4d435a481be5e1ff98"><code>c28ee7a</code></a> Only compile partials that are template strings</li> <li><a href="https://github.com/handlebars-lang/handlebars.js/commit/7d501a5af2ac3aa9ac788a8889dbe419849e46f1"><code>7d501a5</code></a> Only dispatch known node types in the Compiler and Visitor</li> <li><a href="https://github.com/handlebars-lang/handlebars.js/commit/703fdcc5fd6cc8d1cc0c33cc19de40c467c4b8d2"><code>703fdcc</code></a> Validate AST values in the compiler instead of the parser</li> <li><a href="https://github.com/handlebars-lang/handlebars.js/commit/0fcf25c63038b43e78e17adbf029cf073e80554d"><code>0fcf25c</code></a> Clarify that --root does not restrict filesystem access</li> <li>Additional commits viewable in <a href="https://github.com/handlebars-lang/handlebars.js/compare/v4.7.9...v4.7.10">compare view</a></li> </ul> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/boostorg/decimal/network/alerts). </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
- `fma` for `decimal32_t`, `decimal64_t` and the fast types added the exact product and `z` with `add_impl`. If `z` was far below the product, the nearest modes dropped `z`, and a tie in the product went to the even neighbor. - `fma` now keeps the exact product of the significands in 64, 128 or 256 bits, and it does not move the product up. `z` moves to the product. A far `z` moves up p digits, and the product moves down to it. - The operand that moves down records a dropped digit that is not zero. After the sum, this record goes into the last digit. Because of this, a carry or a borrow does not remove it. - A sum of 2p - 1 or 2p digits rounds in one step with the finalizers of multiplication. Other sums round in the constructor. A zero operand leaves one operation, which rounds one time. - For a finite `x` and `y` and an infinite `z`, `fma` returned `x * y + z`. A large product rounded to an infinity, and with `z` of the other sign the sum was NaN. `fma` now returns `z` in this case, for all six types. - `test_fma_tie` tests ties with a far `z`, a dropped digit with a carry or a borrow, the bounds of the finalizers, and the five rounding modes. `github_issue_1522` tests the infinite `z`. Fixes #1522 <details> <summary>Correctness</summary> - 3000000 random triples for each of `decimal32_t`, `decimal64_t` and `decimal128_t`, in the five rounding modes, against Intel libbid 2.0 Update 4. Each value that is not the same is an underflow error of libbid. Python `decimal` gives our value in each of these cases. - 4500000 triples with a far `z`, for all six types and the five modes, against Python `decimal`. All results are correct. - In the `tie_far` set of the benchmark, `develop` gives a different value from libbid for 6720 of 16384 `decimal32_t` triples and 6193 of 16384 `decimal64_t` triples. The fix gives 0. - `test_fma`, `test_fma_tie`, `github_issue_1522` and `test_cmath` pass with GCC 16.2 and Clang 23.1 in C++14 and C++20 with `-Wall -Wextra -Werror`. They also pass without the builtin 128-bit integer, with `BOOST_DECIMAL_FAST_MATH`, and with `BOOST_DECIMAL_NO_CONSTEVAL_DETECTION`. </details> <details> <summary>Speed</summary> The table gives ns for each call, the best of 3 runs. Each set has 16384 triples: random significands and exponents (random), a tie with a far `z` (tie_far), a `z` near the product (near), a `z` near `-(x * y)` (cancel) and a zero `z` (zero_z). I used `-O3 -march=native` on an AMD Ryzen 9 3900X, on one core. libbid is built with GCC at `-O2`. | type | set | GCC develop | GCC fix | Clang develop | Clang fix | libbid | |---|---|---|---|---|---|---| | `decimal32_t` | random | 40.3 | 18.9 | 39.8 | 17.6 | 42.4 | | `decimal32_t` | tie_far | 28.0 | 15.6 | 30.6 | 15.0 | 42.3 | | `decimal32_t` | near | 45.6 | 22.7 | 42.8 | 22.0 | 37.9 | | `decimal32_t` | cancel | 27.5 | 15.3 | 28.3 | 14.4 | 16.8 | | `decimal32_t` | zero_z | 26.6 | 14.3 | 29.8 | 12.2 | 37.6 | | `decimal64_t` | random | 112.7 | 30.0 | 92.6 | 28.8 | 37.8 | | `decimal64_t` | tie_far | 55.4 | 26.1 | 49.4 | 23.1 | 29.4 | | `decimal64_t` | near | 159.9 | 30.3 | 124.6 | 30.2 | 46.0 | | `decimal64_t` | cancel | 62.3 | 17.1 | 60.0 | 16.1 | 27.0 | | `decimal64_t` | zero_z | 40.0 | 21.5 | 38.3 | 18.4 | 31.9 | | `decimal128_t` | random | 135.2 | 58.1 | 115.6 | 56.4 | 154.1 | | `decimal128_t` | tie_far | 118.6 | 55.0 | 99.9 | 49.3 | 129.3 | | `decimal128_t` | near | 131.6 | 59.2 | 111.9 | 59.1 | 177.1 | | `decimal128_t` | cancel | 44.9 | 41.7 | 46.4 | 49.1 | 91.6 | | `decimal128_t` | zero_z | 102.6 | 37.1 | 83.6 | 32.1 | 113.1 | | `decimal_fast32_t` | random | 37.1 | 13.5 | 36.6 | 15.9 | | | `decimal_fast32_t` | tie_far | 26.1 | 10.1 | 26.8 | 13.4 | | | `decimal_fast32_t` | near | 41.5 | 16.9 | 40.3 | 19.3 | | | `decimal_fast32_t` | cancel | 23.6 | 13.4 | 25.1 | 13.0 | | | `decimal_fast32_t` | zero_z | 24.9 | 6.8 | 25.8 | 9.1 | | | `decimal_fast64_t` | random | 110.6 | 25.8 | 89.5 | 25.1 | | | `decimal_fast64_t` | tie_far | 57.5 | 22.5 | 47.3 | 20.2 | | | `decimal_fast64_t` | near | 153.5 | 26.3 | 120.7 | 26.8 | | | `decimal_fast64_t` | cancel | 58.1 | 18.5 | 60.0 | 15.7 | | | `decimal_fast64_t` | zero_z | 43.0 | 16.7 | 36.7 | 13.8 | | | `decimal_fast128_t` | random | 135.3 | 58.4 | 118.8 | 56.5 | | | `decimal_fast128_t` | tie_far | 121.5 | 55.0 | 102.4 | 50.0 | | | `decimal_fast128_t` | near | 128.6 | 60.4 | 115.2 | 58.6 | | | `decimal_fast128_t` | cancel | 49.1 | 46.9 | 46.9 | 51.6 | | | `decimal_fast128_t` | zero_z | 102.5 | 35.8 | 86.1 | 33.5 | | With Clang, the 128-bit types are 3 to 5 ns slower in the cancel set. </details> <details> <summary>Size</summary> One function calls `fma` for each of the six types. The table gives the size of all `.text` sections of its object file, in bytes. | compiler | flags | develop | fix | |---|---|---|---| | GCC 16.2 | `-O2` | 120683 | 117880 | | GCC 16.2 | `-Os` | 68440 | 68563 | | Clang 23.1 | `-O2` | 87270 | 82073 | | Clang 23.1 | `-Os` | 70472 | 68224 | </details>
…#1517) fmod scaled both significands to the smaller exponent and took the integer remainder. When the exponents were too far apart for that, it used x - trunc(x / y) * y. The quotient has more digits than the type, so this value is not exact, and often it is not even below y. When x / y overflowed, fmod returned NaN. For decimal128_t the limit used the digits10 of u256, which is one too large, so an exponent gap of 44 overflowed the u256 product. remainder was built on fmod and had the same defects. It also compared with |y| * 0.5, which rounds for a subnormal y with an odd last digit. remquo divided x by y, rounded the quotient n, and returned x - n * y. This is not exact either, and x / y could overflow to an infinite result. A halfway quotient went toward zero, not to the even integer. An infinite y gave NaN, but C gives x. The new detail::exact_mod takes the significands and exponents of x and y. When the exponent of x is the larger one, it reduces mx * 10^(ex - ey) modulo k * my in steps that keep the product in range, and stops at a zero remainder. Otherwise x is below 2 * 10^p at its own exponent, and one remainder is enough. The result is the exact remainder at the exponent min(ex, ey), which IEEE 754 prefers, and the low bits of the quotient. For remainder, k is 2. For remquo, k is one more than the mask of the stored quotient bits. A last compare rounds the quotient half to even. The remainder stays in the type of the significand. Each step multiplies it by at most 10^19 and reduces the product with one division: a 2-by-1 word division for decimal32_t and decimal64_t, and a 3-by-2 word division for decimal128_t, so no u256 value is necessary. For decimal32_t, x times 10^12 fits 64 bits, so the first step needs no reduction before it. For a gap of 76 digits or more, the remainder is multiplied by (10^19)^n modulo k * my, which repeated squaring gives in about 2 * log2(n) steps. The result has at most p digits and the exponent of x or y, so pack_in_range writes it without the rounding checks of the constructor. A zero y is found from its significand, not with a decimal comparison. With BOOST_DECIMAL_FAST_MATH, a zero y gives zero, as in remainder and remquo. Before, operator% divided by the zero y, which trapped. operator% of each type, remainder and remquo now use this helper. operator% no longer divides first, because the quotient was used only for the old fallback and the overflow test. The remainder output of the division helpers was read only by operator%, so it is removed, and so is the u256 power of ten that only the old fmod used. random_decimal64_math expected NaN from 1e+277 % 1e-311, which was the overflow. The exact result is zero. Fixes #1516
This PR has 2 commits. - The cmath page no longer shows an `int* quo` parameter for `fdim`, `fmax` and `fmin`, and calls the result of `fdim` the positive difference. - `fdim` gives `+0` for an infinite `x` that is not above `y`, and a NaN for an infinite `x` with a NaN `y`. - `fdim` gives a quiet NaN for a signaling NaN argument. - `fdim` no longer has its own NaN and infinity branch. The compare `y >= x` is false for a NaN, and `x - y` gives a quiet NaN and the correct result for an infinite `x`. - A new test covers infinite arguments and signaling NaN arguments for the six types. C17 7.12.12.1 gives `x - y` if `x > y`, and `+0` if `x <= y`. IEEE 754-2019 6.2 and 7.2 give a quiet NaN for an operation on a signaling NaN. Intel libbid and glibc give the same results as the fix. Fixes #1525 <details> <summary>Correctness</summary> - The reproducer of #1525 gives the correct results for all six types with the fix: ``` decimal32_t: fdim(-inf, 1) = 0, fdim(inf, inf) = 0, fdim(inf, qNaN) = nan, fdim(snan, 1) = qNaN, fdim(1, snan) = qNaN decimal64_t: fdim(-inf, 1) = 0, fdim(inf, inf) = 0, fdim(inf, qNaN) = nan, fdim(snan, 1) = qNaN, fdim(1, snan) = qNaN decimal128_t: fdim(-inf, 1) = 0, fdim(inf, inf) = 0, fdim(inf, qNaN) = nan, fdim(snan, 1) = qNaN, fdim(1, snan) = qNaN decimal_fast32_t: fdim(-inf, 1) = 0, fdim(inf, inf) = 0, fdim(inf, qNaN) = nan, fdim(snan, 1) = qNaN, fdim(1, snan) = qNaN decimal_fast64_t: fdim(-inf, 1) = 0, fdim(inf, inf) = 0, fdim(inf, qNaN) = nan, fdim(snan, 1) = qNaN, fdim(1, snan) = qNaN decimal_fast128_t: fdim(-inf, 1) = 0, fdim(inf, inf) = 0, fdim(inf, qNaN) = nan, fdim(snan, 1) = qNaN, fdim(1, snan) = qNaN ``` - Compared with Intel libbid `__bid32_fdim`, `__bid64_fdim` and `__bid128_fdim` on 160000 pairs per type in the 5 rounding modes. The pairs include zeros, infinities, quiet and signaling NaNs, subnormals, the largest values, equal values and neighbors. `develop` gives 4060 (`decimal32_t`), 2025 (`decimal64_t`) and 2025 (`decimal128_t`) incorrect values. The fix gives 0. Two differences remain, and both are permitted: - A result can have the same value as libbid with a different exponent. This comes from `operator-`, not from `fdim`. - If `x` is a quiet NaN and `y` is a signaling NaN of the other sign, the fix gives the NaN of `y`, and libbid gives the NaN of `x`. IEEE 754-2019 6.2.3 permits either. - `github_issue_1525` fails on `develop` with 162 errors and passes with the fix. - `github_issue_1525` and `test_cmath` pass with GCC 16.2 and Clang 23.1, in C++14 and C++20, with `-Wall -Wextra -Werror`. The new test also passes with `BOOST_DECIMAL_FAST_MATH`. - With `BOOST_DECIMAL_FAST_MATH`, the code does not change: the removed branch was already disabled in that mode. </details> <details> <summary>Speed</summary> Nanoseconds for each call, best of 3 runs of 1024 pairs, `-O2`, AMD Ryzen 9 3900X, one core. `mix` has random pairs, `gt` has `x > y` and `le` has `x <= y`: | type | inputs | GCC develop | GCC fix | Clang develop | Clang fix | |---|---|---|---|---|---| | `decimal32_t` | mix | 8.7 | 9.3 | 10.8 | 10.8 | | `decimal32_t` | gt | 13.7 | 14.6 | 17.8 | 17.7 | | `decimal32_t` | le | 2.9 | 3.1 | 4.1 | 4.2 | | `decimal64_t` | mix | 11.4 | 11.3 | 13.6 | 13.5 | | `decimal64_t` | gt | 18.1 | 17.9 | 23.1 | 22.7 | | `decimal64_t` | le | 4.5 | 4.4 | 4.9 | 4.9 | | `decimal128_t` | mix | 23.6 | 24.7 | 27.9 | 27.9 | | `decimal128_t` | gt | 40.8 | 42.0 | 46.8 | 46.5 | | `decimal128_t` | le | 8.9 | 9.0 | 12.1 | 12.1 | | `decimal_fast32_t` | mix | 8.7 | 9.0 | 8.1 | 7.6 | | `decimal_fast32_t` | gt | 13.9 | 14.8 | 13.8 | 12.3 | | `decimal_fast32_t` | le | 3.6 | 3.5 | 4.2 | 3.4 | | `decimal_fast64_t` | mix | 10.0 | 10.1 | 10.6 | 9.0 | | `decimal_fast64_t` | gt | 17.8 | 18.0 | 17.4 | 16.7 | | `decimal_fast64_t` | le | 3.6 | 3.2 | 5.0 | 3.6 | | `decimal_fast128_t` | mix | 15.6 | 16.1 | 15.6 | 15.3 | | `decimal_fast128_t` | gt | 26.9 | 26.6 | 31.4 | 30.5 | | `decimal_fast128_t` | le | 7.1 | 7.3 | 3.3 | 3.1 | GCC is up to 7% slower for `decimal32_t` and `decimal_fast32_t`, and Clang is up to 28% faster for the fast types. Intel libbid `__bid64_fdim` takes 18.8 ns for the `mix` pairs on the same machine. </details> <details> <summary>Size</summary> `.text` bytes of one object file with six functions that call `fdim`, one for each type: | compiler | flags | develop | fix | |---|---|---|---| | GCC 16.2 | `-O2` | 8297 | 9329 | | GCC 16.2 | `-Os` | 5349 | 5716 | | Clang 23.1 | `-O2` | 863 | 896 | | Clang 23.1 | `-Os` | 261 | 307 | A NaN now goes to `operator-`, so the code of `operator-` for values that are not finite stays in `fdim`. </details>
|
An automated preview of the documentation is available at https://1527.decimal.prtest3.cppalliance.org/libs/decimal/doc/html/index.html If more commits are pushed to the pull request, the docs will rebuild at the same URL. 2026-10-23 15:37:40 UTC |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.