Excel & outils métier

Formules Excel en JavaScript : les recréer sans perdre en précision

Recréer des formules Excel en JavaScript : virgule flottante, ARRONDI et Math.round, dates et 29/02/1900, RECHERCHEV, MOD, tests de non-régression.

L’essentiel

  • Excel et JavaScript stockent les nombres dans le même format, la double précision IEEE 754, mais ils ne calculent pas tout de la même façon : arrondis, dates, recherches et restes de division diffèrent.
  • ARRONDI arrondit les demis en s’éloignant de zéro (=ARRONDI(-1,475; 2) donne -1,48), alors que Math.round arrondit un demi vers +∞ : il faut une fonction d’arrondi dédiée, utilisée partout.
  • Une date Excel est un numéro de série compté depuis le 1er janvier 1900, qui inclut un 29 février 1900 fictif hérité de Lotus 1-2-3 : la conversion doit en tenir compte et se faire en UTC.
  • RECHERCHEV devient une Map pour la correspondance exacte, ou une recherche du dernier palier atteint pour la correspondance approximative ; les SI imbriqués deviennent des fonctions nommées.
  • La seule preuve d’équivalence est un jeu de cas calculés par le classeur et comparés automatiquement aux résultats du code, avec une tolérance fixée à l’avance.
Sommaire8 parties
  1. Même format de nombres, mais pas le même calcul
  2. ARRONDI n’est pas Math.round
  3. Les dates : un numéro de série et un 29 février 1900 fictif
  4. RECHERCHEV et RECHERCHEX deviennent des tables de correspondance
  5. SI imbriqués, MOD, ENT, SOMME.SI : les autres traductions
  6. Prouver l’équivalence : les tests de non-régression
  7. En résumé : traduire les règles, pas seulement les formules
  8. Questions fréquentes

Recréer une formule Excel en JavaScript paraît trivial : =A2*B2 devient a * b. Les écarts apparaissent ailleurs, dans les arrondis, les dates, les recherches et les restes de division, là où les deux outils n’appliquent pas les mêmes règles. Or un centime d’écart sur un devis suffit pour que les utilisateurs ne fassent plus confiance à l’outil.

C’est l’exigence centrale quand on transforme un classeur en application. Chez mon employeur, j’ai converti des classeurs Excel complexes en mini-sites intranet locaux en gardant la même logique de calcul et les mêmes formules. Voici les différences qui comptent entre Excel et JavaScript, le code pour les neutraliser, vérifié avec Node.js, et la méthode pour prouver que l’application donne les mêmes résultats que le classeur.

Même format de nombres, mais pas le même calcul

Excel et JavaScript stockent les nombres au format double précision défini par la norme IEEE 754 (Microsoft, MDN). Ils partagent donc les mêmes approximations : 0,1 n’a pas de représentation binaire exacte, et en JavaScript 0.1 + 0.2 vaut 0.30000000000000004. La différence tient à ce qu’Excel fait autour de ce format :

  • 15 chiffres significatifs : Excel limite la précision à 15 chiffres, quand un nombre JavaScript en porte environ 15 à 17. Microsoft cite =(43,1-43,2)+1, qui s’affiche 0,899999999999999 avec 15 décimales ; en JavaScript, le même calcul donne 0.8999999999999986.
  • Les quasi-zéros corrigés : depuis Excel 97, quand une addition ou une soustraction donne un résultat très proche de zéro, Excel tente de compenser l’erreur de conversion binaire pour afficher 0. JavaScript ne fait rien de tel.
  • La précision au format affiché : si cette option des paramètres avancés est activée dans le classeur, Excel remplace chaque valeur par sa valeur affichée. Le code doit alors arrondir aux mêmes endroits, sous peine d’écarts systématiques.
  • Les identifiants longs : Excel peut tronquer à 15 chiffres un long nombre collé ou importé, par exemple depuis un CSV (Microsoft), et JavaScript ne garantit des entiers exacts que jusqu’à Number.MAX_SAFE_INTEGER, soit 9 007 199 254 740 991. Numéros de compte, codes et références restent du texte, des deux côtés.

Conséquence pratique : on ne compare jamais deux résultats décimaux avec === sans les avoir arrondis, et on arrondit exactement aux mêmes étapes que le classeur. S’il arrondit chaque ligne avant de faire le total, le code fait de même ; s’il n’arrondit que le total, aussi.

ARRONDI n’est pas Math.round

La fonction ARRONDI d’Excel arrondit les demis en s’éloignant de zéro : selon la documentation de Microsoft, =ARRONDI(-1,475; 2) donne -1,48. Math.round, lui, arrondit un demi exact vers +∞ : Math.round(-2.5) vaut -2, et MDN précise que ce comportement diffère de celui de nombreux langages (MDN). S’y ajoute le problème des décimales : 1,005 est stocké comme 1,00499999999999989…, donc Math.round(1.005 * 100) / 100 donne 1 au lieu du 1,01 attendu.

