Files
saselang-bible/annexes/A-numeric-conversions.md
2026-09-12 18:12:13 +02:00

17 KiB

Saselang — Annexe A — Matrice des conversions numériques

Version documentaire : 0.2.12. Statut : inventaire de conception Core, destiné à devenir normatif après validation.

Cette annexe est le listing exhaustif de travail des conversions numériques Core. Son objectif est d'énumérer le maximum de conversions numériques sémantiquement distinctes sans introduire d'alias ou de doublons inutiles.

A.1. Répartition des responsabilités

Les conversions numériques sont exposées comme capacités de l'API Core des primitives. Elles ne deviennent pas chacune une construction grammaticale du compilateur.

Bible langage / compilateur
    définit les règles de typage, d'appel, d'intrinsic, de target et de lowering

Core
    expose les membres numériques concrets
    ex. int32::tryToInt8(), float64::tryRoundToFloat32()

Annexe Core
    liste exhaustivement les opérations disponibles par paire de types

Compilateur / Sase IR / backend
    valide et abaisse l'opération Core sans que chaque nom devienne de la grammaire

Une capacité Core peut être conditionnée par un target ou une plateforme lorsque cela est réellement nécessaire. L'absence d'une capacité demandée doit être diagnostiquée à la compilation pour le target choisi, et non découverte tardivement dans le backend.

Les conversions numériques fondamentales entre primitives standard sont destinées à constituer le socle Core commun ; le mécanisme de capacités existe surtout pour les opérations qui ne peuvent raisonnablement pas être garanties partout.

A.2. Règle de non-redondance

Une variante n'existe que si elle apporte une sémantique observable différente de l'opération canonique déjà disponible pour la paire source/destination.

Exemple :

int8 value = ...;
int32 larger = value::toInt32();

Puisque toutes les valeurs int8 sont représentables exactement dans int32, les formes suivantes n'ont aucune raison d'exister :

tryToInt32()
saturateToInt32()
wrapToInt32()

En revanche, pour int32 -> int8, tryToInt8(), saturateToInt8() et wrapToInt8() ont trois contrats différents et peuvent coexister.

Une règle de composition complète cette non-redondance :

Deux opérations ne sont fusionnées dans un même nom que si leur séparation en opérations Core successives modifierait la sémantique, perdrait de l'information ou empêcherait d'exprimer le même contrat.

Ainsi value::floor()::tryToInt32() rend inutile un alias tryFloorToInt32() si les deux formes sont strictement équivalentes.

A.3. Nomenclature générale

Code Famille Contrat
T toTarget() Exacte et totale pour toutes les valeurs valides du type source.
E tryToTarget() Exacte pour la valeur courante ; retourne Err si l'exactitude ou la représentabilité échoue.
R roundToTarget() Perte de précision explicitement acceptée ; conversion totale pour cette paire de types.
TR tryRoundToTarget() Arrondi explicitement accepté, mais la valeur peut être hors du domaine destination.
S saturateToTarget() Conversion totale par saturation lorsqu'aucun arrondi supplémentaire n'est nécessaire.
SR saturatingRoundToTarget() Arrondi canonique + saturation explicites pour une destination flottante lorsque les deux peuvent être nécessaires.
W wrapToTarget() Conversion entière modulo 2^N, avec interprétation selon le type entier destination.
aucune Même type ou opération sans sémantique distincte utile.

NumericConversionError est retenu comme nom de travail Core. Il pourra être renommé avant stabilisation de la spécification si nécessaire.

Les formes try... retournent conceptuellement :

Result<Target, NumericConversionError>

Aucune de ces opérations n'autorise une conversion implicite entre deux variables déjà typées.

A.4. Flottants : hypothèses de représentation

Type Précision significative p Exposant maximal fini
float16 11 bits 15
float32 24 bits 127
float64 53 bits 1023
float128 113 bits 16383

Les conversions avec arrondi utilisent par défaut la règle canonique round to nearest, ties to even. Cette règle ne dépend ni du linter, ni du profil, ni du backend.

A.5. Entier -> entier

