From b6bade29f24a812dad3f456caf402b8d45c84dc2 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Tue, 6 Oct 2026 00:33:54 +0200 Subject: [PATCH 1/2] hausdorff(): +oo before the rounding check when a bound is infinite in one interval only --- doc/using.md | 4 +++- gaol/gaol_interval_fpu.cpp | 16 ++++++++++++++-- gaol/gaol_interval_sse.cpp | 16 ++++++++++++++-- manual/v5/gaol.tex | 4 +++- 4 files changed, 34 insertions(+), 6 deletions(-) diff --git a/doc/using.md b/doc/using.md index fb56e76e..dd2b1e75 100644 --- a/doc/using.md +++ b/doc/using.md @@ -711,7 +711,9 @@ comparisons: [0, 0] with GCC 13 at `-O2`, [3*2^-1074, 100*2^-1074] at either, but it only exchanges the stored bounds, which the modes do not change, as they do not change the test of the empty set, made before the check of every operation (an operation with an empty operand may return before its -check, the modes left as it found them). The modes of other processors, and +check, the modes left as it found them), nor the test by which `hausdorff()` +returns +oo before its check when a bound is infinite in one of its two +intervals only. The modes of other processors, and of ARM with Visual C++, are neither checked nor cleared; GCC links `crtfastmath.o` for none of the other processors GAOL is tested on. Link a program that uses GAOL without these options, or with `-mno-daz-ftz`, and diff --git a/gaol/gaol_interval_fpu.cpp b/gaol/gaol_interval_fpu.cpp index 334a0ca9..c80d0299 100644 --- a/gaol/gaol_interval_fpu.cpp +++ b/gaol/gaol_interval_fpu.cpp @@ -30,12 +30,24 @@ if (I1.is_empty() || I2.is_empty()) { return GAOL_NAN; } + // A bound infinite in one interval only puts them at distance +oo, returned + // before the check of the rounding direction (GAOL v5, point R.50 of + // TODO.md; GAOL 4 returned +oo there for every infinite bound): the check + // and the differences made it two to four times as long (Intel i7-1185G7, + // GCC 9.4 and Clang 18). Whether a bound is infinite depends neither on the + // rounding direction nor on the flush-to-zero modes, which read a subnormal + // bound as a zero, no infinity either. The bounds compared after the check + // are read after it (GAOL_RND_BARRIER() of gaol/gaol_fpu.h, and /fp:strict + // with Visual C++) + if ((std::fabs(I1.left()) == GAOL_INFINITY) != (std::fabs(I2.left()) == GAOL_INFINITY) + || (std::fabs(I1.right()) == GAOL_INFINITY) != (std::fabs(I2.right()) == GAOL_INFINITY)) { + return GAOL_INFINITY; + } // The tightest upper bound of the distance, whatever the rounding direction // of the caller: each difference is rounded upward both ways. // Equal bounds, infinite ones included, are at distance 0 (inf - inf is a // NaN): the distance of [1, +oo] to [1, +oo] is 0 and the one to [2, +oo] is - // 1, where GAOL 4 returned +oo. A bound infinite in one interval only is at - // distance +oo, as inf - x is. + // 1, where GAOL 4 returned +oo. GAOL_RND_ENTER(); const double a = I1.left(), b = I1.right(), c = I2.left(), e = I2.right(); double d = maximum((a == c) ? 0.0 : maximum(a - c, c - a), diff --git a/gaol/gaol_interval_sse.cpp b/gaol/gaol_interval_sse.cpp index 26009c69..74e954af 100644 --- a/gaol/gaol_interval_sse.cpp +++ b/gaol/gaol_interval_sse.cpp @@ -95,12 +95,24 @@ if (I1.is_empty() || I2.is_empty()) { return std::numeric_limits::quiet_NaN(); } + // A bound infinite in one interval only puts them at distance +oo, + // returned before the check of the rounding direction (GAOL v5, point + // R.50 of TODO.md; GAOL 4 returned +oo there for every infinite bound): + // the check and the differences made it two to four times as long + // (Intel i7-1185G7, GCC 9.4 and Clang 18). Whether a bound is infinite + // depends neither on the rounding direction nor on the flush-to-zero + // modes, which read a subnormal bound as a zero, no infinity either. The + // bounds compared after the check are read after it (GAOL_RND_BARRIER() + // of gaol/gaol_fpu.h, and /fp:strict with Visual C++) + if ((std::fabs(I1.left()) == GAOL_INFINITY) != (std::fabs(I2.left()) == GAOL_INFINITY) + || (std::fabs(I1.right()) == GAOL_INFINITY) != (std::fabs(I2.right()) == GAOL_INFINITY)) { + return GAOL_INFINITY; + } // The tightest upper bound of the distance, whatever the rounding // direction of the caller: each difference is rounded upward both ways. // Equal bounds, infinite ones included, are at distance 0 (inf - inf is a // NaN): the distance of [1, +oo] to [1, +oo] is 0 and the one to [2, +oo] is - // 1, where GAOL 4 returned +oo. A bound infinite in one interval only is at - // distance +oo, as inf - x is. + // 1, where GAOL 4 returned +oo. GAOL_RND_ENTER(); const double a = I1.left(), b = I1.right(), c = I2.left(), e = I2.right(); double d = fmax((a == c) ? 0.0 : fmax(a - c, c - a), diff --git a/manual/v5/gaol.tex b/manual/v5/gaol.tex index 31d275aa..d1e9e5b1 100644 --- a/manual/v5/gaol.tex +++ b/manual/v5/gaol.tex @@ -1151,7 +1151,9 @@ \subsection{Flush-to-zero and denormals-are-zero}\label{sec:ftz} makes no check either, but it only exchanges the stored bounds, which the modes do not change, as they do not change the test of the empty set, made before the check of every operation (an operation with an empty operand may -return before its check, the modes left as it found them). The modes of other +return before its check, the modes left as it found them), nor the test by +which \code{hausdorff()} returns $+\infty$ before its check when a bound is +infinite in one of its two intervals only. The modes of other processors, and of ARM with Visual C++, are neither checked nor cleared; GCC links \file{crtfastmath.o} for none of the other processors GAOL is tested on. Link a program that uses GAOL without these options, or with From e61a34d3a1eabb3bbde64388ca1a14cfe7a5a98d Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Tue, 6 Oct 2026 00:33:54 +0200 Subject: [PATCH 2/2] tests/reverse_mappings.cpp: atanh_rel compared at its infinite ends, [0.5, 1] in [0, +oo] --- doc/tests.md | 4 +++- tests/reverse_mappings.cpp | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/doc/tests.md b/doc/tests.md index 46d972ff..758d2782 100644 --- a/doc/tests.md +++ b/doc/tests.md @@ -719,7 +719,9 @@ mode there. values compared, and a failure no longer ends its test. `reverse_mappings` draws its random intervals from a seed of its own rather than from the process identifier (`srand48(getpid())`, which Visual C++ does not have), so - that its checks are the same at each run. `intervalf` and `interval2f` test + 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 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 diff --git a/tests/reverse_mappings.cpp b/tests/reverse_mappings.cpp index 6a2a1dac..18d51ebe 100644 --- a/tests/reverse_mappings.cpp +++ b/tests/reverse_mappings.cpp @@ -229,6 +229,11 @@ class reverse_mappings_test { // on, and every x has its tanh in [-1,1] TEST_EQ(atanh_rel(interval(0.5,1.0),interval(0.0,100.0)),interval(0.5493061443340548457,100.0)); TEST_EQ(atanh_rel(interval(-1.0,1.0),interval(1.0,2.0)),interval(1.0,2.0)); + // The infinite ends themselves, which a bounded I hides: TEST_EQ can + // compare them since hausdorff() puts two equal infinite bounds at + // distance 0 (GAOL v5, point R.7 of TODO.md) + TEST_EQ(atanh_rel(interval(0.5,1.0),interval(0.0,GAOL_INFINITY)),interval(0.5493061443340548457,GAOL_INFINITY)); + TEST_EQ(atanh_rel(interval(-1.0,-0.5),interval(-GAOL_INFINITY,0.0)),interval(-GAOL_INFINITY,-0.5493061443340548457)); interval TI[MAX]; for (unsigned int i = 0; i < MAX; ++i) {