Retour au Centre de connaissances
Empreinte

Intl.NumberFormat : mode d’arrondi et affichage du signe

Une sonde sur le formatage numérique : quel mode d’arrondi est par défaut, ce que fait chaque mode sur une frontière .5, et comment un signe ou un zéro disparaît.

BotBrowser Team

Documentation

Vous voulez la documentation structurée pour Empreinte ?

Cet article fait partie de la bibliothèque éditoriale. Pour les étapes de configuration, la référence et les mises à jour continues, passez directement à la section docs.

BotBrowser permet de rejouer un fixture de même origine autorisé sous un profil déclaré et de garder une trace bornée de la chaîne exacte qu’une page a rendue pour un nombre, mais il ne peut pas faire arrondir un formateur comme le suppose le lecteur, ne peut pas restaurer un signe moins qu’une option a retiré, ni faire afficher le même nombre de décimales à deux devises. Cette revue exécute une sonde sur onze cas fixes et consigne quel mode d’arrondi est par défaut sur une frontière .5, ce que fait chaque mode explicite sur les mêmes quatre valeurs, comment signDisplay et trailingZeroDisplay changent la chaîne rendue, comment roundingPriority arbitre entre deux limites, pourquoi les décimales par défaut d’une devise viennent de son code, et ce que resolvedOptions() et formatToParts() déclarent sur le formateur qui a réellement tourné.

Résumé

  • L’arrondi par défaut est half-expand, en s’éloignant de zéro : la sonde imprime default -> 3,4,-3,1,1 pour 2.5, 3.5, -2.5, 0.5, 1.45 à zéro décimale, donc 2.5 devient 3, pas 2.
  • halfEven est le mode qui donnerait 2.5 -> 2, et la sonde imprime halfEven-vs-expand -> 2,4 vs 3,4, donc les deux modes ne diffèrent que là où le chiffre avant la frontière est pair.
  • trunc, ceil et floor déplacent un nombre négatif dans des directions différentes : -2.5 devient -2 avec trunc, -2 avec ceil et -3 avec floor.
  • signDisplay: 'never' retire le moins d’un nombre négatif, donc la sonde imprime sign:never -> 5,0,5, et un consommateur qui lit cette chaîne ne peut pas savoir qu’elle venait de -5.
  • signDisplay: 'always' met un + aussi sur le zéro, donc la sonde imprime sign:always -> +5,+0,-5.
  • trailingZeroDisplay: 'stripIfInteger' garde les deux décimales de 3.10 mais retire celles qui n’existaient qu’à cause du minimum, donc 3.00 s’affiche 3.
  • roundingPriority: 'morePrecision' élargit la sortie pour satisfaire les deux limites, donc 12.345 imprime 12.35 avec morePrecision tandis qu’auto et lessPrecision impriment 12.3.
  • Les décimales par défaut d’une devise viennent de son code, donc les mêmes options impriment $5.00 et ¥5.
  • Poser maximumFractionDigits: 0 sur un formateur de devise fixe aussi minimumFractionDigits à 0, et resolvedOptions() déclare min=0 max=0 mode=halfExpand.

Sommaire

Le premier panneau montre 2.5 et 3.5 arrondis à 3 et 4 sous le mode par défaut half-expand et à 2 et 4 sous halfEven. Le deuxième montre trunc, ceil et floor menant -2.5 à -2, -2 et -3. Le troisième montre sign never transformant -5 en 5 et sign always transformant 5 en +5. Le quatrième montre morePrecision imprimant 12.35 là où auto imprime 12.3 et un défaut de devise de deux décimales pour USD contre zéro pour JPY.

Quel mode d’arrondi est par défaut

Un formateur numérique arrondit que l’appelant nomme un mode ou non, et le défaut est un choix précis qu’un lecteur vérifie rarement. La sonde formate cinq valeurs à zéro décimale sans mode d’arrondi :