Légende :

  • T = toTarget() uniquement ;
  • E+S+W = tryToTarget() + saturateToTarget() + wrapToTarget().
Source \ Destination int8 int16 int32 int64 int128 int256 uint8 uint16 uint32 uint64 uint128 uint256
int8 T T T T T E+S+W E+S+W E+S+W E+S+W E+S+W E+S+W
int16 E+S+W T T T T E+S+W E+S+W E+S+W E+S+W E+S+W E+S+W
int32 E+S+W E+S+W T T T E+S+W E+S+W E+S+W E+S+W E+S+W E+S+W
int64 E+S+W E+S+W E+S+W T T E+S+W E+S+W E+S+W E+S+W E+S+W E+S+W
int128 E+S+W E+S+W E+S+W E+S+W T E+S+W E+S+W E+S+W E+S+W E+S+W E+S+W
int256 E+S+W E+S+W E+S+W E+S+W E+S+W E+S+W E+S+W E+S+W E+S+W E+S+W E+S+W
uint8 E+S+W T T T T T T T T T T
uint16 E+S+W E+S+W T T T T E+S+W T T T T
uint32 E+S+W E+S+W E+S+W T T T E+S+W E+S+W T T T
uint64 E+S+W E+S+W E+S+W E+S+W T T E+S+W E+S+W E+S+W T T
uint128 E+S+W E+S+W E+S+W E+S+W E+S+W T E+S+W E+S+W E+S+W E+S+W T
uint256 E+S+W E+S+W E+S+W E+S+W E+S+W E+S+W E+S+W E+S+W E+S+W E+S+W E+S+W

Règles :

  • si le domaine source est entièrement inclus dans le domaine destination, seule la forme to... existe ;
  • sinon tryTo... effectue une conversion exacte conditionnelle ;
  • saturateTo... borne à Target::Min / Target::Max ; pour un signé vers non signé, toute valeur négative sature à 0 ;
  • wrapTo... utilise une définition mathématique modulo 2^N, indépendante de la représentation machine du backend ;
  • aucun wrapping ni saturation n'est implicite.

A.6. Entier -> flottant

Légende :

  • T = toFloatXX() uniquement ;
  • E+R = tryToFloatXX() + roundToFloatXX() ;
  • E+TR+SR = tryToFloatXX() + tryRoundToFloatXX() + saturatingRoundToFloatXX().
Source \ Destination float16 float32 float64 float128
int8 T T T T
int16 E+R T T T
int32 E+TR+SR E+R T T
int64 E+TR+SR E+R E+R T
int128 E+TR+SR E+R E+R E+R
int256 E+TR+SR E+TR+SR E+R E+R
uint8 T T T T
uint16 E+TR+SR T T T
uint32 E+TR+SR E+R T T
uint64 E+TR+SR E+R E+R T
uint128 E+TR+SR E+TR+SR E+R E+R
uint256 E+TR+SR E+TR+SR E+R E+R

Contrats :

  • toFloatXX() existe seulement si toute valeur source est exactement représentable ;
  • tryToFloatXX() exige une représentation exacte de la valeur courante ;
  • roundToFloatXX() existe seulement lorsque toute valeur source reste dans le domaine fini destination, mais qu'une perte de précision peut être nécessaire ;
  • tryRoundToFloatXX() accepte l'arrondi canonique mais retourne Err si la magnitude finie dépasse le domaine destination ;
  • saturatingRoundToFloatXX() est réservé aux paires pour lesquelles un débordement de domaine est possible : une valeur finie trop grande est bornée au plus grand fini de même signe, puis la précision destination s'applique selon l'arrondi canonique ;
  • il n'existe pas de wrapToFloatXX().

Exemple distinct :

int64 value = ...;

Result<float64, NumericConversionError> exact = value::tryToFloat64();
float64 approximated = value::roundToFloat64();

tryToFloat64() et roundToFloat64() ne sont pas des alias : le premier refuse toute perte de précision, le second l'accepte explicitement.

A.7. Flottant -> flottant

Légende :

  • T = toFloatXX() uniquement ;
  • E+TR+SR = tryToFloatXX() + tryRoundToFloatXX() + saturatingRoundToFloatXX().
