/**
* Vérifie si le nombre est bien écrit (ne gère pas l'écriture scientifique 1e3)
* L'ancienne syntaxe `verifNombreBienEcrit (nombreStr, checkSpace, checkUselessZero, ignoreVirguleInutile)` fonctionne aussi
* @param {string} nombreStr L'écriture du nombre à vérifier
* @param {Object} [options]
* @param {boolean} [options.checkSpace=true] Passer false pour ignorer les espaces (ils seront tolérés n'importe où)
* @param {boolean} [options.checkUselessZero=true] Passer false pour ignorer les zéros inutiles
* @param {boolean} [options.ignoreVirguleInutile=false] Passer true pour tolérer une virgule sans partie décimale
* @param {boolean} [options.laxStartingSpace=false] Passer true pour tolérer des espaces au début (avant ou après le signe)
* @returns {{good: boolean, erreur: string, remede: string, nb: number|undefined}} nb fourni si good
*/
export function verifNombreBienEcrit (nombreStr, options) {
let checkSpace, checkUselessZero, ignoreVirguleInutile, laxStartingSpace, laxEndingSpace
if (arguments.length > 2) {
checkSpace = Boolean(arguments[1])
checkUselessZero = Boolean(arguments[2])
ignoreVirguleInutile = Boolean(arguments[3])
} else if (typeof options === 'boolean') {
checkSpace = Boolean(arguments[1])
checkUselessZero = false
ignoreVirguleInutile = false
} else {
// options en objet, les valeurs par défaut deviennent true
({ checkSpace = true, checkUselessZero = true, ignoreVirguleInutile = false, laxStartingSpace = false, laxEndingSpace = false } = options ?? {})
}
if (typeof nombreStr === 'number') {
console.warn(TypeError('Il faut appeler cette fonction avec une string'))
nombreStr = String(nombreStr)
}
if (typeof nombreStr !== 'string') {
throw Error(`nombre invalide : ${nombreStr}`)
}
if (/\de[+-]?\d+/.test(nombreStr)) {
// c'est un nombre en écriture scientifique, on ne le gère pas ici
throw RangeError(`Nombre hors du champ d’analyse : ${nombreStr}`)
}
nombreStr = nombreStr
// on remplace le(s) point(s) par une virgule
.replace(/\./g, ',')
// et d'éventuel espaces fins insécables mis par les ZonesStyleMathquill
// eslint-disable-next-line no-irregular-whitespace
.replace(/ /g, ' ')
// on nettoie les espaces qu'on nous demande d'ignorer
if (!checkSpace) {
nombreStr = nombreStr.replace(/ /g, '')
}
if (laxStartingSpace) {
nombreStr = nombreStr.replace(/^ *([+-]?) */, '$1')
}
if (laxEndingSpace) {
nombreStr = nombreStr.replace(/ +$/, '')
}
// pas d'espace au début
if (nombreStr.startsWith(' ')) {
return {
good: false,
erreur: 'espaceDebut',
remede: 'Il ne doit pas y avoir d’espace au début du nombre !'
}
}
// récup du signe, on cherche pas plus loin si y'en a plusieurs
if (/[+-].*[+-]/.test(nombreStr)) {
return {
good: false,
erreur: 'pbSigne',
remede: 'Un nombre ne peut pas avoir deux signes !'
}
}
// check si le signe éventuel est bien au début (on ignore les espaces avant traités ensuite)
const isNegatif = nombreStr.startsWith('-')
if (isNegatif) {
nombreStr = nombreStr.substring(1)
} else if (nombreStr.startsWith('+')) {
nombreStr = nombreStr.substring(1)
}
if (/[+-]/.test(nombreStr)) {
return {
good: false,
erreur: 'pbSigne',
remede: 'Le signe n’est pas au début du nombre !'
}
}
// nombreStr est désormais sans signe
if (nombreStr.startsWith(' ')) {
return {
good: false,
erreur: 'espaceDebut',
remede: 'Il ne doit pas y avoir d’espace entre le signe et le nombre !'
}
}
if (nombreStr.endsWith(' ')) {
return {
good: false,
erreur: 'espaceFin',
remede: 'Il ne doit pas y avoir d’espace à la fin du nombre !'
}
}
const intrus = nombreStr.replace(/[ 0-9,]/g, '')
if (intrus) {
const pl = intrus.length > 1 ? 's' : ''
return {
good: false,
erreur: 'intrus',
remede: `Ce${pl} caractère${pl} ne peu${pl ? 'ven' : ''}t pas être dans un nombre : ${intrus}`
}
}
// ok, nb sans signe et sans espace aux extrémités
if (nombreStr.includes(' ')) {
return {
good: false,
erreur: 'espaceDouble',
remede: 'Tu as mis 2 espaces côte à côte !'
}
}
// on passe à la virgule
if (nombreStr.indexOf(',') !== nombreStr.lastIndexOf(',')) {
return {
good: false,
erreur: 'virguleMultiple',
remede: 'Il ne peut y avoir qu’une seule virgule dans un nombre !'
}
}
// position de la virgule
if (nombreStr.startsWith(',')) {
return {
good: false,
erreur: 'virguleDebut',
remede: 'Un nombre ne peut pas commencer par une virgule !'
}
}
let [partieEntiere, partieDecimale] = nombreStr.split(',')
if (partieDecimale == null) partieDecimale = ''
if (!checkUselessZero) {
// pour la partie entière faut garder le dernier si c'est le seul
if (partieEntiere.startsWith('0')) {
partieEntiere = partieEntiere.replace(/^0+/, '')
if (!partieEntiere) partieEntiere = '0'
}
partieDecimale = partieDecimale.replace(/0+$/, '')
}
if ((partieEntiere.startsWith('0') && partieEntiere.length > 1) || partieDecimale.endsWith('0')) {
return {
good: false,
erreur: 'zeroInutile',
remede: 'Tu dois supprimer tous les zéros inutiles !'
}
}
if (!ignoreVirguleInutile && /,[ 0]*$/.test(nombreStr)) {
return {
good: false,
erreur: 'virguleInutile',
remede: 'Pas besoin de virgule quand la partie décimale est nulle !'
}
}
if (partieEntiere.endsWith(' ')) {
return {
good: false,
erreur: 'espaceFinEnt',
remede: 'Il ne doit pas y avoir d’espace à la fin de la partie entière !'
}
}
if (partieDecimale.startsWith(' ')) {
return {
good: false,
erreur: 'espaceDebDec',
remede: 'Il ne doit pas y avoir d’espace après la virgule !'
}
}
if (checkSpace) {
// test que les espaces sont bien les séparateurs de milliers
let comptgrp = 0
let comptgrpold = 3
let ing = false
// on part de la droite pour la partie entière
for (let i = partieEntiere.length - 1; i > -1; i--) {
if (partieEntiere[i] === ' ') {
if (ing) {
comptgrpold = comptgrp
ing = false
}
comptgrp = 0
} else {
comptgrp++
ing = true
if ((comptgrpold !== 3) || (comptgrp > 3)) {
return {
good: false,
erreur: 'espaceEnt',
remede: 'Dans la partie entière, les chiffres doivent être regroupés par 3 en commençant par la droite !'
}
}
}
}
comptgrp = 0
comptgrpold = 3
ing = false
for (const char of partieDecimale) {
if (char === ' ') {
if (ing) {
comptgrpold = comptgrp
}
ing = false
comptgrp = 0
} else {
comptgrp++
ing = true
if ((comptgrpold !== 3) || (comptgrp > 3)) {
return {
good: false,
erreur: 'espaceDec',
remede: 'Dans la partie décimale, les chiffres doivent être regroupés par 3 en commençant par la gauche !'
}
}
}
}
}
// recolle tout
const nbStrCompact = (isNegatif ? '-' : '') + nombreStr
.replace(',', '.')
.replace(/ /g, '')
return {
good: true,
nb: parseFloat(nbStrCompact),
erreur: '',
remede: ''
}
}
/**
* Formate un nombre en string, avec séparateur de milliers
* @param {number} nb
* @returns {string}
*/
export function formateNombre (nb) {
if (!Number.isFinite(nb)) {
console.error(Error('nombre invalide'), nb)
return ''
}
let signe = ''
let entier = false
let nbChaine = String(nb)
// on gére le cas où nb serait hors scope (1e42 ou 1e-42)
if (nbChaine.includes('e')) {
if (nbChaine.includes('e-')) {
console.error(Error(`nombre trop petit ${nbChaine}`))
} else {
console.error(Error(`nombre trop grand ${nbChaine}`))
}
return nbChaine
}
let pEntiereFormate = ''
let decimaleFormate = ''
if (nb < 0) {
// si le nombre est négatif on récupère le signe"-"
nbChaine = nbChaine.substring(1)
signe = '-'
}
let [pEntiere, decimale] = nbChaine.split('.')
if (decimale == null) {
// le nombre est un entier
decimale = ''
entier = true
}
if (pEntiere.length > 3) {
// formatage de la partie entière
const q = Math.floor(pEntiere.length / 3)
const r = pEntiere.length % 3
if (r === 0) {
pEntiereFormate += pEntiere.substring(0, 3)
for (let i = 1; i < q; i++) {
pEntiereFormate += ' ' + pEntiere.substring(i * 3, (i + 1) * 3)
}
} else {
pEntiereFormate += pEntiere.substring(0, r)
const chaineTemp = pEntiere.substring(r)
for (let i = 0; i < q; i++) {
pEntiereFormate += ' ' + chaineTemp.substring(i * 3, (i + 1) * 3)
}
}
} else {
pEntiereFormate = pEntiere
}
// fin du formatage de la partie entière
if (!entier) {
// formatage des décimales
if (decimale.length > 3) {
decimaleFormate += decimale.substring(0, 3)
const q1 = Math.floor(decimale.length / 3)
const r1 = decimale.length % 3
if (r1 === 0) {
for (let i = 1; i < q1; i++) {
decimaleFormate += ' ' + decimale.substring(i * 3, (i + 1) * 3)
}
} else {
const chaineTemp1 = decimale.substring(3 * q1)
for (let i = 1; i < q1; i++) {
decimaleFormate += ' ' + decimale.substring(i * 3, (i + 1) * 3)
}
decimaleFormate += ' ' + chaineTemp1
}
} else decimaleFormate = decimale
}
// fin du formatage des décimales
if (entier) {
return signe + pEntiereFormate
}
return signe + pEntiereFormate + ',' + decimaleFormate
}