node=v26.9.0
default -> 3,4,-3,1,1
mode:halfExpand -> 3,4,-3,2
mode:halfEven -> 2,4,-2,2
mode:trunc -> 2,3,-2,1
mode:ceil -> 3,4,-2,2
mode:floor -> 2,3,-3,1
mode:expand -> 3,4,-3,2
mode:halfTrunc -> 2,3,-2,2
halfEven-vs-expand -> 2,4 vs 3,4
sign:auto -> 5,0,-5
sign:never -> 5,0,5
sign:always -> +5,+0,-5
sign:exceptZero -> +5,0,-5
sign:negative -> 5,0,-5
trailing:default -> 3.00 3.10
trailing:strip -> 3 3.10
priority -> 12.345:auto=12.3 more=12.35 less=12.3 | 1234.5:auto=1230 more=1234.5 less=1230 | 456.78:auto=457 more=456.78 less=457
currency -> $5.00 ¥5
currency:min0 -> $5
percent -> 13% 13%
compact -> 1.2M 1.2M
digits -> ar-EG=١٢٣٤ ar-EG-latn=1234
grouping -> de-DE=1.234.567,89 en-US=1,234,567.89
resolved -> min=0 max=0 mode=halfExpand
parts -> minusSign:-|currency:USD|literal: |integer:5|decimal:.|fraction:00

La première ligne répond à la question que presque tout le monde rate. default -> 3,4,-3,1,1 signifie que 2.5 s’arrondit à 3 et -2.5 à -3, donc le défaut est half-expand, qui arrondit une égalité en s’éloignant de zéro. Ce n’est pas la règle « arrondir au pair » que beaucoup supposent d’autres langages, et la différence apparaît sur chaque valeur .5, précisément le genre de nombre qu’un fixture de test contient souvent.

Le second chiffre montre l’autre moitié. 1.45 à zéro décimale imprime 1, pas 2, parce que 1.45 n’est pas exactement 1.45 en virgule flottante binaire ; il est juste sous le milieu, donc il n’y a pas d’égalité à rompre et le résultat est l’entier le plus proche en dessous. Une page qui suppose qu’un littéral décimal est exact verra donc un .45 arrondir vers le bas alors qu’un .5 arrondit vers le haut, et les deux comportements viennent de la même règle appliquée à des entrées différentes.

Le dernier chiffre est celui qui surprend un rapport. 0.5 devient 1, donc une petite valeur positive arrondit à une unité entière. Une page qui utilise zéro décimale pour un décompte affichera donc 1 pour toute valeur dans [0.5, 1.5), et une page qui l’utilise pour un petit taux affichera 1 dès un demi, ce qui peut se lire comme une erreur de 100 % sur les plus petites valeurs.

Les données de langue qui gouvernent les séparateurs et les formes de chiffres qu’un formateur émet sont un sujet à part, et données de langue de l’API Intl consigne quelles parties d’une étiquette de langue un formateur lit. Il n’y a aucune dépendance de langue. La sonde passe en-US et le mode est une propriété du formateur, pas de la langue ; de-DE ou ja-JP arrondissent pareil et ne diffèrent que par le séparateur et les formes de chiffres montrées plus bas. Une page qui teste l’arrondi dans une seule langue a donc vu toute la règle, et une page qui suppose qu’une autre langue arrondit autrement lit une différence de groupement comme une différence d’arithmétique.

Le défaut se voit aussi dans resolvedOptions(), et c’est la vérification à inscrire dans une trace. La sonde imprime resolved -> min=0 max=0 mode=halfExpand pour un formateur qui n’a fixé que maximumFractionDigits: 0, donc le mode résolu nomme le défaut que l’appelant n’a pas écrit. Une trace qui garde les options d’entrée mais pas les résolues ne peut plus expliquer pourquoi 2.5 a imprimé 3, parce que la raison vit dans un champ que l’appelant n’a pas fixé.

Ce que fait chaque mode sur une frontière

La sonde fixe ensuite chaque mode offert par la spécification et formate les mêmes quatre valeurs, ce qui transforme la question en tableau. Chaque ligne est un mode et ses quatre résultats pour 2.5, 3.5, -2.5, 1.6.

mode:halfExpand -> 3,4,-3,2 est le défaut rendu explicite. mode:halfEven -> 2,4,-2,2 envoie 2.5 au voisin pair 2 et laisse 3.5 au voisin pair 4, donc l’égalité tombe toujours sur une valeur dont le dernier chiffre est pair. La ligne dédiée rend la différence concrète : halfEven-vs-expand -> 2,4 vs 3,4, donc les deux modes s’accordent sur 3.5 et divergent sur 2.5.