Source \ Destination float16 float32 float64 float128
float16 T T T
float32 E+TR+SR T T
float64 E+TR+SR E+TR+SR T
float128 E+TR+SR E+TR+SR E+TR+SR

Règles :

  • l'élargissement de format est exact au niveau de la valeur IEEE et utilise uniquement toFloatXX() ;
  • la réduction de format propose tryToFloatXX() pour exiger l'exactitude, tryRoundToFloatXX() pour accepter la perte de précision mais pas le débordement fini, et saturatingRoundToFloatXX() pour obtenir une opération totale sur le domaine flottant ;
  • NaN, +Infinity, -Infinity, +0 et -0 restent des catégories IEEE valides dans la destination ;
  • pour NaN, seule la propriété sémantique isNaN(destination) == true est garantie ; payload, signe et quiet/signaling bit ne sont pas garantis par une conversion de valeur ;
  • tryToFloatXX() accepte NaN et les infinities lorsque la destination est un type flottant Saselang, car ces catégories y sont représentables ;
  • saturatingRoundToFloatXX() ne transforme pas une infinité en valeur finie puisque l'infinité est elle-même représentable dans le format destination ; la saturation explicite concerne les valeurs finies hors du domaine fini destination ;
  • la conservation exacte d'une représentation binaire relève d'un contrat binaire distinct et de bitcast lorsque ses propres contraintes sont compatibles.

A.8. Flottant -> entier : composition des politiques

Il n'existe jamais de simple toIntXX() ou toUintXX() depuis un flottant.

La conversion stricte canonique est :

tryToIntXX()
tryToUintXX()

Elle réussit uniquement si la valeur flottante est finie, mathématiquement entière, dans le domaine de la destination et exactement représentable comme entier destination. Sinon elle retourne Err(NumericConversionError(...)).

A.8.1. Politiques mathématiques séparées

Les opérations Core :

floor()
ceil()
round()
truncate()

restent des opérations sur le flottant et définissent chacune une politique mathématique unique.

Elles se composent ensuite avec les conversions :

value::floor()::tryToInt32()
value::ceil()::tryToInt32()
value::round()::tryToInt32()
value::truncate()::tryToInt32()

Cette composition remplace les alias redondants tryFloorTo..., tryCeilTo..., tryRoundTo... et tryTruncateTo....

round() utilise la règle canonique nearest, ties to even.

A.8.2. Dépassement de domaine

Les politiques de dépassement restent séparées de la politique mathématique.

Pour chaque destination entière, le Core peut exposer :

tryToTarget()
trySaturateToTarget()
tryWrapToTarget()

avec les contrats suivants :

Opération Préconditions non liées à la plage Politique de plage
tryToTarget() valeur finie et mathématiquement entière Err si hors plage
trySaturateToTarget() valeur finie et mathématiquement entière borne à Target::Min / Target::Max
tryWrapToTarget() valeur finie et mathématiquement entière modulo 2^N selon le type entier destination

Les politiques se composent :

value::floor()::trySaturateToInt32()
value::round()::trySaturateToUint16()

value::truncate()::tryWrapToInt8()
value::ceil()::tryWrapToUint64()

Il n'existe pas de variantes combinées telles que trySaturatingFloorToInt32() ou tryWrappingRoundToInt32() lorsque la composition ci-dessus possède exactement la même sémantique.

A.8.3. Matrice flottant -> entier après élimination des doublons

Pour chaque paire, la matrice conserve uniquement les politiques dont le résultat peut réellement différer sur une valeur source admissible.

Légende :

C
    tryToTarget() uniquement

CSW
    tryToTarget()
    trySaturateToTarget()
    tryWrapToTarget()
Source \ Destination int8 int16 int32 int64 int128 int256 uint8 uint16 uint32 uint64 uint128 uint256
float16 CSW CSW C C C C CSW CSW CSW CSW CSW CSW
float32 CSW CSW CSW CSW CSW C CSW CSW CSW CSW CSW CSW
float64 CSW CSW CSW CSW CSW CSW CSW CSW CSW CSW CSW CSW
float128 CSW CSW CSW CSW CSW CSW CSW CSW CSW CSW CSW CSW