Les exemples de la documentation ARRONDI, recalculés en JavaScript
Formule ExcelRésultat ExcelCalcul naïf : Math.round(x * 10 ** n) / 10 ** nFonction arrondi(x, n)
=ARRONDI(2,15; 1)2,22,22,2
=ARRONDI(2,149; 1)2,12,12,1
=ARRONDI(-1,475; 2)-1,48-1,47-1,48
=ARRONDI(21,5; -1)202020
=ARRONDI(626,3; -3)1 0001 0001 000

La fonction suivante reproduit ces résultats. Elle tient en trois lignes, dans function arrondi(x, n = 0) { … } :

  1. const [m, e] = Math.abs(x).toPrecision(15).split('e'); ramène le nombre à 15 chiffres significatifs, comme Excel : 1,005 redevient 1,00500000000000 ;
  2. const k = Math.round(Number(m + 'e' + ((Number(e) || 0) + n))); déplace la virgule par la notation exponentielle, sans multiplication, puis arrondit un nombre positif ;
  3. return (Math.sign(x) * (n >= 0 ? k / 10 ** n : k * 10 ** -n)) || 0; remet le signe, ce qui revient à arrondir les demis en s’éloignant de zéro, et || 0 évite d’obtenir -0.

Vérifiée avec Node.js, elle donne les résultats de la documentation de Microsoft, 1,01 pour 1,005 et -3 pour -2,5. Elle ne prétend pas imiter Excel au bit près dans tous les cas : seule la comparaison avec votre classeur le prouve, comme on le verra plus bas.

Pour l’affichage, Intl.NumberFormat arrondit par défaut les demis en s’éloignant de zéro, avec l’option roundingMode réglée sur 'halfExpand' (MDN). Mais il produit du texte : gardez arrondi() pour les calculs. Évitez aussi toFixed() : MDN rappelle que (2.55).toFixed(1) renvoie '2.5' (MDN).

Les dates : un numéro de série et un 29 février 1900 fictif

Pour Excel, une date est un nombre. Le 1er janvier 1900 porte le numéro de série 1, et le 1er janvier 2008 le numéro 39448 (Microsoft). La partie décimale représente l’heure : 0,5 correspond à midi (Microsoft). Les anciennes versions d’Excel pour Mac utilisaient un système partant de 1904, décalé de 1 462 jours (Microsoft) : vérifiez lequel utilise votre classeur.

Ce système contient une anomalie connue : Excel considère 1900 comme une année bissextile, ce qu’elle n’est pas. Microsoft l’explique par la compatibilité avec Lotus 1-2-3, qui faisait la même hypothèse, et ne la corrige pas, car presque toutes les dates des classeurs existants seraient décalées d’un jour (Microsoft Learn). Le numéro 60 désigne donc un 29 février 1900 qui n’a jamais existé, et la fonction JOURSEM renvoie des valeurs fausses avant le 1er mars 1900.

Pour convertir, on s’appuie sur le numéro 25569, qui correspond au 1er janvier 1970, l’origine des dates en JavaScript, et on décale d’un jour avant le 1er mars 1900 :

  • const dateDepuisExcel = (n) => n === 60 ? null : new Date((n - (n < 60 ? 25568 : 25569)) * 86400000);
  • const serieExcel = (d) => d.getTime() / 86400000 + 25569;, pour les dates à partir du 1er mars 1900.

Avec ces deux fonctions, 39448 donne bien le 1er janvier 2008, et 61 le 1er mars 1900. Un piège reste : le fuseau horaire. new Date(2011, 6, 5) crée le 5 juillet à minuit, heure locale, soit la veille à 22 h en temps universel l’été en France ; le numéro de série obtenu tombe alors à 40728,9166… Travaillez en UTC de bout en bout : new Date(Date.UTC(2011, 6, 5)) pour créer une date, en se souvenant que les mois commencent à 0 (MDN), et d.toLocaleDateString('fr-FR', { timeZone: 'UTC' }) pour l’afficher.

RECHERCHEV et RECHERCHEX deviennent des tables de correspondance

RECHERCHEV cherche une valeur dans la première colonne d’une table. Son quatrième argument décide de tout : FAUX exige une correspondance exacte, et s’il est omis, Excel applique une correspondance approximative qui suppose une première colonne triée (Microsoft). Un FAUX oublié sur une table non triée renvoie une valeur fausse plutôt qu’une erreur. RECHERCHEX cherche une correspondance exacte par défaut, mais elle n’existe pas dans Excel 2016 et 2019 (Microsoft).