mode:trunc -> 2,3,-2,1 jette tout chiffre au-delà de la frontière quelle que soit sa proximité, donc 1.6 devient 1 et -2.5 devient -2. mode:ceil -> 3,4,-2,2 arrondit vers l’infini positif, donc 1.6 devient 2 et -2.5 devient -2 parce que -2 est plus grand que -2.5. mode:floor -> 2,3,-3,1 arrondit vers l’infini négatif, donc 1.6 devient 1 et -2.5 devient -3. mode:expand -> 3,4,-3,2 s’éloigne de zéro sur chaque valeur, donc il s’accorde avec ceil sur les positifs et avec floor sur les négatifs.

Les trois modes directionnels sont là où un défaut de gestion du signe s’introduit le plus facilement. Une page qui veut « arrondir au-dessus » et prend ceil obtient -2 pour -2.5, qui est plus grand mais plus proche de zéro, donc un « arrondir au-dessus » qui veut dire « en s’éloignant de zéro » a besoin d’expand. Une page qui veut le choix prudent pour une quantité qu’elle ne doit pas exagérer a besoin de floor sur un positif et de ceil sur un négatif, ce qui fait deux règles et non une, et les noms de modes ne les combinent pas.

mode:halfTrunc -> 2,3,-2,2 arrondit une égalité vers zéro et le reste au plus proche, donc 2.5 devient 2, 3.5 devient 4 et -2.5 devient -2. C’est la direction d’égalité opposée à halfExpand, et c’est le mode à prendre quand une page rapporte un total qu’elle ne doit pas arrondir au-dessus sur une égalité, comme un décompte qu’un système en aval comparera à une limite.

La valeur 1.6 est le témoin de chaque ligne. Ce n’est pas une égalité, donc tout mode arrondissant au plus proche donne 2 et seuls les modes tronquants donnent 1. Un test qui formate une valeur non-égalité et une égalité sépare donc un mode qui ne change que les égalités d’un mode qui change chaque valeur, et une page qui ne teste que 1.6 ne distingue pas halfExpand d’halfEven.

La conséquence pour une trace est que le mode lui appartient. Deux exécutions de la même app peuvent imprimer 2 et 3 pour le même 2.5 si un formateur a été construit une fois avec halfEven et une fois avec le défaut, et la chaîne stockée ne peut pas dire quel mode l’a produite. Une trace qui garde le roundingMode résolu à côté de chaque valeur formatée rend la différence reproductible au lieu d’un mystère qui n’apparaît que sur les valeurs qui se trouvent être des égalités.

Comment le signe et le zéro final changent la chaîne

Deux options d’affichage changent la largeur et le signe de la sortie sans changer la valeur, et les lignes de signe montrent un cas où le signe se perd.

sign:auto -> 5,0,-5 est le défaut : aucun signe sur un positif ni un zéro, un moins sur un négatif. sign:always -> +5,+0,-5 met un plus sur le positif et sur le zéro, donc un lecteur peut voir qu’un nombre a été formaté avec un signe, et un diff de deux rapports peut distinguer un positif inchangé d’un négatif inchangé. sign:exceptZero -> +5,0,-5 met le plus sur un positif mais laisse le zéro nu, ce qui est l’option à choisir quand un zéro est une vraie valeur qui ne doit pas paraître signée.

sign:never -> 5,0,5 est la dangereuse. Elle retire le moins de -5 et imprime 5, donc un consommateur qui analyse la chaîne en nombre obtient une valeur positive d’une entrée négative. Une page qui rend un delta avec sign:never en supposant que « le signe est affiché ailleurs » produit deux chaînes identiques pour +5 et -5, et une analyse en aval ne peut pas récupérer laquelle c’était. sign:negative -> 5,0,-5 montre un moins seulement quand la valeur est négative, ce qui est l’intention habituelle derrière le recours à never, et garde le signe sur le négatif.

