diff --git a/.github/workflows/build-systems.yml b/.github/workflows/build-systems.yml index 56164c86..80d8cad4 100644 --- a/.github/workflows/build-systems.yml +++ b/.github/workflows/build-systems.yml @@ -10,7 +10,10 @@ # generate first, macOS and Windows having one (.github/scripts/comma-locale.sh, # GAOL v5). The autotools jobs check that make distclean gives the sources back # as git has them. One job builds GAOL as a subproject of a meson project -# (tests/meson_subproject, GAOL v5). +# (tests/meson_subproject, GAOL v5). An autotools job (--disable-exceptions) +# and a meson job (-Denable-exception=false) build GAOL without exceptions, +# where an error ends the program, and run the examples and the tests, which +# leave out the checks of an exception there (GAOL v5). # # Copyright (c) 2026 ENSTA, France # @@ -53,6 +56,7 @@ jobs: - { os: macos-15-intel, configure: '', desc: 'Autotools, macOS 15 x86_64' } - { os: ubuntu-24.04, configure: '--enable-preserve-rounding', desc: 'Autotools, Ubuntu 24.04 x86_64, rounding direction preserved' } - { os: ubuntu-24.04-arm, configure: '--enable-preserve-rounding', desc: 'Autotools, Ubuntu 24.04 arm64, rounding direction preserved' } + - { os: ubuntu-24.04, configure: '--disable-exceptions --with-examples', desc: 'Autotools, Ubuntu 24.04 x86_64, without exceptions' } steps: - uses: actions/checkout@v7 - name: Tools @@ -112,6 +116,9 @@ jobs: run: sh .github/scripts/comma-locale.sh generate - name: Tests (make test) run: make -j 4 test || { cat tests/test-suite.log; exit 1; } + - name: The examples + if: contains(matrix.cfg.configure, '--with-examples') + run: make -j 4 -C examples check || { cat examples/test-suite.log; exit 1; } - name: The test numbers ran under a locale writing a decimal comma run: sh .github/scripts/comma-locale.sh check tests/numbers.log - name: make distclean gives the sources back as git has them @@ -151,6 +158,7 @@ jobs: - { os: macos-15-intel, options: '', desc: 'Meson, macOS 15 x86_64' } - { os: ubuntu-24.04, options: '-Denable-preserve-rounding=true', desc: 'Meson, Ubuntu 24.04 x86_64, rounding direction preserved' } - { os: macos-15, options: '-Denable-preserve-rounding=true', desc: 'Meson, macOS 15 arm64, rounding direction preserved' } + - { os: ubuntu-24.04, options: '-Denable-exception=false -Dwith-examples=true', desc: 'Meson, Ubuntu 24.04 x86_64, without exceptions' } steps: - uses: actions/checkout@v7 - name: Tools @@ -207,6 +215,9 @@ jobs: run: sh .github/scripts/comma-locale.sh generate - name: Tests (ninja test, make test of meson) run: ninja -C build-meson test + - name: The examples + if: contains(matrix.cfg.options, '-Dwith-examples=true') + run: meson test -C build-meson --setup check --suite examples --print-errorlogs - name: The test numbers ran under a locale writing a decimal comma run: sh .github/scripts/comma-locale.sh check build-meson/meson-logs/testlog.txt - name: Tests built with the installed GAOL diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 99750387..bfb75734 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -13,6 +13,10 @@ # checks after them that numbers used it (.github/scripts/comma-locale.sh) # (GAOL v5). # +# The job Ubuntu 24.04 x86_64 GCC also compiles the examples of the manual of +# GAOL v5 that show what they print with the GAOL it installs, and checks that +# they print it (manual/check_examples.py) (GAOL v5). +# # Copyright (c) 2026 ENSTA, France # # Created 2026-09-20 by Jordan NININ @@ -52,7 +56,7 @@ jobs: - { os: ubuntu-26.04, cc: clang, cxx: clang++, desc: 'Ubuntu 26.04 x86_64 Clang' } - { os: ubuntu-26.04-arm, cc: gcc, cxx: g++, desc: 'Ubuntu 26.04 arm64 GCC' } - { os: ubuntu-26.04-arm, cc: clang, cxx: clang++, desc: 'Ubuntu 26.04 arm64 Clang' } - - { os: ubuntu-24.04, cc: gcc, cxx: g++, desc: 'Ubuntu 24.04 x86_64 GCC' } + - { os: ubuntu-24.04, cc: gcc, cxx: g++, manual: true, desc: 'Ubuntu 24.04 x86_64 GCC' } - { os: ubuntu-24.04, cc: clang, cxx: clang++, desc: 'Ubuntu 24.04 x86_64 Clang' } - { os: ubuntu-24.04-arm, cc: gcc, cxx: g++, desc: 'Ubuntu 24.04 arm64 GCC' } - { os: ubuntu-24.04-arm, cc: clang, cxx: clang++, desc: 'Ubuntu 24.04 arm64 Clang' } @@ -112,6 +116,12 @@ jobs: cmake -S tests/find_package -B build-find-package -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=${{ matrix.cfg.cxx }} -DCMAKE_PREFIX_PATH="$PWD/gaol-install" cmake --build build-find-package -j 4 cmake --build build-find-package --target test + - name: The examples of the manual print what it shows + if: matrix.cfg.manual + run: | + # Compiled with the flags of gaol.pc, in C++11, the oldest standard + # GAOL's headers compile in + PKG_CONFIG_PATH="$PWD/gaol-install/lib/pkgconfig" CXX=${{ matrix.cfg.cxx }} python3 manual/check_examples.py -j 4 --std c++11 clang-14-arm64: name: Ubuntu 24.04 arm64 Clang 14, refused diff --git a/doc/continuous-integration.md b/doc/continuous-integration.md index d8821b41..81f6a3b2 100644 --- a/doc/continuous-integration.md +++ b/doc/continuous-integration.md @@ -80,8 +80,11 @@ and Clang) on x64 and GCC 11 on x86, clang-cl without `/fp:strict`. Jobs of each build restore the rounding direction (`GAOL_PRESERVE_ROUNDING`): Ubuntu x86_64 GCC and arm64 Clang, Debian i386 and armhf, macOS arm64, Visual Studio x64, -autotools and meson. The jobs built in Release print the time per operation in -their summary. +autotools and meson. Jobs of autotools and of meson on Ubuntu x86_64 build GAOL +without exceptions (`--disable-exceptions`, `-Denable-exception=false`), +where an error of GAOL ends the program, and run the examples and the tests, +which leave out there the checks of an exception (GAOL v5). The jobs built in +Release print the time per operation in their summary. Two jobs of `linux.yml`, with the GCC and the Clang of Ubuntu 26.04, and one of `macos.yml`, with the AppleClang of macOS 26 arm64, build the library, the @@ -116,7 +119,11 @@ the same code, and once more than 400. The manuals, that of GAOL v5 (`manual/v5/gaol.tex`) and that of GAOL 4 (`manual/v4/gaol.tex`), are built with the LaTeX of Ubuntu 24.04, by the autotools and the meson builds, when `manual/` changes (`manual.yml`); the -PDFs are artifacts of the run. +PDFs are artifacts of the run. The examples of the manual of GAOL v5 that show +what they print are compiled in C++11 with the GAOL that the job Ubuntu 24.04 +x86_64 GCC of `linux.yml` installs, at each run, so that a change of the +library that changes one of their outputs shows, and have to print what the +manual shows (`manual/check_examples.py`, GAOL v5). ## Configurations refused or left out, and why diff --git a/doc/tests.md b/doc/tests.md index 85556238..baedf835 100644 --- a/doc/tests.md +++ b/doc/tests.md @@ -40,9 +40,9 @@ mode there. 2^-968 or beyond the largest double), they have to be those of the binary exponentiation from the lowest bit of n, each rounded in the direction set for it: the SSE2 intervals multiplied from the highest bit, and their bounds - were not those of the FPU intervals (GAOL v5). The operators of an interval with a double have to give, on - bounds and doubles of special values (zeros of both signs, infinities, NaN), - the sets the operators with `interval(d)` give. + were not those of the FPU intervals (GAOL v5). The operators of an interval + with a double have to give, on bounds and doubles of special values (zeros of + both signs, infinities, NaN), the sets the operators with `interval(d)` give. Products of intervals with zero and infinite bounds have to be the hull of the products of the bounds, a zero bound times an infinite one counting as 0. The constructors have to give the empty set for a lower bound of `+oo`, an @@ -59,11 +59,11 @@ mode there. their TwoSum after the direction was set back to upward, where it is no longer exact (GAOL v5). - **`elementary`:** `exp`, `log`, `sin`, `cos`, `tan`, `asin`, `acos`, `atan`, - `sinh`, `cosh`, `tanh`, `asinh`, `acosh`, `atanh`, `sqrt` and `pow` at doubles, - at intervals, and at intervals whose images are known exactly (extrema, - poles, domains, `log` of intervals holding no positive number and `atanh` of - intervals with no point of (-1, 1) being empty, `exp(0)`, `log(1)` and `1^y` - exact). The functions have to be the tightest + `sinh`, `cosh`, `tanh`, `asinh`, `acosh`, `atanh`, `sqrt` and + `pow` at doubles, at intervals, and at intervals whose images + are known exactly (extrema, poles, domains, `log` of intervals holding no + positive number and `atanh` of intervals with no point of (-1, 1) being empty, + `exp(0)`, `log(1)` and `1^y` exact). The functions have to be the tightest enclosures where their value is 0, 1, ±π/4, ±π/2 or π (`sin(0)`, `cos(0)`, `acos(1)`, `acos(-1)`, `asin(1)`, `atan(1)`, `atan([-oo, +oo])`, `acosh(1)`...), and `cosh`, `sinh` and `tanh` beyond the largest double and @@ -72,9 +72,10 @@ mode there. exponents in every position about the base 1 and the exponent 0, and the power itself as lower bound where it is a double: `pow([4], 0.5)` is `[2]`. `atan2` has to be within one double of the tightest bounds at - points of the four quadrants and over 324 boxes in every position about the - axes, and the tightest over the boxes with infinite bounds or on an axis, - `[-pi, pi]` across the half-line y = 0, x < 0, and empty at (0, 0). sin, cos and tan have to be within one + points of the four quadrants and over 324 boxes in every position + about the axes, and the tightest over the boxes with infinite + bounds or on an axis, `[-pi, pi]` across the half-line y = 0, + x < 0, and empty at (0, 0). sin, cos and tan have to be within one double of the tightest bounds at every magnitude, `sin([1e-10])` and `cos([2^60])` included, and over 649 intervals next to their extrema and poles, of width about π and 2π, and of consecutive doubles up to the largest, @@ -97,8 +98,8 @@ mode there. the whole numbers and the doubles on either side of them, and over the magnitudes beyond 2^52, on both signs and on the empty set. The values are in `elementary_values.h`, which `elementary_values.py` generates. -- **`rounding_direction`:** about 110 operations of GAOL's interface, - called with the rounding direction upward, to nearest, downward and toward zero (and +- **`rounding_direction`:** about 110 operations of GAOL's interface, called + with the rounding direction upward, to nearest, downward and toward zero (and on x86, with the x87 and SSE directions differing), have to give the results they give when called rounding upward, and leave the rounding direction upward, or as they found it with `GAOL_PRESERVE_ROUNDING`. Products and sums @@ -110,9 +111,9 @@ mode there. of glibc reads the x87 unit, and the upper bound was below the exact value (GAOL v5). `cbrt`, `pow` and `atan2` have to leave the exception masks of the SSE control register as they found them, and an empty interval to be told - empty after them: the `fesetexceptflag()` of mingw-w64 for 32-bit Windows - unmasked the exceptions, and the comparison of the NaN bounds of an empty - interval then killed the program (GAOL v5). + empty after them: the `fesetexceptflag()` of mingw-w64 for 32-bit + Windows unmasked the exceptions, and the comparison of the NaN + bounds of an empty interval then killed the program (GAOL v5). `is_empty()` has to be true for six empty sets (`interval::emptyset()`, `[3, 2]`, `sqrt([-2, -1])`, `log([-2, -1])` and the two orders of `[1, 2] & [3, 4]`) and false for three nonempty intervals, and to raise no @@ -129,9 +130,10 @@ mode there. alone, which GCC for 32-bit ARM and for POWER9 made signaling when `is_empty()` was one quiet comparison, and which jobs of the continuous integration also check built for size, where GCC calls the intersection - rather than inlining it: armhf, and POWER9 under qemu) have to give the result of the empty set and raise no - invalid-operation flag, and, with glibc, not die in a child process that - enabled the exception (GAOL v5): 45 of the 48 died with the SSE2 intervals, + rather than inlining it: armhf, and POWER9 under qemu) have to + give the result of the empty set and raise no invalid-operation + flag, and, with glibc, not die in a child process that enabled + the exception (GAOL v5): 45 of the 48 died with the SSE2 intervals, and all 48 with the FPU ones (`x & y` for an empty `y`, `sqrt`, `exp`, `min`, `max`, `floor`, `set_contains()`, the output...). So do (GAOL v5, point D.24 of `TODO.md`) 18 operations given a NaN double, the empty set @@ -143,16 +145,16 @@ mode there. K and J with bounds in {-oo, -2, -0, 0, 4, +oo}, which has to raise no division-by-zero flag either, nor die with both exceptions enabled, and where J does not contain 0, give the extrema of the quotients of the bounds - (the SSE2 intervals divided +oo by +oo, or a bound by a zero one, in the - half of a register they did not keep); 13 powers with an + (the SSE2 intervals divided +oo by +oo, or a bound by a zero + one, in the half of a register they did not keep); 13 powers with an exponent of extreme magnitude, which have to be the tightest enclosures (CORE-MATH's `pow` compares a NaN it makes on purpose there); and 29 loops of 64 relations, constructions, intersections or `floor()`, with every third x and every fifth y empty, which a compiler vectorizes or if-converts (with the FPU intervals, GCC 9.4 and 13 at `-O3` with `-mfma`, GCC 13 to 15 for 32-bit x86, GCC 14 for POWER8 and GCC 11 to 15 for 64-bit ARM made some - of them raise the exception, and with the SSE2 intervals the construction of an - interval), compared with each relation computed on its own. The midpoints + of them raise the exception, and with the SSE2 intervals the construction of + an interval), compared with each relation computed on its own. The midpoints of five intervals with a bound of 2^1023 or more in magnitude, `[DBL_MAX]` among them, (`midpoint()`, `mid()`, `rad()`, `mid_rad()`, `split()`...) have to be right, `mid()` the tightest enclosure of the exact midpoint, and @@ -203,17 +205,17 @@ mode there. it upward before `main()`: GAOL has to initialize itself before. `cancel_minus([DBL_MAX], [1])` has to give the hull of `DBL_MAX` and raise no invalid-operation flag, nor die in a child process that enabled the - exception: the terms of its TwoSum, computed by GCC after the direction - was set back to upward, read +oo and made inf - inf. + exception: the terms of its TwoSum, computed by GCC after the + direction was set back to upward, read +oo and made inf - inf. The suite takes the AVX-512 path of `GAOL_PREFER_AVX512` whole, when a processor has the instructions: the bounds of `x + y`, `x - y`, `x*y`, `x/y` and `sqrt(x)` are then those of the SSE2 path, bit for bit, and these operations leave the rounding direction as the program set it, checked where the SSE2 path checks it left upward, and clear the - flush-to-zero modes when a plug-in has set them (GAOL v5). After `cleanup()` and - an operation of GAOL, `gaol::restore_rounding()` has to set the direction - to nearest again, as many times as it is called, where `cleanup()` does - so at its first call only, and to do nothing with + flush-to-zero modes when a plug-in has set them (GAOL v5). After + `cleanup()` and an operation of GAOL, `gaol::restore_rounding()` + has to set the direction to nearest again, as many times as it is called, + where `cleanup()` does so at its first call only, and to do nothing with `GAOL_PRESERVE_ROUNDING`, the operations restoring the direction themselves (GAOL v5). - **`fast_math_link`:** a program linked with `-ffast-math` (its link only, with @@ -249,8 +251,8 @@ mode there. enclosing the number read, and the number itself when it is a double. The constants have to be the tightest enclosures of π, 2π and π/2. No `const char*` nor `nullptr` may convert to an interval, and `interval(0)` has - to be `[0, 0]`: the constructor from one string, which made it ambiguous, is - gone (GAOL v5). The literals of IEEE 1788-2015 + to be `[0, 0]`: the constructor from one string, which made it + ambiguous, is gone (GAOL v5). The literals of IEEE 1788-2015 (`[entire]`, `[ ]`, `[1,]`, `3.56?1`, hexadecimal numbers...) have to be read as the tightest intervals enclosing them, whatever the case of their letters, and `[inf]` and the like as the empty set. Expressions read again and again @@ -267,8 +269,8 @@ mode there. only for bounds finite, not 0, of the same sign, with as many digits before the point and the same exponent, that share their first digit that is not 0, and the bounds otherwise: `[1, 10]`, `[1, 2]`, `[0]` for a zero, - `-1.25~[67, 0]` (GAOL v5: `1~[., 0.]`, `~[1., 2.]`, `~[-0., 0.]`, and every - negative interval with its bounds). The text of a point + `-1.25~[67, 0]` (GAOL v5: `1~[., 0.]`, `~[1., 2.]`, `~[-0., 0.]`, + and every negative interval with its bounds). The text of a point interval, written with 1 to 25 digits in each of these formats, has to be read back as an interval enclosing it: the literal `[a]` of IEEE 1788-2015 is written for a number that is the point itself, and read back as the @@ -277,8 +279,8 @@ mode there. (`[0.1, 0.1000000000000001]` for `interval(0.1)`); the largest doubles and the subnormals, written with all their digits (309 digits, 1074 after the point), are written `[a]` and read back as the point where the C++ library - writes them exactly. GAOL wrote `` for every point interval, and the - reader refused most of them (GAOL v5). In hexadecimal, the + writes them exactly. GAOL wrote `` for every point interval, + and the reader refused most of them (GAOL v5). In hexadecimal, the bounds have to be written in the hexadecimal-significand form of IEEE 1788-2015 (13.4.1) and read back as the same doubles, which is the recovery requirement of 13.4: over random intervals, and over the empty @@ -286,13 +288,13 @@ mode there. doubles and point intervals, written `[a]` as in decimal, and `[0x0p+0]` for a zero whatever the signs of its bounds, read back as the same set (GAOL v5: `[0x1.8p+0, 0x1.8p+0]`, and `[-0x0p+0, 0x0p+0]` for - `interval::zero()` with the SSE2 intervals). - `operator<<` has to leave the precision of the stream as it was, and - `std::setw` to pad the whole interval, adjusted to the right, to the left + `interval::zero()` with the SSE2 intervals). `operator<<` has + to leave the precision of the stream as it was, and `std::setw` + to pad the whole interval, adjusted to the right, to the left or inside (with `std::internal`, the fill follows the sign of the midpoint in the width and center formats, and that of the shared digits in the - agreeing format), and after `0x` in the hexadecimal floating-point format - of the stream. The texts written under the flags of the + agreeing format), and after `0x` in the hexadecimal floating-point + format of the stream. The texts written under the flags of the stream (`showpoint`, `showpos`, `uppercase`, `fixed`, `scientific`), its fill and a locale of its own (a decimal colon, digits grouped by three, or a decimal comma) have to be those `operator<<` wrote in a stream of its @@ -303,8 +305,8 @@ mode there. radius with the grouping of the locale, as the midpoint, and without a sign under `showpos` (it wrote `+2 (+/- +1)`), and a midpoint 0 as `0` (it wrote `-0` for [-2u, u]): 33 cases of four formats, over zeros, - infinities, the empty set and point intervals (GAOL v5). - The width and center formats have to write the `midpoint()` and the `rad()` + infinities, the empty set and point intervals (GAOL v5). The + width and center formats have to write the `midpoint()` and the `rad()` of IEEE 1788-2015: with every precision and flag, over the special values, the subnormals, the largest doubles, unbounded intervals and random intervals, the radius written has to be `rad()` rounded upward, less than @@ -332,17 +334,17 @@ mode there. is flushed before the first character is read, as for a number, so that a prompt shows before the user types (`std::ws` alone does none of this). Numbers with a million zeros after their point and an exponent of 7 digits - (`0.00…01e1000001`, and in hexadecimal and in the uncertain form) have to be - read exactly: the exponent was cut at 100000 (GAOL v5). Under a locale - writing a decimal comma, where one is installed + (`0.00…01e1000001`, and in hexadecimal and in the uncertain form) + have to be read exactly: the exponent was cut at 100000 (GAOL v5). + Under a locale writing a decimal comma, where one is installed (`fr_FR.UTF-8`, `de_DE.UTF-8`, `French_France.1252`...), numbers have to be read as in the C locale, and `exact_string()` has to write points and read back bit for bit: the reading never ended there, and the test, which ctest would otherwise let run with no limit, fails after 5 minutes should it hang again (GAOL v5), in `tests/find_package`, `tests/fetch_content` and `tests/meson_subproject` too; the cache variable `GAOL_NUMBERS_TIMEOUT` - gives it more time on a slower build - (600 s with GCC and the sanitizers on macOS x86_64). Numbers of 5000 to 20000 characters, in decimal, in + gives it more time on a slower build (600 s with GCC and the sanitizers on + macOS x86_64). Numbers of 5000 to 20000 characters, in decimal, in hexadecimal and in the uncertain form, have to be read as the tightest enclosures, known without reading them (`1.5` followed by zeros is 1.5, and followed by zeros and a 1 is between 1.5 and the next double...), under the C @@ -350,13 +352,12 @@ mode there. locale, the number of 20000 characters has to take at most 10 times the time it takes under the C locale, and 50 ms more: the reader read the text again for each of its 125 comparisons or so, in a time quadratic in its - length, and took 50 times as long (GAOL v5). - The Ubuntu runners of the continuous integration have no - such locale: its Linux jobs generate `fr_FR.UTF-8` for the test, and fail if - it did not check under it (see - [Continuous integration](continuous-integration.md)). - With flush-to-zero, denormals-are-zero or both set in MXCSR - (x86 only, and where the processor honours them), the numbers from 0 to the + length, and took 50 times as long (GAOL v5). The Ubuntu runners + of the continuous integration have no such locale: its Linux + jobs generate `fr_FR.UTF-8` for the test, and fail if it did + not check under it (see [Continuous integration](continuous-integration.md)). + With flush-to-zero, denormals-are-zero or both set in MXCSR (x86 + only, and where the processor honours them), the numbers from 0 to the least normal double, in decimal and in hexadecimal, alone, in intervals and in the uncertain form, have to be read as the tightest intervals enclosing them, and the doubles among them, 0 included, as themselves: the reader @@ -372,17 +373,20 @@ mode there. macOS), and writes 0: nothing is checked where the C library does not write the bounds of the test under the mode as it does without it. - **`other_functions`:** midpoints (of subnormal bounds, and of `intervalf` - where a developer of GAOL compiles the float intervals, `gaol/gaol_config.h`), widths, radii (`rad()`, `mid_rad()`), magnitudes, mignitudes, Hausdorff - distances (of intervals with infinite bounds too, equal bounds being at - distance 0), `nb_fp_numbers()` (across the two zeros), splitting, integer parts, the comparisons of IEEE 1788-2015 + where a developer of GAOL compiles the float intervals, `gaol/gaol_config.h`), + widths, radii (`rad()`, `mid_rad()`), magnitudes, mignitudes, + Hausdorff distances (of intervals with infinite bounds too, equal + bounds being at distance 0), `nb_fp_numbers()` (across the two + zeros), splitting, integer parts, the comparisons of IEEE 1788-2015 (`precedes`, `interior`, `subset`, `equal`, `disjoint`, from Tables 10.3 and 10.4, on intervals of zero, infinite and small bounds and the empty set), and the relational functions (`sqrt_rel`, `div_rel`...): `acos_rel`, `asin_rel` and `atan_rel` have to keep their value within 6 doubles from 1 to 2^50, and decide an interval of a single double beyond 2^53. `less`, - `strictly_less`, `is_entire` and `is_common_interval` have to give the values - of Tables 10.3 and 10.4, and `==` and `!=` must not compile on intervals, - `certainly_neq()` and the possibly relations being gone. Each name of `gaol_ieee1788` has to be the operation + `strictly_less`, `is_entire` and `is_common_interval` have to + give the values of Tables 10.3 and 10.4, and `==` and `!=` must + not compile on intervals, `certainly_neq()` and the possibly + relations being gone. Each name of `gaol_ieee1788` has to be the operation of the standard it names, which a wrong translation would not show at compilation: the eight comparisons against the bounds of Table 10.3 and the empty cases of Table 10.4, over 20 000 pairs; `inf`, `sup` and the numeric @@ -392,9 +396,9 @@ mode there. base, and at integer exponents beyond the ints, where it has to give the tightest bounds and GAOL's own `pow` gives [-oo, +oo] (GAOL v5). - **`ieee1788`:** `gaol_ieee1788` as a program uses it, under - `using namespace gaol_ieee1788;` alone. Every name of the standard it - provides is called unqualified, which compiles only if none of them is - ambiguous with a function of `gaol_core`. `pow` has to be the standard's with an interval, an + `using namespace gaol_ieee1788;` alone. Every name of the standard it provides + is called unqualified, which compiles only if none of them is ambiguous with + a function of `gaol_core`. `pow` has to be the standard's with an interval, an `int` or a `double` exponent: on a negative base, at `[0]`, and at infinite, NaN and beyond-the-ints exponents; `pown` and `gaol::pow` the integer power, for an exponent of any integer type too (GAOL v5): `pown([2], 3000000000u)` @@ -406,9 +410,8 @@ mode there. the unsigned ints the tightest enclosures of the roots, compared with doubles computed by mpmath, `rootn(x, 3000000000u)`, which was wrong, and `pownRev(c, 0L)`, which throws; `pown(e, 3000000000u)` of an expression, - [-oo, +oo]; `numsToInterval()` and `isMember()` - take ±(2^53 + 1) as the integer it is, and bounds in the wrong order give the - empty set; + [-oo, +oo]; `numsToInterval()` and `isMember()` take ±(2^53 + 1) + as the integer it is, and bounds in the wrong order give the empty set; the bounds of `gaol_ieee1788::pow` and of `gaol::pow` on 92 boxes, each reaching a branch of the pow of Table 9.1, which the two share, or of what `gaol::pow` adds to it (the integer power, [-oo, +oo] beyond the ints): @@ -416,14 +419,14 @@ mode there. own copy of the pow, each an enclosure of the exact power within one double of the tightest bounds, and the same with the exponent given as a double; and the expressions `pow(e1, e2)` and `pown(e, n)`, evaluated, the - standard's too. GAOL's functions on intervals and on an interval and a - number; the functions of C on numbers, by `static_assert`. + standard's too. GAOL's functions on intervals and on an interval + and a number; the functions of C on numbers, by `static_assert`. `intervalToExact()` has to be `exact_string()`, read back bit for bit, and to leave the global output format alone; the check of it by a second - thread writing intervals meanwhile is commented out, the tests running no - thread. `textToInterval(intervalToText(x))` has to contain x for a point - interval x, `interval(0.1)` first: it was the empty set (GAOL v5); and to be - x itself for a text `[a]`. + thread writing intervals meanwhile is commented out, the tests + running no thread. `textToInterval(intervalToText(x))` has to + contain x for a point interval x, `interval(0.1)` first: it was + the empty set (GAOL v5); and to be x itself for a text `[a]`. `intervalToText(x)` has to be an interval literal of the standard, `[l, r]`, `[a]` (`[4]`, `[0]`) or `[empty]`, with the digits of the precision of the intervals, whatever the global format and the locale: in each of the five @@ -439,10 +442,9 @@ mode there. format with no digit and the showpoint flag, and with 1074 digits) has to be read back by `gaol::textToInterval` as an interval containing the point, and as the point itself when it is one number (GAOL v5: the literal - `[a]` wrote `[-2,5]`, read as `[-2, 5]`, `[12,5]`, read as the empty set, - and `[0,]`, read as `[0, +oo]`, then a point with its two bounds, - `[-2,5, -2,5]`, which the reader refused). - `textToInterval` has to read each name of + `[a]` wrote `[-2,5]`, read as `[-2, 5]`, `[12,5]`, read as the empty set, and + `[0,]`, read as `[0, +oo]`, then a point with its two bounds, `[-2,5, -2,5]`, + which the reader refused). `textToInterval` has to read each name of Tables 9.1 and 10.5 as the function of that name, in any case of letters, `pow([-4,-1],2)` being the empty set, and to give the empty set for the names of GAOL alone (`nth_root`, `cbrt`, `log1p`...) and the calls that are @@ -487,10 +489,10 @@ mode there. subnormal x (see `3rd/README.md` and `gaol/core_math_port.h`). Among the pairs is 8 to the double nearest 1/3, whose product 3y rounds to 1 without being 1. With flush-to-zero set (x86), the lower bound of - `pow([0.5], [2^-1074])` has to stay below 1, the product 1024y being flushed - to 0. The functions of - Table 10.5 GAOL provides have to be the tightest enclosures over intervals - too: the hull of their image, computed from the values at the bounds of the + `pow([0.5], [2^-1074])` has to stay below 1, the product 1024y + being flushed to 0. The functions of Table 10.5 GAOL provides + have to be the tightest enclosures over intervals too: the hull + of their image, computed from the values at the bounds of the part of the interval in the domain (`expm1`, `exp2m1`, `exp10m1`, `log1p`, `log2p1`, `log10p1`, `rsqrt`, `atanpi`, `asinpi`, `acospi`), at the points of the box nearest to the origin and farthest from it (`hypot`), at the @@ -553,10 +555,10 @@ mode there. too, `pow(e, 3)` and `/=` included, which did not link, and `expression(n)` of 2^53 + 1, the interval of the two doubles around it, and `expression(7)`, the node of the double 7, written `7`, and `pow(e, n)` and `nth_root(e, n)` - with a `long` n, an unsigned beyond the ints, a negative int and orders - beyond the unsigned ints, of positive, negative and mixed-sign expressions - (GAOL v5), and have to be - printed as written, `x/(y*z)` with its `/` and `(-2)^2` with its parentheses (GAOL v5). + with a `long` n, an unsigned beyond the ints, a negative int + and orders beyond the unsigned ints, of positive, negative and + mixed-sign expressions (GAOL v5), and have to be printed as written, + `x/(y*z)` with its `/` and `(-2)^2` with its parentheses (GAOL v5). Every function of GAOL has to be read under its name, those the reader did not know included (`exp10`, `hypot`, `sinpi`, `fma`...), the names of IEEE 1788-2015 alone (`pown`, `rootn`, `recip`...) and the calls with a wrong @@ -569,13 +571,12 @@ mode there. explanation, without one, with a null pointer (no explanation) and with an explanation that starts with a NUL, and of what the reader, `operator>>` and `nb_fp_numbers()` throw, read through a `std::exception`; and `operator<<` has - to write the explanation once. `what()` was `std::exception`, which a handler of - `std::exception` printed and an exception that nothing catches ended the + to write the explanation once. `what()` was `std::exception`, which a handler + of `std::exception` printed and an exception that nothing catches ended the program with, and `operator<<` wrote it next to the explanation (GAOL v5). - The reading of - strings by four threads at once, where the reader, whose state was global, - crashed before its lexer became reentrant and its parser pure, is commented - out: the tests run no thread + The reading of strings by four threads at once, where the reader, + whose state was global, crashed before its lexer became reentrant + and its parser pure, is commented out: the tests run no thread (GAOL v5). Each value is compared with the same computation written in C++, which the other tests check against the exact results: what is tested here is the lexer, the parser and the evaluation, not the operations. An empty @@ -587,9 +588,9 @@ mode there. (`expr_node::references()` does not change as they are built, copied, assigned and extended): the count, changed by several threads at once, came down to 0 and the node was deleted twice (GAOL v5). -- **`u128`:** the accurate phases of CORE-MATH's `log`, `sin`, `cos`, `tan`, - `atan2` and `pow`, and of `log10` and seven functions of Table 10.5, compute - with a 128-bit unsigned integer, which Visual C++ +- **`u128`:** the accurate phases of CORE-MATH's `log`, `sin`, + `cos`, `tan`, `atan2` and `pow`, and of `log10` and seven functions + of Table 10.5, compute with a 128-bit unsigned integer, which Visual C++ has on no architecture and GCC has on no 32-bit target; there GAOL computes with the two 64-bit halves of `gaol/gaol_u128.h`. The test compiles those halves (`GAOL_U128_FORCE_EMULATION`) and compares every operation with the @@ -685,18 +686,22 @@ mode there. program including ``, is compiled with `-ffinite-math-only` and with `-ffast-math`, put after the flags of interval arithmetic, and the compilation has to fail with the message of `gaol/gaol_config.h`, which the - test looks for in the output of the compiler; compiled with neither, it has - to compile. The CMake build makes each compilation a target of its own, - built by the test; `tests/refused_options.sh` runs the three compilations in - the autotools and meson builds, with the compiler and the flags of the code - using GAOL. With `-ffinite-math-only` the compiler takes NaN and infinities - never to occur, in the inline functions of the headers too, and the empty - interval, whose bounds are NaN, is no longer told empty: - `([1, 2] & [3, 4]).is_empty()` was false with GCC 9.4 and 13 at `-O0`, `-O2` - and `-O3`, and with Clang 18 at `-O0`, and at `-O2` and `-O3` when the bounds - are `volatile`. The header did not refuse `-ffinite-math-only`, nor `-Ofast` - or `-ffast-math` followed by `-frounding-math` with Clang, which leave - `__FAST_MATH__` undefined and `__FINITE_MATH_ONLY__` at 1 (GAOL v5). + test looks for in the output of the compiler; compiled with neither, it has to + compile. The CMake build makes each compilation a target of its own, built by + the test; where the compilation has to fail, the target compiles a copy of the + source in the build tree, which `tests/compile_error.cmake` copies again and + touches before the build: an object compiled once stayed up to date, and the + test failed until it was removed, after a header or the source was restored + with its former modification time (GAOL v5). `tests/refused_options.sh` runs + the three compilations in the autotools and meson builds, with the compiler + and the flags of the code using GAOL. With `-ffinite-math-only` the compiler + takes NaN and infinities never to occur, in the inline functions of the + headers too, and the empty interval, whose bounds are NaN, is no longer told + empty: `([1, 2] & [3, 4]).is_empty()` was false with GCC 9.4 and 13 at `-O0`, + `-O2` and `-O3`, and with Clang 18 at `-O0`, and at `-O2` and `-O3` when the + bounds are `volatile`. The header did not refuse `-ffinite-math-only`, nor + `-Ofast` or `-ffast-math` followed by `-frounding-math` with Clang, which + leave `__FAST_MATH__` undefined and `__FINITE_MATH_ONLY__` at 1 (GAOL v5). `tests/fp_strict` is the check of Visual C++ without `/fp:strict`. - **`cpack_stale_configure`** (CMake build, on a Unix system that builds for itself, where the tree has a `configure`): a script, not a program @@ -748,7 +753,11 @@ mode there. process identifier (`srand48(getpid())`, which Visual C++ does not have), so that its checks are the same at each run, and checks the infinite ends of `atanh_rel(J, I)` where J ends at 1 or -1 (`atanh_rel([0.5, 1], [0, +oo])`), - which a bounded I hides (GAOL v5). `intervalf` and `interval2f` test + which a bounded I hides (GAOL v5). `input_output` checks that the + reader refuses `<-inf, -inf>` and ``, whose bounds are not + one value (`-inf` reads [-oo, -MAX]), where the check of GAOL 4 expected + an interval: the exception went to a `try` around the checks, which + skipped the six after it (GAOL v5). `intervalf` and `interval2f` test the intervals of floats where a developer of GAOL compiles them (`GAOL_FLOAT_INTERVALS`, see `gaol/gaol_config.h`), and are skipped otherwise (exit status 77). `constructor`, `assignment`, `arithmetic_operators` and @@ -758,32 +767,30 @@ mode there. order that lie between the same two doubles (`interval(2^53 + 1, 2^53)`), `=`, `&=` (empty for `[0, 2^53] &= 2^53 + 1`) and `|=`, each arithmetic operator in both orders, and the relations, `set_contains()` and - `set_strictly_contains()` at 2^53 + 1 and 2^64 − 1, which the conversion to - a double made wrong; and that an integer that is a double gives what the - double gives. `relations` also checks `gaol::lexicographic_less` - (GAOL v5, point V): a strict total order that puts the empty set first and - the intervals by their bounds, which `<` is not, being the `strictPrecedes` - of IEEE 1788-2015, true as soon as either interval is empty; it sorts a - `std::vector` holding an empty interval, which `<` reads past the end of, - and fills a `std::set` with intervals that overlap, which `<` loses. - `misc` declares a `namespace detail` of its own, which - `using namespace gaol` made ambiguous while GAOL's helpers were in - `gaol_core::detail`, and checks the names of `gaol`, which names those of - `gaol_core` one by one (GAOL v5). `static_assert` checks that `gaol::f` + `set_strictly_contains()` at 2^53 + 1 and 2^64 − 1, which the conversion to a + double made wrong; and that an integer that is a double gives what the double + gives. `relations` also checks `gaol::lexicographic_less` (GAOL v5, point V): + a strict total order that puts the empty set first and the intervals by their + bounds, which `<` is not, being the `strictPrecedes` of IEEE 1788-2015, true + as soon as either interval is empty; it sorts a `std::vector` holding an + empty interval, which `<` reads past the end of, and fills a `std::set` with + intervals that overlap, which `<` loses. `misc` declares a `namespace detail` + of its own, which `using namespace gaol` made ambiguous while GAOL's helpers + were in `gaol_core::detail`, and checks the names of `gaol`, which names those + of `gaol_core` one by one (GAOL v5). `static_assert` checks that `gaol::f` compiles, a class being that of `gaol_core` and a function returning the type expected, for the names of GAOL 4 that Codac and IBEX use - (`gaol::operator+`, `gaol::operator|`, `gaol::round_upward`, - `gaol::nth_root`, `gaol::div_rel`, `gaol::init`...), the other classes of - GAOL 4, the functions on intervals GAOL v5 adds and the nodes of their - expressions, `restore_rounding()`, `exact_string()`, `rnd_keep()` and - `NaN_val`, and + (`gaol::operator+`, `gaol::operator|`, `gaol::round_upward`, `gaol::nth_root`, + `gaol::div_rel`, `gaol::init`...), the other classes of GAOL 4, the + functions on intervals GAOL v5 adds and the nodes of their expressions, + `restore_rounding()`, `exact_string()`, `rnd_keep()` and `NaN_val`, and the overloads of every function and operator of the expressions; that what GAOL uses for itself is not found through `using namespace gaol`: the functions (`get_rounding()`, `get_flush_modes()`, `gaol_pown()`...), their name between parentheses so that argument-dependent lookup does not find - them in `gaol_core`, lose to fallbacks that any function of GAOL beats, and - the types and the variables (`rounding_state`, `uintdouble`, `the_null_expr`, - the internals GAOL 4 had in `gaol` too...) to fallbacks of + them in `gaol_core`, lose to fallbacks that any function of GAOL + beats, and the types and the variables (`rounding_state`, `uintdouble`, + `the_null_expr`, the internals GAOL 4 had in `gaol` too...) to fallbacks of the same name, with which they would be ambiguous. The test did not compile while `gaol` took `gaol_core` whole. A call `sin(x)` on an interval still finds the function of `gaol_core` by argument-dependent lookup. @@ -800,29 +807,32 @@ mode there. `-Werror=attributes` (`/we4834`, `/we5030` and `/we5051` with Visual C++): throwing away the result of `sqrt(x)` has to fail the compilation with the warning of the compiler, which the test looks for in the output of the - build, and using it has to compile, GAOL's headers throwing no result away, - nor carrying an attribute the compiler ignores where it is written. - `GAOL_NODISCARD` was empty before C++17, so that a CMake project with GCC 9, - which compiles in C++14 unless it says otherwise, got no warning for - `sqrt(x);`; it is now `[[nodiscard]]` there with GCC 7 and later and Visual - C++ 2019 16.4 and later, and the attribute of Clang with Clang (see - [Using GAOL](using.md#a-result-thrown-away)). The autotools and meson - builds have no such test, the header being the same. + build (of a copy of the source, as `refused_fast_math`), and using it has to + compile, GAOL's headers throwing no result away, nor carrying an attribute the + compiler ignores where it is written. `GAOL_NODISCARD` was empty before C++17, + so that a CMake project with GCC 9, which compiles in C++14 unless it says + otherwise, got no warning for `sqrt(x);`; it is now `[[nodiscard]]` there with + GCC 7 and later and Visual C++ 2019 16.4 and later, and the attribute of Clang + with Clang (see [Using GAOL](using.md#a-result-thrown-away)). The autotools + and meson builds have no such test, the header being the same. The three builds compile them with `WITH_TESTS` (CMake), `--with-tests` (configure) and `with-tests` (meson), all off by default, and run them with `make test` (`meson test`, or `ninja test`); `make check` (`ninja check`) runs the examples of `examples/` too, where they are built (see -[Building GAOL](building.md#tests-examples-performance-and-the-parser)). The -continuous integration runs `make test` in every job. `tests/find_package` +[Building GAOL](building.md#tests-examples-performance-and-the-parser)). Built +without exceptions (configure `--disable-exceptions`, meson +`-Denable-exception=false`), where an error of GAOL ends the program, the tests +leave out the checks of an exception and of a text the reader refuses (GAOL v5). +The continuous integration runs `make test` in every job. `tests/find_package` builds some of the same tests with an installed GAOL, and `.github/scripts/tests.sh` with a GAOL installed by configure or meson. `tests/fetch_content` builds them with a GAOL brought in by FetchContent, and `tests/meson_subproject` with a GAOL brought in as a meson subproject (GAOL v5), which meson refused. Its test `flags` checks that `gaol_dep` gives their -compilation the flags of the `Cflags` of `gaol.pc`, without which the tests -pass all the same, and their link the options of its `Libs` (`-mno-daz-ftz`, -where the compiler takes it), and its test `release` that GAOL is built in release in +compilation the flags of the `Cflags` of `gaol.pc`, without which the tests pass +all the same, and their link the options of its `Libs` (`-mno-daz-ftz`, where +the compiler takes it), and its test `release` that GAOL is built in release in the default build type of meson, debug: `tests/release_flags.py` checks that the sources of GAOL and CORE-MATH are compiled with `-O3` and `NDEBUG`, and that `GAOL_DEBUGGING` is left undefined. The continuous integration runs it on @@ -856,6 +866,14 @@ catch, but for the 7 that give the same bounds, and runs `diffpow` on the errors no test catches. `bench.cpp` measures the time per call of each path of `pow`. +`manual/check_examples.py` compiles each example of the manual of GAOL v5 +(`manual/v5/gaol.tex`) that shows what it prints, 88 of them, with an installed +GAOL and the flags of `pkg-config gaol`, runs it, and compares what it prints +with the manual, line by line (GAOL v5): until #55, the examples printed 1 and +0 where the manual showed `true` and `false`. It is no test of the builds; the +job Ubuntu 24.04 x86_64 GCC of `linux.yml` runs it, in C++11, on the GAOL it +installs (see [Continuous integration](continuous-integration.md)). + What they show of GAOL, beyond the fixes of [What differs from GAOL](differences.md): diff --git a/examples/15_text_and_ieee1788.cpp b/examples/15_text_and_ieee1788.cpp index 393d7a06..35446824 100644 --- a/examples/15_text_and_ieee1788.cpp +++ b/examples/15_text_and_ieee1788.cpp @@ -11,7 +11,10 @@ * that no double holds. A malformed text throws gaol::input_format_error, * where gaol_ieee1788::textToInterval() returns the empty set. A file of * intervals is read line by line, each line in a try block, so that one bad - * line is reported and skipped. Written in decimal, the bounds are rounded + * line is reported and skipped. Where GAOL is built without exceptions + * (configure --disable-exceptions, meson -Denable-exception=false), its + * reader ends the program on such a text, and sections 1.2 and 1.3 read with + * gaol_ieee1788::textToInterval(). Written in decimal, the bounds are rounded * outward, so that the text still encloses the interval; the hexadecimal * text of exact_string() reads back bit for bit. Part 2 writes one step of a * contractor (the HC4-revise of 2 x^2 in c over x, as IBEX's and Codac's @@ -167,6 +170,7 @@ namespace { signals UndefinedOperation then, and GAOL has neither this signal nor the decorations: a malformed text cannot be told from "[empty]". */ std::cout << "1.2 A malformed text\n"; +#if GAOL_EXCEPTIONS_ENABLED bool thrown = false; try { const interval x = gaol::textToInterval("[1, 2"); @@ -176,6 +180,10 @@ namespace { std::cout << " \"[1, 2\": input_format_error: " << e.explanation() << '\n'; } check(thrown, "\"[1, 2\" throws input_format_error"); +#else + // Without exceptions, gaol::textToInterval("[1, 2") ends the program + std::cout << " GAOL built without exceptions: gaol::textToInterval would end the program\n"; +#endif show("textToInterval(\"[1, 2\")", gaol_ieee1788::textToInterval("[1, 2"), "of gaol_ieee1788: nothing thrown", gaol_ieee1788::textToInterval("[1, 2").is_empty()); } @@ -208,6 +216,7 @@ namespace { continue; } const std::string where = "line " + std::to_string(number) + ": " + line; +#if GAOL_EXCEPTIONS_ENABLED try { const interval m = gaol::textToInterval(line); // Each measurement holds R: so does their intersection @@ -218,6 +227,21 @@ namespace { ++errors; std::cout << " " << std::left << std::setw(29) << where << "skipped: " << e.explanation() << '\n'; } +#else + /* Without exceptions, gaol::textToInterval would end the program on + the bad line: the reader of gaol_ieee1788 returns the empty set for + a text that is no interval. It still ends the program on a call it + refuses, as pown([2, 5], 2.5) (issue #98). */ + const interval m = gaol_ieee1788::textToInterval(line); + if (m.is_empty()) { + ++errors; + std::cout << " " << std::left << std::setw(29) << where << "skipped: not an interval\n"; + } else { + r &= m; + ++measurements; + show(where, m, "read: a bounded interval", m.is_common_interval()); + } +#endif } check(measurements == 3 && errors == 1, "three measurements read, one line skipped"); // [99.5, 100.5] & [99.7, 100.7] & [99.5, 100.3]: R is in [99.7, 100.3] diff --git a/gaol/gaol_interval.cpp b/gaol/gaol_interval.cpp index cddb47a2..162224f4 100644 --- a/gaol/gaol_interval.cpp +++ b/gaol/gaol_interval.cpp @@ -1101,8 +1101,8 @@ namespace gaol_core { [-0, -0], is written [0], the text of +0: the three are the set {0}, which [0] and [-0] are both read back as, and the text of interval::zero() is then the same whatever the build ([-0, +0] with the SSE2 intervals, - [+0, +0] with the FPU ones); the hexadecimal format writes the signs of the - bounds. The bounds are compared by their bits: under denormals-are-zero, + [+0, +0] with the FPU ones), and the hexadecimal format writes it [0x0p+0]. + The bounds are compared by their bits: under denormals-are-zero, which a program may set, a subnormal compares equal to 0, and l == r and l == 0.0 would have [0, 5e-324] written [0]. (Under that mode, bound_to_text() takes a subnormal bound for 0 as well, and writes it diff --git a/manual/check_examples.py b/manual/check_examples.py new file mode 100644 index 00000000..2c2de912 --- /dev/null +++ b/manual/check_examples.py @@ -0,0 +1,251 @@ +#!/usr/bin/env python3 +#--------------------------------------------------------------------------- +# gaol -- NOT Just Another Interval Library +#--------------------------------------------------------------------------- +# The outputs the manual of GAOL v5 shows, against those of the library +# (GAOL v5): each example of manual/v5/gaol.tex that shows what it prints, +# in its lines @outputs^...~, is compiled with an installed GAOL and run, and +# what it prints is compared, line by line, with what the manual shows. Until +# #55, the examples printed 1 and 0 where the manual showed true and false, +# and chi() of the empty set printed -nan with GCC where it showed nan. +# +# An example holding a main() is compiled as it is. The others are put in a +# main() of their own, after the headers of GAOL ( and +# ) and of the standard library they use, with +# using namespace std and using namespace gaol, or gaol_ieee1788 in the +# chapter of its names, which the manual's examples take for granted. In the +# code, @rem^...~ is the comment it holds; @textasciitilde is a tilde; in an +# output, @version is the version of the installed GAOL, which gaol::version +# prints. A line printed that is too long for the page may be shown on +# several lines of output, each break standing for a blank. Each example is a +# program of its own: a format or a precision one sets does not reach the +# next. A compiler error points to the line of the example in gaol.tex. Every +# line @outputs^...~ of the manual has to be in an example the script reads. +# What the manual shows otherwise, in a block onscreen after a program or in +# a comment "Prints ...", is not checked. +# +# The compiler is $CXX, c++ otherwise, with the flags pkg-config gives for +# gaol, so that PKG_CONFIG_PATH has to lead to the gaol.pc of the GAOL to +# check, in /lib/pkgconfig: +# +# PKG_CONFIG_PATH=/lib/pkgconfig python3 manual/check_examples.py [-j 4] [--std c++11] +# +# The exit status is 1 when an example does not compile, fails, or prints +# something else than the manual shows. +#--------------------------------------------------------------------------- +# gaol is a software distributed WITHOUT ANY WARRANTY. Read the associated +# COPYING file for information. +#--------------------------------------------------------------------------- +# +# Copyright (c) 2026 ENSTA, France +# +# Created 2026-10-06 by Jordan NININ + +import argparse +import concurrent.futures +import os +import re +import shlex +import subprocess +import sys +import tempfile + +# The commands of the examples: @name^argument~ (commandchars=\@\^\~ of the +# environment examplevb, manual/v5/manual.cls) +COMMAND = re.compile(r'@(\w+)\^([^~]*)~') +OUTPUT = re.compile(r'\s*@outputs\^(.*)~\s*') +# A tilde, which would end a command: '\0' while the commands are read +TILDE = '@textasciitilde' + +PROLOGUE = '''#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; +using namespace {namespace}; + +int main() +{{ +#line {line} "{tex}" +''' + +EPILOGUE = ''' + gaol::cleanup(); + return 0; +} +''' + + +class Example: + def __init__(self, line, code, outputs, namespace): + self.line = line # The line of the first line of code in gaol.tex + self.code = code # The code, a line of output left empty + self.outputs = outputs # The lines of output the manual shows + self.namespace = namespace # gaol, or gaol_ieee1788 + + +def code_line(text, tex, line): + """The C++ of a line of an example: the comment of @rem^...~.""" + def command(m): + if m.group(1) != 'rem': + raise ValueError('%s:%d: @%s^...~ in an example that shows an output' + % (tex, line, m.group(1))) + return m.group(2) + code = COMMAND.sub(command, text) + if '@' in code: + raise ValueError('%s:%d: a command the script does not know: %s' % (tex, line, text.strip())) + return code + + +def examples(tex, text): + """The examples of text that show what they print, in their order.""" + # The examples of the chapter of the names of IEEE 1788-2015 open gaol_ieee1788 + chapters = [m.start() for m in re.finditer(r'\\chapter\b', text)] + [len(text)] + label = text.find(r'\label{chap:ieee1788}') + ieee = [(a, b) for a, b in zip(chapters, chapters[1:]) if a <= label < b] + found = [] + for m in re.finditer(r'\\begin\{example\}[^\n]*\n(.*?)\\end\{example\}', text, re.S): + if '@outputs^' not in m.group(1): + continue + first = text.count('\n', 0, m.start(1)) + 1 + code, outputs = [], [] + for i, line in enumerate(m.group(1).replace(TILDE, '\0').split('\n')[:-1]): + output = OUTPUT.fullmatch(line) + if output: + outputs.append(output.group(1).rstrip().replace('\0', '~')) + code.append('') + elif '@outputs^' in line: + raise ValueError('%s:%d: an output that is not alone on its line' % (tex, first + i)) + else: + code.append(code_line(line, tex, first + i).replace('\0', '~')) + namespace = 'gaol_ieee1788' if ieee and ieee[0][0] <= m.start() < ieee[0][1] else 'gaol' + found.append(Example(first, '\n'.join(code), outputs, namespace)) + # An output the script does not read, in an example it does not + # recognize, would not be checked: none may be left out + read = sum(len(example.outputs) for example in found) + if not found or read != text.count('@outputs^'): + raise ValueError('%s: %d lines @outputs^...~, of which %d in the %d examples read' + % (tex, text.count('@outputs^'), read, len(found))) + return found + + +def program(example, tex): + """The program of an example, its lines at their line in tex.""" + if re.search(r'\bmain\s*\(', example.code): + return '#line %d "%s"\n%s\n' % (example.line, tex, example.code) + return (PROLOGUE.format(namespace=example.namespace, line=example.line, tex=tex) + + example.code + EPILOGUE) + + +def lines(text): + """The lines of text, without the blanks that end them, nor the empty lines at its end.""" + result = [line.rstrip() for line in text.split('\n')] + while result and not result[-1]: + result.pop() + return result + + +def shows(expected, printed): + """Whether the lines printed are those the manual shows, where a line + printed may be shown on several, each break standing for a blank.""" + i = 0 + for line in printed: + if i == len(expected): + return False + shown = expected[i] + i += 1 + while shown != line and len(shown) < len(line) and i < len(expected): + shown += ' ' + expected[i] + i += 1 + if shown != line: + return False + return i == len(expected) + + +def check(example, number, tex, directory, compiler, flags, libs, version): + """None if the example prints what the manual shows, what goes wrong otherwise.""" + source = os.path.join(directory, 'example%02d.cpp' % number) + executable = os.path.join(directory, 'example%02d' % number) + with open(source, 'w') as f: + f.write(program(example, tex)) + build = subprocess.run(compiler + flags + [source, '-o', executable] + libs, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) + if build.returncode != 0: + return 'does not compile:\n' + build.stdout + try: + run = subprocess.run([executable], stdout=subprocess.PIPE, stderr=subprocess.PIPE, + universal_newlines=True, timeout=60) + except subprocess.TimeoutExpired: + return 'did not end within 60 s' + if run.returncode != 0: + return 'ended with the status %d:\n%s%s' % (run.returncode, run.stdout, run.stderr) + expected = [output.replace('@version', version) for output in example.outputs] + printed = lines(run.stdout) + if not shows(expected, printed) or run.stderr: + return ('prints something else than the manual shows\n manual: %s\n printed: %s%s' + % ('\n '.join(expected), '\n '.join(printed), + '\n on the error output: ' + run.stderr if run.stderr else '')) + return None + + +def pkg_config(*arguments): + try: + return subprocess.run(['pkg-config'] + list(arguments) + ['gaol'], stdout=subprocess.PIPE, + universal_newlines=True, check=True).stdout.strip() + except (OSError, subprocess.CalledProcessError): + sys.exit('check_examples.py: pkg-config does not find gaol: ' + 'set PKG_CONFIG_PATH to the directory of its gaol.pc') + + +def main(): + p = argparse.ArgumentParser(description='Compiles the examples of the manual of GAOL v5 that show what ' + 'they print, runs them, and compares what they print with the manual.') + p.add_argument('tex', nargs='?', default=os.path.join(os.path.dirname(os.path.abspath(__file__)), + 'v5', 'gaol.tex'), + help='the manual (manual/v5/gaol.tex by default)') + p.add_argument('-j', '--jobs', type=int, default=1, help='the examples compiled at once (1)') + p.add_argument('--std', help='the standard of C++, as c++11 (that of the compiler by default)') + p.add_argument('--keep', metavar='DIRECTORY', help='writes the programs there, and keeps them') + p.add_argument('-v', '--verbose', action='store_true', help='names every example checked') + args = p.parse_args() + + with open(args.tex, encoding='utf-8') as f: + try: + found = examples(args.tex, f.read()) + except ValueError as e: + sys.exit('check_examples.py: %s' % e) + compiler = shlex.split(os.environ.get('CXX', 'c++')) + flags = (['-std=' + args.std] if args.std else []) + shlex.split(pkg_config('--cflags')) + libs = shlex.split(pkg_config('--libs')) + if sys.platform != 'win32': + libs.append('-Wl,-rpath,' + pkg_config('--variable=libdir')) + version = pkg_config('--modversion') + + def run_all(directory): + with concurrent.futures.ThreadPoolExecutor(max(1, args.jobs)) as pool: + return list(pool.map(lambda n: check(found[n], n + 1, args.tex, directory, compiler, flags, + libs, version), range(len(found)))) + if args.keep: + os.makedirs(args.keep, exist_ok=True) + results = run_all(args.keep) + else: + with tempfile.TemporaryDirectory() as directory: + results = run_all(directory) + + failed = 0 + for example, result in zip(found, results): + if result: + failed += 1 + print('%s:%d: the example %s' % (args.tex, example.line, result)) + elif args.verbose: + print('%s:%d: as the manual shows' % (args.tex, example.line)) + print('%d examples showing an output: %d print it, %d do not' % (len(found), len(found) - failed, failed)) + return 1 if failed else 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/manual/v5/gaol.tex b/manual/v5/gaol.tex index bdfd13c1..76574db3 100644 --- a/manual/v5/gaol.tex +++ b/manual/v5/gaol.tex @@ -1503,7 +1503,7 @@ \subsection{Floating-point arithmetic and rounding} exactly, the two intervals are: \begin{onscreen} -interval(0.1) = [0x1.999999999999ap-4, 0x1.999999999999ap-4] +interval(0.1) = [0x1.999999999999ap-4] one_tenth = [0x1.9999999999999p-4, 0x1.999999999999ap-4] \end{onscreen} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 98740594..86206dc1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -119,38 +119,58 @@ add_test(NAME version_file "-DWORK_DIR=${CMAKE_CURRENT_BINARY_DIR}/version_file" -P "${CMAKE_CURRENT_SOURCE_DIR}/version_file.cmake") set_tests_properties(version_file PROPERTIES LABELS unit) +# A compile test (GAOL v5): the object of the target gaol_, compiled from +# source, left out of the build of GAOL and made by the test (cmake --build), +# so that it gets the compiler and the flags of the platform as the files of +# GAOL do. Without a message, the compilation has to succeed. With one, it has +# to fail, and the test passes if the output of the build holds the message: a +# build fails for many reasons, so its exit status is not looked at. The target +# then compiles a copy of source in the build tree, which compile_error.cmake +# copies again and touches before the build: an object whose compilation +# succeeded once stayed up to date, and failed the test, once the header or +# the source was restored with its former modification time (cp -p). The +# definitions and the include directories gaol::gaol carries are written out +# (those of the project GAOL, should another include it): gaol::gaol is not +# linked, so that the test does not wait for the library, which it would build +# first; the caller adds the flags. +function(gaol_compile_test name source message) + if(message) + configure_file(${source} ${name}.cpp COPYONLY) + add_library(gaol_${name} OBJECT "${CMAKE_CURRENT_BINARY_DIR}/${name}.cpp") + add_test(NAME ${name} + COMMAND "${CMAKE_COMMAND}" "-DORIGINAL=${CMAKE_CURRENT_SOURCE_DIR}/${source}" + "-DSOURCE=${CMAKE_CURRENT_BINARY_DIR}/${name}.cpp" + "-DBUILD_DIR=${CMAKE_BINARY_DIR}" "-DCONFIG=$" "-DTARGET=gaol_${name}" + -P "${CMAKE_CURRENT_SOURCE_DIR}/compile_error.cmake") + set_tests_properties(${name} PROPERTIES PASS_REGULAR_EXPRESSION "${message}") + else() + add_library(gaol_${name} OBJECT ${source}) + add_test(NAME ${name} + COMMAND "${CMAKE_COMMAND}" --build "${CMAKE_BINARY_DIR}" --config $ --target gaol_${name}) + endif() + set_target_properties(gaol_${name} PROPERTIES EXCLUDE_FROM_ALL TRUE) + target_include_directories(gaol_${name} PRIVATE "${PROJECT_BINARY_DIR}" "${PROJECT_SOURCE_DIR}") + target_compile_definitions(gaol_${name} PRIVATE $) + # One build at a time in the build directory, whatever ctest -j says: the + # lock of every compile test + set_tests_properties(${name} PROPERTIES LABELS unit RESOURCE_LOCK gaol_compile_tests) +endfunction() # The options gaol/gaol_config.h refuses where GCC and Clang define a macro for # them (GAOL v5): the tests refused_finite_math_only and refused_fast_math # compile refused_options.cpp, a program using GAOL, with -ffinite-math-only # and with -ffast-math, and the compilation has to fail with the message of the # refusal; refused_positive compiles it with neither, and the compilation has -# to succeed. The object is a target of its own, left out of the build of GAOL -# and made by the test (cmake --build), so that it gets the compiler and the -# flags of the platform as the files of GAOL do. A build fails for many -# reasons, so the tests of a refused option do not look at its exit status: -# they pass if the output of the build holds the message. +# to succeed. Each is a compile test (gaol_compile_test() above). # # The option has to come after the flags of interval arithmetic, whose -# -fno-fast-math turns it off otherwise. gaol::gaol is therefore not linked, -# CMake putting the options of a linked library after those of the target: the -# flags, the definitions and the include directories it carries are written -# out here, as for the tests nodiscard_* below. Not with Visual C++, which has -# neither option nor macro (see doc/three-builds.md). The autotools and meson -# builds run the same checks with tests/refused_options.sh. +# -fno-fast-math turns it off otherwise: CMake would put the options of a +# linked gaol::gaol after those of the target, which is one more reason to +# write out what it carries. Not with Visual C++, which has neither option nor +# macro (see doc/three-builds.md). The autotools and meson builds run the same +# checks with tests/refused_options.sh. function(gaol_refused_test name flag text) - add_library(gaol_${name} OBJECT refused_options.cpp) - set_target_properties(gaol_${name} PROPERTIES EXCLUDE_FROM_ALL TRUE) - target_include_directories(gaol_${name} PRIVATE "${PROJECT_BINARY_DIR}" "${PROJECT_SOURCE_DIR}") + gaol_compile_test(${name} refused_options.cpp "${text}") target_compile_options(gaol_${name} PRIVATE ${GAOL_INTERVAL_FLAGS} ${GAOL_FMA_FLAGS} ${flag}) - target_compile_definitions(gaol_${name} PRIVATE $) - add_test(NAME ${name} - COMMAND "${CMAKE_COMMAND}" --build "${CMAKE_BINARY_DIR}" --config $ --target gaol_${name}) - # One build at a time in the build directory, whatever ctest -j says: the - # lock of every compile test - set_tests_properties(${name} PROPERTIES LABELS unit RESOURCE_LOCK gaol_compile_tests) - if(text) - set_tests_properties(${name} PROPERTIES PASS_REGULAR_EXPRESSION "${text}") - endif() endfunction() if(NOT MSVC AND CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") gaol_refused_test(refused_finite_math_only -ffinite-math-only @@ -218,12 +238,8 @@ set_target_properties(gaol_performance PROPERTIES CXX_STANDARD 17 CXX_STANDARD_R # no result away. -Werror=attributes (/we5030 and /we5051 of Visual C++, an # attribute it does not know or ignores before C++17) makes an attribute the # compiler ignores fail them too, as [[nodiscard]] written after the -# declarator, int f() [[nodiscard]];, of which GCC 13 only warns. The object -# is a target of its own, left out of the build of GAOL and made by the test -# (cmake --build), with the flags, the definitions and the include directories -# of GAOL's headers as gaol::gaol carries them, written out here (those of the -# project GAOL, should another include it): only the headers are read, so that -# the test does not wait for the library, which gaol::gaol would build first. +# declarator, int f() [[nodiscard]];, of which GCC 13 only warns. Each is a +# compile test (gaol_compile_test() above), which reads GAOL's headers only. # The tests run in the C locale, the message of GCC and Clang being translated # otherwise; that of Visual C++ is looked for by its number. Not with Visual C++ # before 2019 16.4, whose _Check_return_ before C++17 only /analyze reports, nor @@ -232,22 +248,15 @@ set_target_properties(gaol_performance PROPERTIES CXX_STANDARD 17 CXX_STANDARD_R function(gaol_nodiscard_test standard flags message) foreach(kind discard used) set(name nodiscard_${kind}_cxx${standard}) - add_library(gaol_${name} OBJECT nodiscard.cpp) - set_target_properties(gaol_${name} PROPERTIES EXCLUDE_FROM_ALL TRUE - CXX_STANDARD ${standard} CXX_STANDARD_REQUIRED ON) - target_include_directories(gaol_${name} PRIVATE "${PROJECT_BINARY_DIR}" "${PROJECT_SOURCE_DIR}") - target_compile_options(gaol_${name} PRIVATE ${GAOL_INTERVAL_FLAGS} ${GAOL_FMA_FLAGS} ${flags}) - target_compile_definitions(gaol_${name} PRIVATE $) - add_test(NAME ${name} - COMMAND "${CMAKE_COMMAND}" --build "${CMAKE_BINARY_DIR}" --config $ --target gaol_${name}) - # One build at a time in the build directory, whatever ctest -j says: the - # lock of every compile test - set_tests_properties(${name} PROPERTIES LABELS unit RESOURCE_LOCK gaol_compile_tests - ENVIRONMENT "LC_ALL=C;LANGUAGE=C") if(kind STREQUAL "discard") + gaol_compile_test(${name} nodiscard.cpp "${message}") target_compile_definitions(gaol_${name} PRIVATE GAOL_DISCARD) - set_tests_properties(${name} PROPERTIES PASS_REGULAR_EXPRESSION "${message}") + else() + gaol_compile_test(${name} nodiscard.cpp "") endif() + set_target_properties(gaol_${name} PROPERTIES CXX_STANDARD ${standard} CXX_STANDARD_REQUIRED ON) + target_compile_options(gaol_${name} PRIVATE ${GAOL_INTERVAL_FLAGS} ${GAOL_FMA_FLAGS} ${flags}) + set_tests_properties(${name} PROPERTIES ENVIRONMENT "LC_ALL=C;LANGUAGE=C") endforeach() endfunction() if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND NOT MSVC_VERSION LESS 1924) diff --git a/tests/compile_error.cmake b/tests/compile_error.cmake new file mode 100644 index 00000000..65cf7cf9 --- /dev/null +++ b/tests/compile_error.cmake @@ -0,0 +1,36 @@ +# ================================================================== +# A compile test whose compilation has to fail (GAOL v5) +# ================================================================== +# +# The tests refused_finite_math_only, refused_fast_math and +# nodiscard_discard_cxx (tests/CMakeLists.txt) run this script: +# +# cmake -DORIGINAL= -DSOURCE= +# -DBUILD_DIR= -DCONFIG= +# -DTARGET= -P tests/compile_error.cmake +# +# Each builds the object of its target, whose compilation has to fail with a +# message of the compiler, which CTest looks for in the output of the build. +# Should the compilation succeed once (a header that regressed, or the check +# that the test fails without the fix), the object is up to date, and if the +# header is then restored with its former modification time (cp -p, an +# archive), the build compiles nothing and prints no message: the test failed +# until the object was removed. The target therefore compiles SOURCE, a copy +# of its source ORIGINAL in the build tree, copied again and touched here +# before the build, which compiles it again whatever the times of the headers +# and of the source: CMake makes the copy when it configures, and does not +# configure again for a source restored with its former modification time. +# +# Copyright (c) 2026 ENSTA, France +# +# Created 2026-10-06 by Jordan NININ + +cmake_minimum_required(VERSION 3.14) + +configure_file("${ORIGINAL}" "${SOURCE}" COPYONLY) +file(TOUCH "${SOURCE}") +# The output of the build goes to that of the test, where CTest looks for the +# message; the exit status of the build is not looked at, as a build fails for +# many reasons +execute_process(COMMAND "${CMAKE_COMMAND}" --build "${BUILD_DIR}" --config "${CONFIG}" + --target "${TARGET}") diff --git a/tests/constructor.cpp b/tests/constructor.cpp index 57686c3b..1282c885 100644 --- a/tests/constructor.cpp +++ b/tests/constructor.cpp @@ -6,6 +6,7 @@ #include #include +#if GAOL_EXCEPTIONS_ENABLED #define TEST_INOUT_EQ(Istr,Ires) \ try { \ interval I = textToInterval(Istr); \ @@ -21,6 +22,16 @@ } catch (const input_format_error&) { \ CPPUNIT_FAIL(std::string("Wrong format: ")+std::string(Istr)); \ } +#else +// Without exceptions (configure --disable-exceptions, meson +// -Denable-exception=false), input_format_error does not exist, and a string +// the reader refused would end the program (GAOL v5) +#define TEST_INOUT_EQ(Istr,Ires) \ + do { interval I = textToInterval(Istr); TEST_EQ(I,Ires); } while (0) + +#define TEST_INOUT_SEQ(Istr,Ires) \ + do { interval I = textToInterval(Istr); TEST_SEQ(I,Ires); } while (0) +#endif class constructor_test { diff --git a/tests/expressions.cpp b/tests/expressions.cpp index 734bc128..862020ec 100644 --- a/tests/expressions.cpp +++ b/tests/expressions.cpp @@ -139,6 +139,7 @@ namespace // A string the parser has to refuse void refused(const std::string& text) { +#if GAOL_EXCEPTIONS_ENABLED bool threw = false; try { const interval got = textToInterval(text); @@ -150,6 +151,12 @@ namespace } check("expression: a string that is wrong raises an exception", threw, [&] { return text + " gave an interval"; }); +#else + // Without exceptions (configure --disable-exceptions, meson + // -Denable-exception=false), the string would end the program: not + // checked (GAOL v5) + (void)text; +#endif } void numbers() @@ -325,6 +332,7 @@ namespace check("gaol::textToInterval(\"2\", \"1\") is the empty set, which stays empty", textToInterval("2", "1").is_empty() && (textToInterval("2", "1") + interval(0.0, 1.0)).is_empty(), [] { return hex(textToInterval("2", "1") + interval(0.0, 1.0)); }); +#if GAOL_EXCEPTIONS_ENABLED bool threw = false; try { const interval z = textToInterval("pown([2,5],5)"); @@ -333,6 +341,7 @@ namespace threw = true; } check("gaol::textToInterval throws input_format_error for a name of IEEE 1788-2015 alone", threw); +#endif } void wrong_strings() @@ -373,6 +382,7 @@ namespace refused("[1,2]+[nth_root(8,1.5)]"); } +#if GAOL_EXCEPTIONS_ENABLED /* What an exception of GAOL says as a std::exception (GAOL v5). Its what() was the one of the standard class, "std::exception" with libstdc++ and libc++ whatever went wrong: the text that a handler of std::exception @@ -468,6 +478,7 @@ namespace thrown_says("nb_fp_numbers(NaN, 1)", [] { (void)nb_fp_numbers(std::numeric_limits::quiet_NaN(), 1.0); }); thrown_says("operator>> of \"[1, 2\"", [] { std::istringstream in("[1, 2"); interval x; in >> x; }); } +#endif /* Many decimal intervals, whose bounds are no doubles: the interval read has to hold them, and to be no wider than the two doubles around them. */ @@ -763,7 +774,9 @@ int main() step("operators"); operators(); step("functions"); functions(); step("wrong_strings"); wrong_strings(); +#if GAOL_EXCEPTIONS_ENABLED step("exception_messages"); exception_messages(); +#endif step("decimals"); decimals(); step("built_expressions"); built_expressions(); step("null_node_not_counted"); null_node_not_counted(); diff --git a/tests/float_functions.cpp b/tests/float_functions.cpp index 9897aba1..31d332cd 100644 --- a/tests/float_functions.cpp +++ b/tests/float_functions.cpp @@ -77,6 +77,10 @@ class float_functions_test { CPPUNIT_ASSERT(nb_fp_numbers(-3.5,next_float(-3.5))==2); CPPUNIT_ASSERT(nb_fp_numbers(4,4)==1); +#if GAOL_EXCEPTIONS_ENABLED + // GAOL raises invalid_action_error where it raises exceptions; without + // them (configure --disable-exceptions, meson -Denable-exception=false), + // the error ends the program (GAOL v5) { bool ok = false; try { @@ -177,6 +181,7 @@ class float_functions_test { } CPPUNIT_ASSERT(ok); } +#endif } diff --git a/tests/ieee1788.cpp b/tests/ieee1788.cpp index 4260fda5..de4fa376 100644 --- a/tests/ieee1788.cpp +++ b/tests/ieee1788.cpp @@ -492,11 +492,19 @@ namespace check("textToInterval reads the names of IEEE 1788-2015", got.set_eq(c.value), [&] { return std::string(c.text) + ": " + hex(got) + " rather than " + hex(c.value); }); } - // The names of GAOL alone, and the calls that are wrong: the empty set - const char *const not_the_standard[] = { + // The names of GAOL alone, and the calls that are wrong: the empty set. + // textToInterval turns the exception of the reader into the empty set: + // without exceptions (configure --disable-exceptions, meson + // -Denable-exception=false), the error of a wrong call, as pown([2,5],2.5), + // ends the program, where a name the reader does not know is still the + // empty set + std::vector not_the_standard = { "nth_root(8,3)", "cbrt(8)", "inverse(2)", "integer([1.5,3])", "log1p(0)", - "round_ties_to_even(1)", "pown([2,5],2.5)", "sin(1,2)", "fma(1,2)", + "round_ties_to_even(1)", }; +#if GAOL_EXCEPTIONS_ENABLED + not_the_standard.insert(not_the_standard.end(), { "pown([2,5],2.5)", "sin(1,2)", "fma(1,2)" }); +#endif for (const char *t : not_the_standard) { const interval got = textToInterval(t); check("textToInterval gives the empty set for a name of GAOL alone or a wrong call", @@ -780,11 +788,16 @@ namespace const std::string text = point.str(); bool refused = false; interval back; +#if GAOL_EXCEPTIONS_ENABLED try { back = gaol::textToInterval(text); } catch (const gaol::input_format_error&) { refused = true; } +#else + // Without exceptions, a text the reader refuses ends the program + back = gaol::textToInterval(text); +#endif const bool one_number = (text.find(", ") == std::string::npos); check("operator<< of a point under that locale: read back as an interval containing it, [a] as the point", !refused && back.set_contains(x) && (!one_number || (back.left() == x && back.right() == x)), diff --git a/tests/input_output.cpp b/tests/input_output.cpp index 57781ad1..470c4cfc 100644 --- a/tests/input_output.cpp +++ b/tests/input_output.cpp @@ -63,9 +63,6 @@ class input_output_test { } #endif -#if GAOL_EXCEPTIONS_ENABLED - try { -#endif instr.clear(); instr.str("<3,3>"); instr >> I; @@ -118,10 +115,21 @@ class input_output_test { instr.str("-inf"); instr >> I; CPPUNIT_ASSERT(I.left()==-GAOL_INFINITY && I.right()==-std::numeric_limits::max()); +#if GAOL_EXCEPTIONS_ENABLED + // -inf reads [-oo, -MAX], which is no double: the two bounds of + // <-inf, -inf> are not one value, and the reader refuses it, as + // below, where the checks of GAOL 4 expected [-oo, -MAX] and + // [MAX, +oo]. The exception went to the try that held these checks, + // which printed it and skipped the six after it (GAOL v5) instr.clear(); instr.str("<-inf,-inf>"); - instr >> I; - CPPUNIT_ASSERT(I.left()==-GAOL_INFINITY && I.right()==-std::numeric_limits::max()); + try { + instr >> I; + CPPUNIT_FAIL("no exception for <-inf,-inf>"); + } catch (const input_format_error& e) { + CPPUNIT_ASSERT(e.explanation().find("degenerate") != std::string::npos); + } +#endif instr.clear(); // A lower bound +oo or an upper bound -oo leaves no interval, as // numsToInterval of IEEE 1788-2015 (10.5.8) @@ -144,14 +152,15 @@ class input_output_test { instr.str("[inf,inf]"); instr >> I; TEST_EMPTY(I); +#if GAOL_EXCEPTIONS_ENABLED instr.clear(); instr.str(""); - instr >> I; - CPPUNIT_ASSERT(I.left()==std::numeric_limits::max() && I.right()==GAOL_INFINITY); -#if GAOL_EXCEPTIONS_ENABLED - } catch (const input_format_error& e) { - std::cerr << e; - } + try { + instr >> I; + CPPUNIT_FAIL("no exception for "); + } catch (const input_format_error& e) { + CPPUNIT_ASSERT(e.explanation().find("degenerate") != std::string::npos); + } #endif } diff --git a/tests/misc.cpp b/tests/misc.cpp index 6b711d80..9be386b2 100644 --- a/tests/misc.cpp +++ b/tests/misc.cpp @@ -44,10 +44,14 @@ namespace gaol_names #define GAOL_NAMES_CLASS(c) static_assert(same(), "gaol::" #c) GAOL_NAMES_CLASS(interval); GAOL_NAMES_CLASS(interval_format); +#if GAOL_EXCEPTIONS_ENABLED + // The exceptions exist only where GAOL raises them (not with configure + // --disable-exceptions nor meson -Denable-exception=false) GAOL_NAMES_CLASS(gaol_exception); GAOL_NAMES_CLASS(input_format_error); GAOL_NAMES_CLASS(invalid_action_error); GAOL_NAMES_CLASS(unavailable_feature_error); +#endif GAOL_NAMES_CLASS(timepiece); GAOL_NAMES_CLASS(expression); GAOL_NAMES_CLASS(expr_node); @@ -201,9 +205,12 @@ namespace gaol_names && same() && same(), "gaol::atan2(e1, e2), gaol::nth_root(e, n), gaol::pow(e, n), gaol::operator+(e1, e2)..."); - static_assert(same() - && same())), std::ostream&>(), - "gaol::operator<< of the expressions and of the exceptions"); + static_assert(same(), + "gaol::operator<< of the expressions"); +#if GAOL_EXCEPTIONS_ENABLED + static_assert(same())), std::ostream&>(), + "gaol::operator<< of the exceptions"); +#endif static_assert(same())), bool>(), "gaol::evaluate_expr"); // The functions of doubles, the initialization and the rounding of GAOL 4, diff --git a/tests/numbers.cpp b/tests/numbers.cpp index 9a1c6c81..c586080b 100644 --- a/tests/numbers.cpp +++ b/tests/numbers.cpp @@ -555,6 +555,9 @@ namespace /* An uncertain number is an interval literal of its own, not a bound: [5?1], and the uncertain numbers as bounds in the other forms, which the grammar reading every string as an expression accepted (GAOL v5) */ +#if GAOL_EXCEPTIONS_ENABLED + // Without exceptions (configure --disable-exceptions, meson + // -Denable-exception=false), a text the reader refuses ends the program const char *const invalid[] = { "[5?1]", "[1 000 000]", "[ganz]", "[entire!comment]", "5???u", "[1,2,3]", "3.56?1?", "[5?1, 6]", "[1, 3.56?1]", "[3.56?1,]", "[,3.56?1]", "[- 3.56?1]" }; for (const char *s : invalid) { @@ -568,6 +571,7 @@ namespace } check("IEEE 1788 literals: input_format_error for what is not one", threw, [&] { return std::string(s); }); } +#endif // Doubles written in hexadecimal, with 13 digits after the point, are read // exactly; with a 14th digit 8, half a unit above them, as the tightest @@ -608,6 +612,8 @@ namespace } check("textToInterval(expression) read again: the same interval", same, [&] { return std::string(s); }); } +#if GAOL_EXCEPTIONS_ENABLED + // Without exceptions, a text the reader refuses ends the program const char *const invalid[] = { "nth_root(8, 1.5)", "nth_root(8, 1.5)+1", "[nth_root(8, 1.5), 2]", "sin(1)+", "[sin(1), cos(", "(1+2", "[1, 2*(3+4]", "pow(2, 1)+*3", "<1, 2>", "exp(1) exp(2)", "nth_root([1,2], 1.5)", @@ -624,6 +630,7 @@ namespace check("textToInterval(expression) not computed: an exception", threw, [&] { return std::string(s); }); } } +#endif // atan2(y, x) in expressions: 4 atan2(1, 1) is the tightest enclosure of // pi, and atan2(0, 0), which has no value, is empty const interval four_angles = textToInterval("4*atan2(1, 1)"), none = textToInterval("atan2(0, 0)"), @@ -1209,8 +1216,10 @@ namespace } try { std::rethrow_exception(e); +#if GAOL_EXCEPTIONS_ENABLED } catch (const input_format_error&) { return "input_format_error"; +#endif } catch (const std::ios_base::failure&) { return "std::ios_base::failure"; } catch (...) { @@ -1644,6 +1653,9 @@ namespace !threw && in.fail() && x.left() == 1.0 && x.right() == 2.0, [&] { return std::string(threw ? "threw, " : "") + (in.fail() ? "failbit, " : "no failbit, ") + hex(x); }); } +#if GAOL_EXCEPTIONS_ENABLED + // A line the reader refuses: input_format_error where GAOL raises + // exceptions; without them, the error ends the program { std::istringstream in("[1, 2\n"); interval x(1.0, 2.0); @@ -1688,6 +1700,7 @@ namespace check("operator>> of a line that is no interval, on a stream throwing on failbit: input_format_error", format_error && in.fail(), [&] { return std::string(format_error ? "input_format_error" : "another exception"); }); } +#endif // Blank lines: a file ending with an empty line, and blank lines, or lines // of blanks, before and between the intervals, whatever their line ends @@ -1727,6 +1740,9 @@ namespace !threw && in.fail() && x.left() == 1.0 && x.right() == 2.0, [&] { return std::string(threw ? "threw, " : "") + (in.fail() ? "failbit, " : "no failbit, ") + hex(x); }); } +#if GAOL_EXCEPTIONS_ENABLED + // Lines that are no interval: input_format_error where GAOL raises + // exceptions; without them, the error ends the program // An interval written on two lines is two lines, of which the first is no interval { const loop_result r = read_intervals("\n[1,\n2]\n"); @@ -1741,6 +1757,7 @@ namespace r.ended == "input_format_error" && r.fail, [&] { return describe(r); }); } +#endif // GAOL leaves the rounding direction upward, and the C runtime of Windows reads 1.5 in that // direction one double above (1.5 + 2^-52) where glibc reads it exactly: what these checks are