Correspondance exacte : une Map

On construit la table une fois, au chargement : const prix = new Map(articles.map((a) => [a.code.trim().toUpperCase(), a.prix]));. La recherche devient prix.get(code.trim().toUpperCase()). Deux précautions :

  • Excel compare les textes sans tenir compte des majuscules, comme le précise la documentation d’EQUIV, alors qu’une Map distingue « a12 » de « A12 » : normalisez les clés des deux côtés ;
  • get() renvoie undefined quand la valeur est absente, là où Excel affiche #N/A. Traitez ce cas explicitement, avec un message à l’écran, plutôt que de laisser un undefined devenir un NaN plus loin dans le calcul.

Correspondance approximative : le dernier palier atteint

Pour un barème par tranches, EQUIV en mode 1 recherche « la plus grande valeur qui est inférieure ou égale » à la valeur cherchée, dans une liste triée par ordre croissant : c’est le principe de la correspondance approximative. En JavaScript, sur un tableau trié par seuil croissant : const palier = paliers.findLast((p) => p.seuil <= montant);. La méthode findLast est disponible dans les navigateurs récents et dans Node.js depuis la version 18 (MDN). Un montant inférieur au premier seuil renvoie undefined, l’équivalent du #N/A. Mieux qu’Excel, le code peut vérifier au chargement que la table est bien triée, et refuser de calculer sinon.

SI imbriqués, MOD, ENT, SOMME.SI : les autres traductions

Équivalents JavaScript de formules Excel courantes
ExcelJavaScriptLe piège
=SI(A2>=5000;5%;SI(A2>=1000;2%;0))function tauxRemise(m) { if (m >= 5000) return 0.05; if (m >= 1000) return 0.02; return 0; }L’ordre des conditions compte : testez les seuils du plus haut au plus bas, et donnez un nom à la règle
=MOD(n; d)n - d * Math.floor(n / d)-3 % 2 vaut -1, alors que =MOD(-3; 2) vaut 1 : le résultat de MOD a le signe du diviseur
=ENT(x)Math.floor(x)ENT arrondit vers le bas : =ENT(-8,9) donne -9, là où Math.trunc donnerait -8
=SOMME.SI(plage; critère; somme_plage)lignes.filter((l) => l.famille === 'A').reduce((s, l) => s + l.montant, 0)Une seule valeur non numérique donne NaN et contamine tout le total
=SIERREUR(calcul; 0)Un test explicite, par exemple Number.isFinite(resultat)Remplacer une erreur par 0 masque les problèmes : affichez plutôt un message

Les règles de MOD et d’ENT viennent de la documentation de Microsoft (MOD, ENT) ; l’opérateur % de JavaScript, lui, garde le signe du dividende (MDN).

Les saisies demandent la même rigueur. En JavaScript, Number('') vaut 0, si bien qu’un champ vide passe pour un zéro, et Number('12abc') vaut NaN. Les nombres tapés à la française, comme « 1 234,56 », doivent être convertis : Number(texte.replace(/\s/g, '').replace(',', '.')). Contrôlez chaque champ au moment de la saisie, avec un message clair, plutôt qu’au moment du calcul.

Prouver l’équivalence : les tests de non-régression

Aucune relecture ne garantit qu’une formule a été bien traduite. La preuve vient d’une comparaison systématique entre le classeur et le code, sur des cas réels :

  1. Créer une feuille de tests dans le classeur d’origine : une ligne par cas, avec les entrées et les résultats calculés par Excel. Prévoyez des cas courants, des cas limites (zéro, valeurs négatives, seuils exacts de barème, fins de mois) et les cas qui ont déjà posé problème.
  2. Exporter cette feuille en CSV UTF-8. Attention : ce format enregistre le texte et les valeurs « tels qu’ils sont affichés dans les cellules » (Microsoft). Affichez assez de décimales dans les colonnes de résultats avant l’export, sinon vous comparerez des valeurs déjà arrondies.
  3. Écrire un script de comparaison qui lit ce fichier, appelle les mêmes fonctions que l’application et compare chaque résultat. Le lanceur de tests intégré à Node.js, node --test, suffit, sans aucune dépendance (Node.js).
  4. Fixer la tolérance à l’avance : égalité stricte pour les montants arrondis au centime, tolérance relative pour les valeurs intermédiaires, par exemple const egal = (a, b, tol = 1e-9) => Math.abs(a - b) <= tol * Math.max(1, Math.abs(a), Math.abs(b));, et comparaison des dates sous forme de numéros de série.
  5. Expliquer chaque écart : erreur de traduction à corriger, ou erreur découverte dans le classeur, à trancher avec le métier.

Ce jeu de tests ne sert pas qu’une fois. Conservé avec l’application et relancé à chaque modification d’un barème ou d’une formule, il signale une régression avant que les utilisateurs ne la voient.