L’option de zéro final change les décimales qui n’existent qu’à cause d’un minimum. La sonde imprime trailing:default -> 3.00 3.10 pour un formateur à minimum et maximum de deux chiffres, et trailing:strip -> 3 3.10 après stripIfInteger. Un nombre entier perd donc sa fraction de zéros tandis que 3.10 garde son zéro final, parce que 3.10 n’est pas entier et seul le cas entier est retiré. Une colonne de prix qui utilise le défaut affiche 3.00 et 4.00, et la même colonne avec l’option de retrait affiche 3 à côté d’un 3.10 voisin, une colonne de largeur mixte qu’un tableau peut mal aligner.

Les deux options interagissent avec le signe et la partie fractionnaire d’une façon que la liste d’options ne déclare pas. stripIfInteger sur -3.00 donne -3, donc le moins est conservé ; sign:always sur -3.00 donne -3.00, donc le signe et la fraction sont indépendants. Une page qui compose les deux doit décider ce qu’elle veut pour -0, et le +0 de la sonde sous sign:always montre que le formateur imprimera un signe sur un zéro, la valeur qu’une soustraction de nombres égaux produit souvent.

La largeur de la sortie n’est donc pas une propriété du seul nombre. Le même 5 se rend comme 5, +5, 5.00 ou +5.00 selon trois options, et une mise en page qui réserve une largeur de colonne en mesurant une variante rognera ou décalera les autres. Une page qui formate un tableau doit fixer les options une fois et les utiliser pour chaque cellule, plutôt que laisser un défaut et une option explicite se rencontrer dans la même colonne.

Où deux limites se heurtent

Deux options peuvent limiter les chiffres à la fois, et laquelle gagne dépend d’une troisième option que presque aucun code ne fixe. La sonde combine maximumSignificantDigits: 3 avec maximumFractionDigits: 2 et formate trois valeurs sous chaque roundingPriority ; la transcription consigne ces résultats sur la ligne priority ->.

auto et lessPrecision s’accordent sur chaque valeur : 12.345 devient 12.3, 1234.5 devient 1230 et 456.78 devient 457. Les deux respectent la limite de chiffres significatifs et considèrent la limite de fraction satisfaite par le résultat, donc la règle de trois chiffres gagne et la règle de deux décimales ne mord jamais. morePrecision est l’exception : il imprime 12.35, 1234.5 et 456.78, ce qui garde plus de chiffres qu’aucune des deux limites seule ne le permettrait, parce qu’il arrondit pour que les deux limites soient satisfaites au lieu de laisser l’une dominer.

Le cas 1234.5 rend la surprise visible. Sous auto le nombre devient 1230, trois chiffres significatifs et pas de fraction, alors que sous morePrecision il reste 1234.5, quatre chiffres significatifs et une fraction. Une page qui fixe les deux limites en attendant « au plus 3 chiffres significatifs et au plus 2 décimales » obtient 1230 avec le défaut, et une page qui fixe morePrecision en attendant « la plus stricte des deux » obtient le résultat plus large. Aucune lecture des options ne correspond au comportement, donc la sortie résolue appartient à la trace et non l’intention.

Un formateur qui veut une limite doit fixer une limite. Fixer les deux et compter sur la priorité par défaut produit le comportement de chiffres significatifs, donc une page qui ne voulait que « deux décimales » doit fixer maximumFractionDigits seul, et une page qui ne voulait que « trois chiffres significatifs » doit fixer maximumSignificantDigits seul. La combinaison sert au cas où les deux doivent être respectées, et c’est ce que morePrecision déclare explicitement au lieu de le laisser au défaut.

Les lignes de devise montrent une seconde limite que l’appelant ne fixe jamais. currency -> $5.00 ¥5 imprime deux fractions pour un dollar américain et aucune pour un yen japonais, parce que les décimales par défaut viennent du code de la devise, pas de la langue ni des options. currency:min0 -> $5 montre que fixer minimumFractionDigits: 0 remplace le défaut du code, donc le symbole et les décimales sont deux décisions séparées. Une page qui imprime un tableau de prix entre devises obtient donc une largeur différente par devise, et la seule façon de savoir laquelle est de lire resolvedOptions() pour chaque code.