Justification :

  • toutes les valeurs finies et mathématiquement entières de float16 tiennent dans int32 et les entiers signés plus larges ; saturation et wrapping n'apportent donc rien pour ces paires ;
  • toutes les valeurs finies et mathématiquement entières de float32 tiennent dans int256, mais pas dans int128 ou plus petit ;
  • pour toute destination non signée, les valeurs négatives rendent checked, saturation et wrapping distincts, même lorsque toute magnitude positive finie tient dans la destination ;
  • float64 et float128 disposent de valeurs finies dépassant le domaine de tous les entiers Saselang V1.

Cas particuliers :

  • +0 et -0 donnent l'entier zéro ;
  • NaN, +Infinity et -Infinity échouent dans ces familles car ils ne satisfont pas la précondition de valeur finie et mathématiquement entière ;
  • pour une destination non signée, la saturation d'une valeur négative entière donne 0 ;
  • pour une destination signée, la saturation utilise Target::Min / Target::Max ;
  • le wrapping est appliqué au résultat entier mathématique fini selon modulo 2^N.

A.9. bitcast reste hors de cette matrice

bitcast<T>(value) n'est pas une conversion numérique. Il conserve les bits et change leur interprétation sous les contraintes de taille et de validité déjà définies par le langage.

Il ne constitue jamais une alternative implicite à to..., try..., round..., saturate... ou wrap....

A.10. Typage contextuel des littéraux

Le typage contextuel d'un littéral reste distinct de toute conversion de valeur déjà typée.

int32 a = 42;                    // contextualisation du littéral

int8 small = ...;
int32 b = small;                // ERROR : pas de conversion implicite
int32 c = small::toInt32();     // OK

A.11. Core, targets et capacités

Les membres de conversion appartiennent au Core des primitives. Le compilateur doit pouvoir reconnaître leur contrat via les métadonnées/Core intrinsics nécessaires sans transformer chaque membre en syntaxe spéciale.

Pour les conversions fondamentales de cette matrice :

si les types source et destination sont supportés par le target, les opérations non redondantes définies par la matrice sont Core-required.

L'absence d'une instruction matérielle native ne rend pas l'opération optionnelle lorsqu'une émulation logicielle conforme est raisonnablement possible.

Un target réduit peut ne pas exposer un type fondamental donné. Dans ce cas, l'indisponibilité doit porter sur le type/capacité fondamentale, pas sur une sélection arbitraire de ses conversions.

La nomenclature générale des capabilities et leurs niveaux sont définis au chapitre 40.

A.12. Hiérarchie documentaire cible

La documentation Saselang est destinée à être organisée par niveaux :

Language / Compiler Specification
    ce que le compilateur doit accepter, refuser et produire

Saselang + Core Specification
    langage + environnement Core normatif

Saselang Platform Documentation
    langage + Core + SDKs + extensions de plateforme

Depuis 0.2.12, la Bible est distribuée sous forme d'une archive multifichier structurée avec sommaire, chapitres séparés, exemples/DO-DON'T par chapitre et annexes référencées.

A.13. NumericConversionError

NumericConversionError est le nom de travail du ResultError Core des conversions récupérables.

Les codes sémantiques V1 sont :

NotFinite
NotIntegral
OutOfRange
Inexact

L'erreur reste minimale et ne transporte pas automatiquement la valeur source, les types source/destination, le backend, un timestamp ou un mode d'arrondi.

A.14. État de l'annexe

Les principes sémantiques de la matrice sont désormais largement figés :

non-redondance par paire
composition plutôt qu'alias combinés
règles float -> integer
règles NaN / infinities / signed zero
NumericConversionError
disponibilité Core pour les types supportés
émulation logicielle lorsque raisonnable

Restent principalement à auditer lors de l'implémentation Core :

  1. le listing mécanique exhaustif des membres concrets exposés par chaque primitive ;
  2. les noms définitifs des constantes/membres Core associés ;
  3. la représentation interne exacte des codes NumericConversionError ;
  4. les tests de conformité couvrant toutes les cellules de la matrice.