Il arrive que la comparaison révèle une erreur dans le classeur lui-même, comme une plage qui s’arrête trop tôt. Ce n’est pas rare : les études sur les erreurs dans les fichiers Excel en trouvent dans la plupart des gros classeurs.

En résumé : traduire les règles, pas seulement les formules

Recréer des formules Excel en JavaScript, c’est reproduire les règles implicites du tableur : arrondi des demis en s’éloignant de zéro, 15 chiffres significatifs, numéros de série des dates et leur 29 février 1900, recherches insensibles à la casse, signe du reste de MOD. Regroupez ces règles dans quelques fonctions utilitaires testées, utilisez-les partout, et prouvez l’équivalence par une comparaison automatique avec le classeur. Pour l’application qui accueille ces calculs, voyez un intranet local en HTML et JavaScript et la méthode complète dans transformer un fichier Excel en application web. Si vous préférez confier ce travail, c’est l’objet de mon accompagnement Excel en application web.

Questions fréquentes

Comment reproduire la fonction ARRONDI d’Excel en JavaScript ?

Math.round ne suffit pas : il arrondit un demi vers +∞, et multiplier par 100 introduit des erreurs, 1,005 × 100 donnant 100,49999999999999. Ramenez d’abord le nombre à 15 chiffres significatifs avec toPrecision(15), déplacez la virgule par la notation exponentielle, arrondissez la valeur absolue puis remettez le signe. Vérifiez ensuite le résultat sur des cas calculés par votre classeur.

Comment convertir une date Excel en date JavaScript ?

Pour une date à partir du 1er mars 1900, retirez 25569 au numéro de série et multipliez par 86 400 000 pour obtenir des millisecondes depuis le 1er janvier 1970 : new Date((n - 25569) * 86400000). Avant cette date, le décalage est de 25568, et le numéro 60 correspond à un 29 février 1900 qui n’existe pas. Travaillez en UTC et vérifiez que le classeur n’utilise pas le système de dates 1904.

Comment faire une RECHERCHEV en JavaScript ?

Pour une correspondance exacte, construisez une Map à partir de la table, avec des clés normalisées en majuscules, puisque Excel ne distingue pas la casse. Pour une correspondance approximative, triez la table par seuil croissant et cherchez le dernier seuil inférieur ou égal à la valeur avec findLast. Dans les deux cas, traitez explicitement l’absence de résultat, l’équivalent du #N/A.

Pourquoi 0,1 + 0,2 ne donne-t-il pas 0,3 en JavaScript ?

Parce que 0,1 et 0,2 n’ont pas de représentation binaire exacte au format IEEE 754, que JavaScript utilise pour tous ses nombres : la somme vaut 0.30000000000000004. Excel utilise le même format, mais limite l’affichage et les calculs à 15 chiffres significatifs et corrige certains résultats proches de zéro. Comparez donc les résultats avec une tolérance, ou après arrondi.

Comment vérifier que mon code donne les mêmes résultats qu’Excel ?

Créez dans le classeur une feuille de cas de test avec leurs résultats, exportez-la en CSV en affichant assez de décimales, puis comparez automatiquement chaque résultat à celui de vos fonctions JavaScript, par exemple avec node --test. Fixez la tolérance à l’avance et expliquez chaque écart avant la mise en service.

Sources

  1. L’arithmétique à virgule flottante peut donner un résultat incorrect dans ExcelMicrosoft Learn, 30 mars 2026
  2. NumberMDN Web Docs
  3. Définir des conversions automatiques de donnéesMicrosoft Support
  4. ARRONDI (fonction)Microsoft Support
  5. Math.round()MDN Web Docs
  6. Intl.NumberFormat() constructor (option roundingMode)MDN Web Docs
  7. Number.prototype.toFixed()MDN Web Docs
  8. DATE (fonction)Microsoft Support
  9. TEMPS (fonction)Microsoft Support
  10. Systèmes de date dans ExcelMicrosoft Support
  11. Excel suppose incorrectement que l’année 1900 est une année bissextileMicrosoft Learn, 30 mars 2026
  12. Date.UTC()MDN Web Docs
  13. RECHERCHEV (fonction)Microsoft Support
  14. RECHERCHEX (fonction)Microsoft Support
  15. EQUIV (fonction)Microsoft Support
  16. MapMDN Web Docs
  17. Array.prototype.findLast()MDN Web Docs
  18. MOD (fonction)Microsoft Support
  19. ENT (fonction)Microsoft Support
  20. Reste (%)MDN Web Docs
  21. Mise en forme d’Excel et fonctionnalités non transférées à d’autres formats de fichierMicrosoft Support
  22. Test runnerNode.js