percent -> 13% 13% et compact -> 1.2M 1.2M montrent la même forme de surprise depuis une autre option. 0.125 en pourcentage vaut 12.5%, et le défaut l’arrondit à 13%, donc un taux attendu comme un demi-point devient un point entier. La notation compacte arrondit à des chiffres significatifs plutôt qu’à des fractions, donc 1234567 devient 1.2M avec le défaut comme avec un maximumFractionDigits: 1 explicite, parce que la notation limite déjà les chiffres avant le suffixe.

Les lignes digits et grouping rappellent que la valeur et la chaîne sont deux objets distincts, et ces mêmes données de langue décident aussi comment se rend un fuseau horaire à côté d’un nombre formaté. digits -> ar-EG=١٢٣٤ ar-EG-latn=1234 rend le même 1234 avec des chiffres arabo-indiens dans une langue et des chiffres latins avec une extension Unicode dans l’autre, et grouping -> de-DE=1.234.567,89 en-US=1,234,567.89 montre l’échange de séparateur qui vient de la seule langue. Une page qui analyse une chaîne formatée en nombre doit connaître les deux, et une page qui ne stocke que la chaîne a déjà perdu le nombre.

resolvedOptions() est le champ qui boucle. La sonde imprime resolved -> min=0 max=0 mode=halfExpand après n’avoir fixé qu’un maximum, donc le minimum a suivi le maximum jusqu’à zéro alors que l’appelant ne l’a jamais demandé. C’est la règle qu’une page rate le plus souvent quand elle fixe un maximum sur un formateur de devise : le minimum par défaut de deux de la devise est remplacé, pas conservé, et la différence entre $5.00 et $5 est une seule option que l’appelant n’a pas écrite.

formatToParts() est l’autre moitié de la trace. La sonde imprime parts -> minusSign:-|currency:USD|literal: |integer:5|decimal:.|fraction:00, donc chaque pièce de la sortie est nommée et un consommateur peut reconstruire la chaîne ou réordonner les pièces en connaissant le type de chacune. Une page qui rend un prix avec la devise après le nombre dans une langue et avant dans une autre peut le faire depuis les pièces plutôt que depuis la chaîne entière, ce qui est la façon fiable de gérer une langue dont l’ordre diffère.

Consigner une lecture de format numérique

Une lecture de format numérique soutient une affirmation bornée : ce moteur, cette langue, ces options ont produit cette chaîne. Quatre champs la gardent vérifiable :

  1. Les options résolues, parce que resolvedOptions() nomme le mode d’arrondi, le minimum et le maximum qui ont réellement tourné, et l’intention de l’appelant n’est pas dans la chaîne.
  2. Le mode d’arrondi et les valeurs d’entrée, parce qu’une égalité et une non-égalité peuvent différer d’une unité entière et une trace sans l’entrée ne peut pas dire laquelle est arrivée.
  3. Les options d’affichage du signe et de la fraction, parce que sign:never efface un moins et stripIfInteger efface une fraction de zéros, et aucune ne se récupère de la sortie.
  4. Les pièces, parce que l’ordre et les pièces de la sortie d’une langue diffèrent, et une chaîne stockée ne se réanalyse pas sans la langue qui l’a produite.

Sources publiques

Conclusion pratique

Fixez le mode d’arrondi au lieu de faire confiance au défaut, parce que le défaut est half-expand et qu’une valeur .5 ne s’arrondira pas comme l’attend un lecteur de « arrondir au pair ». Gardez le signe moins avec sign:negative plutôt que sign:never, parce que never transforme un négatif en chaîne positive qu’un analyseur ne peut pas récupérer. Choisissez une limite de chiffres plutôt que deux sauf si les deux doivent mordre, et lisez resolvedOptions() après avoir construit un formateur de devise parce qu’un maximum abaisse aussi le minimum. Pour les données de langue qui gouvernent les séparateurs et les formes de chiffres, lisez données de langue de l’API Intl ; pour la moitié fuseau horaire de la même famille de formateurs, lisez fuseaux horaires et langue.

#Intl.NumberFormat#Arrondi#Localisation#Devise#ECMAScript

Cet article fait partie du guide : Qu'est-ce qu'une empreinte de navigateur ?

Faites passer BotBrowser de la recherche à la production

Utilisez ces guides pour comprendre le modèle, puis passez à la validation multi-plateforme, aux contextes isolés et au déploiement navigateur prêt pour l'échelle.