Skip to content

pow : les contournements de l'ancien CORE-MATH retirés, la puissance minuscule de |y| ≥ 2^1013 gardée - #213

Merged
Jordan08 merged 3 commits into
configure-cleanfrom
todo-ph-pow-workarounds
Oct 28, 2026
Merged

Jordan08 merged 3 commits into
configure-cleanfrom
todo-ph-pow-workarounds

Conversation

@Jordan08

Copy link
Copy Markdown
Owner

Point PH.1 de TODO.md : « après un import d'un CORE-MATH d'après c01f08c5, retirer le contournement du < de cr_pow (pow_rounded_up() et pow_rounded_dn() ». L'import du 27 octobre (0b2ae6d1, cb731c0) a repris ce commit amont. La pull request retire de gaol/core/gaol_interval_core.cpp ce que le commit rend inutile, et garde une seule puissance calculée à part, pour un autre défaut de cr_pow() que c01f08c5 ne corrige pas.

Le défaut, reproduit

cr_pow() met son approximation de log(x) à NaN exprès pour |y| < 2^-969 et pour |y| ≥ 2^1014 (l'exposant de y sous 0x36 ou à partir de 0x7f5), puis testait rh * rh < 0x1p-1022 pour effacer un sous-dépassement parasite. Deux défauts en sortaient :

  • la comparaison < d'un NaN lève l'exception d'opération invalide (pow([1, 2], [1e-300]) tuait un programme qui l'avait activée) ;
  • le carré déborde à partir de |rh| = 2^512 : FE_OVERFLOW pour une puissance qui s'annule.

c01f08c5 (patch d'Erik Enikeev, 5 octobre, pour un sous-dépassement parasite) teste l'exposant de rh (((_rh.u >> 52) & 0x7ff) < 0x200) : ni NaN comparé, ni carré. GAOL calculait ces puissances lui-même (1 + 2^-52, 1, +oo ou 2^-1074).

Reproduit avec le pow.c d'avant l'import et celui d'après, compilés comme GAOL le fait (-O2 -frounding-math -ffp-contract=off), sur 20 exposants de grandeur extrême, 14 bases, les deux signes et les quatre arrondis (2 240 appels) : mêmes valeurs bit à bit ; avec Clang 18 et 21 et avec GCC -O0, l'ancien lève l'invalidité 616 fois et le débordement 664 fois, le nouveau 0 et 576 (ceux d'un vrai débordement) ; avec GCC -O2 l'ancien ne lève rien d'anormal (cause non cherchée). La CI a des jobs Clang et Debug : c'est là que le défaut se voit.

Ce que la pull request change

pow_rounded_up() ne calcule plus à part les exposants de grandeur extrême (|y| < 2^-969 et |y| ≥ 2^1014), ni les puissances inférieures à 1 pour |y| ≥ 2^450 ; pow_up_without_overflow_exception() n'a plus la branche de t ≥ 1100 ; ils appellent cr_pow(). Le commentaire de root_of_power_near() et ceux des tests disent ce qu'il en est.

Ce qui reste, et pourquoi (un défaut de plus de cr_pow())

En retirant tout, la comparaison avec la base a montré 160 cas où pow lève FE_OVERFLOW pour un résultat fini (pow([2^-1074], [1.5·2^1014]), [0, 2^-1074]) : is_exact() de cr_pow() calcule y * e (e est l'exposant de x quand x est une puissance de 2 et y un entier) et ce produit déborde à partir de |y e| = 2^1024, c'est-à-dire pour |y| ≥ 2^1014 environ (|e| va jusqu'à 1 074). GAOL garde donc, dans pow_rounded_up(), la seule puissance minuscule : pour x fini > 0 autre que 1, (x > 1) ≠ (y > 0) et |y| ≥ 2^1013, la puissance arrondie vers le haut est 2^-1074 (|y log x| ≥ 2^960). Ce défaut n'est pas dans 3rd/README.md ; il est à proposer en amont avec les autres (PH.1), sans envoi.

Tests, et comment on montre qu'ils échouent sans le correctif

Les tests existants (rounding_direction) couvraient déjà les deux défauts : les 13 extreme_powers avec FE_INVALID activé, et 20 000 tirages par fonction de pow, pow_rel, pow_exponent_rel sans FE_OVERFLOW pour un résultat sans borne infinie.

  • Avec les contournements retirés et l'ancien pow.c remis (arbre de la base, Clang 21) : rounding_direction échoue, 1 899 vérifications sur 1 151 964 (invalidité, mort sur SIGFPE, débordement). Avec le nouveau pow.c : 1 151 964 vérifications, 0 échec.
  • Trois cas nouveaux dans trap_cases() pour la puissance gardée (pow([2^-1074], [1.5 2^1014]), pow([2^-16], 2^1020), gaol_ieee1788::pow([2^-1000], [2^1013, 2^1020]), bornes [0, 2^-1074], avec FE_OVERFLOW activé dans un processus enfant) : sans le raccourci gardé, les trois échouent (débordement, puis SIGFPE) ; avec lui, ils passent.
  • Comparaison des bornes et des exceptions de la base et de la branche (Clang 21), sur 107 715 résultats de pow(I, J), pow(I, double), pow_rel et pow_exponent_rel d'une grille d'opérandes extrêmes : 0 différence de borne ; 0 invalidité de part et d'autre (33 345 avec l'ancien pow.c) ; FE_OVERFLOW en plus dans 14 642 cas, tous avec une borne supérieure infinie (aucun avec une borne finie). La base ne levait pas le débordement de ces bornes infinies pour |y| ≥ 2^1014 ; doc/using.md dit que FE_OVERFLOW signale une borne infinie.

Pour doc/differences.md et ChangeLog (pull request de synthèse)

pow d'un exposant de grandeur extrême ne lève plus l'invalidité (déjà dit) ; il est maintenant calculé par CORE-MATH, que l'amont a corrigé, sauf la puissance minuscule de |y| ≥ 2^1013. FE_OVERFLOW est levé, pour une borne supérieure infinie, avec |y| ≥ 2^1014 comme avec les autres exposants, et FE_UNDERFLOW peut l'être pour une puissance minuscule finie que CORE-MATH calcule maintenant (la documentation ne promet rien sur le sous-dépassement).

Relecture

Un relecteur indépendant (sous-agent, sans mon contexte) n'a trouvé aucun point bloquant. Il a construit la base et la branche avec Clang 21 et GCC 15 (-O2, -O3 et -O0 en Debug), dans les quatre arrondis, avec les intervalles FPU et dans le conteneur Debian i386 (GCC 14.2), et comparé environ 9 millions de sorties de pow(I, J), pow(I, double), gaol_ieee1788::pow, pow_rel, pow_exponent_rel, powRev1/2 et des puissances entières au-delà des int, sur une grille de 70 bases et 140 exposants, 800 000 paires de boîtes et 3 millions de tirages proches des seuils :

  • 0 différence de borne, Clang comme GCC, -O0 comme -O2, FPU comme SSE2 ;
  • FE_OVERFLOW en plus seulement quand le résultat a une borne infinie ; FE_UNDERFLOW en plus sur des résultats finis minuscules (CORE-MATH les calcule maintenant) ; aucun FE_INVALID ni FE_DIVBYZERO de plus, aucun FE_OVERFLOW pour un résultat fini, aucun indicateur en moins ;
  • ASan et UBSan sur la grille et les tirages : aucun rapport ;
  • dents : avec l'ancien pow.c (Clang 21), rounding_direction échoue 1 899 fois (invalidité et mort sur SIGFPE pour 11 des 13 extreme_powers, débordements dans les tirages) ; sans le raccourci gardé, seuls les 3 cas nouveaux échouent, chacun deux fois ; sur la branche ils passent ;
  • seuil : 8.777798510069902e+304 est exactement 2^1013 ; avec |e| ≤ 1 074, is_exact() ne déborde qu'à partir de |y| ≥ 2^1013,93, donc 2^1013 suffit, et le raccourci est juste pour les deux signes de y.

Corrigé à sa demande : le commentaire de root_of_power_near() (inexact sur 1/y), la précision que le raccourci est une borne supérieure, non la valeur arrondie, pour y infini (la base faisait de même ; root_of_power_near() n'en fait qu'un point de départ), une référence croisée entre pow_rounded_up() et pow_up_without_overflow_exception(), un commentaire périmé de tests/ieee1788.cpp et la phrase de doc/using.md et du manuel. Il doutait de la date du 5 octobre pour c01f08c5 (il n'a pas pu consulter l'amont) : je l'ai relue dans un clone de CORE-MATH, 5 octobre 2026 à 15 h 08.

Il note un risque résiduel : le chemin NaN de CORE-MATH est de nouveau atteint pour |y| < 2^-969 et |y| ≥ 2^1014, et sur un compilateur qui rend signalante une comparaison silencieuse (if-conversion de GCC sur ARM 32 bits et POWER9, GCC 9 vectorisé) il pourrait lever l'invalidité ; les 13 extreme_powers (indicateur et enfant sous SIGFPE) le diront sur ces jobs.

Vérifié en local

Suite complète (ctest), -Wall -Wextra -Werror : GCC 15.2 SSE2 (61 tests, avec les exemples), FPU (58), Clang 18 (61), GCC Debug avec ASan et UBSan (61) ; Clang 21 (45, sans les exemples). rounding_direction : 1 151 964 vérifications, 0 échec. Le manuel se construit (deux passes de pdflatex, 0 erreur) avec le seul « Overfull » qu'avait la base. Aucun fichier de build ne change : autotools et meson n'ont pas été relancés.

Non vérifiable ici : Visual C++ (/fp:strict, /W4 /WX), clang-cl, MinGW-w64, MSYS2, macOS, ARM 32 bits, POWER, s390x, RISC-V et GCC 9 : seule la CI les couvre, et c'est elle qui dira le risque ci-dessus.

Dépendances

Aucune. L'import de CORE-MATH 0b2ae6d1 (cb731c0) est déjà dans configure-clean.

Questions ouvertes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant