legacy/outils/fonctions/etude.js

import $ from 'jquery'
import { j3pAddElt, j3pElement, j3pEmpty, j3pFocus, j3pGetRandomInt, j3pMathquillXcas, j3pMonome, j3pNombre, j3pPaletteMathquill, j3pPGCD, j3pRandomTab, j3pStyle, j3pVirgule } from 'src/legacy/core/functions'
import { j3pExtraireNumDen, j3pGetLatexProduit, j3pGetLatexQuotient, j3pGetLatexSomme, j3pSimplificationRacineTrinome } from 'src/legacy/core/functionsLatex'
import { j3pCreeRectangle, j3pCreeSegment } from 'src/legacy/core/functionsSvg'
import { j3pCalculValeur } from 'src/legacy/core/functionsTarbre'
import Tarbre from 'src/legacy/outils/calculatrice/Tarbre'
import { ecritBienBorne, ecritBienFraction } from 'src/legacy/sections/lycee/fonctionsetude/sectionEtudeFonction_variations'
import { j3pAffiche, mqRestriction } from 'src/lib/mathquill/functions'

const epsilon = 1e-12

/**
 *
 * @private
 * @param typeOp
 * @param nb1Latex
 * @param nb2Latex
 * @param puis
 * @returns {string|*}
 */
function operationAvecRadical ({ typeOp, nb1Latex, nb2Latex, puis }) {
  /* obj est un objet contenant jusqu'à 3 propriétés
    - typeOp vaut "+", "-", "*", "/", "inv" (pour l’inverse), "^"
    - nb1Latex et nb2Latex qui sont des nombres écrits au format latex et correspondant à une expression de la forme (a+b*racine(c))/d
    - tabCoef1 et tabCoef2 qui sont des tableau contenant dans l’ordre a,b,c,d (c’est-à-dire les coefs des expressions des nombres)
    - puis est utile pour élever nb1 à une certaine puissance (il correspond à un entier naturel)
    tabCoef1 et tabCoef2 n’ont d’intérêt que si nb1Latex et nb2Latex n’ont pas été définis. Cela évite de perdre du temps dans la recherche des coefs quand on les a
    Cette fonction renvoie le résultat du calcul effectué entre nb1 et nb2 avec "l’opération" précisée
    */
  // à ce stade les nombres ne peuvent être qu’entiers, fractionnaires ou de la forme a+b\\sqrt{c} et \\frac{a+b\\sqrt{c}}{d}, a, b, c, d entiers
  // Attention également dans l’utilisation de cette fonction : le nombre sous le radical, s’il existe doit être le même pour les 2 nombres
  function extraireCoef (nbLatex) {
    // nbLatex est une chaîne latex représentant un nombre sous la forme (a+b*racine(c))/d
    // cette fonction me permet de récupérer les coefs pour faire les calculs ensuite
    if (!nbLatex.includes('sqrt')) {
      // il n’y a pas de raciFne carrée, cela va être simple car c’est un décimal ou un nb en écriture fractionnaires
      const nb = j3pGetLatexProduit(nbLatex, '1')// c’est pour mettre sous forme fractionnaire les nombres décimaux
      const tabFrac = j3pExtraireNumDen(nb)
      if (tabFrac[0]) {
        // on a un nombre en écriture fractionnaire
        return [tabFrac[1], 0, 0, tabFrac[2]]
      }
      // On a un nombre entier
      return [nb, 0, 0, 1]
    }
    // tout le boulot est là !
    let a, b, c, d, bInit
    let avantPlus, apresPlus
    const bracinec = /-?\d*\\sqrt{\d+}/g // pour trouver b\sqrt{c}
    const racinec = /\\sqrt{\d+}/g // pour trouver \sqrt{c}
    const abRacineC = /-?\d+[+-]\d*\\sqrt{\d+}/g // pour trouver a+b\sqrt{c}
    const bRacineCplusA = /-?\d*\\sqrt{\d+}[+-]\d+/g // pour trouver b\sqrt{c}+a
    const bRacineCsurD = /\\frac{-?\d*\\sqrt{\d+}}{\d+}/g // pour trouver \frac{b\sqrt{c}}{d}
    const bSurDracineC = /\\frac{-?\d+}{\d+}\\sqrt{\d+}/g // pour trouver \frac{b}{d}\sqrt{c}
    const abRacineCsurD = /\\frac{-?\d+[+-]\d*\\sqrt{\d+}}{\d+}/g // pour trouver \frac{a+b\sqrt{c}}{d}
    const bRacineCaSurD = /\\frac{-?\d*\\sqrt{\d+}[+-]\d+}{\d+}/g // pour trouver \frac{b\sqrt{c}+b}{d}
    if (abRacineCsurD.test(nbLatex) || bRacineCaSurD.test(nbLatex)) {
      // le nombre est de la forme \frac{a+b\sqrt{c}}{d} ou \frac{b\sqrt{c}+b}{d}
      // je récupère b\sqrt{c}
      const tabMatch = nbLatex.match(bracinec)// c’est un tableau de longueur 1
      c = tabMatch[0].substring(tabMatch[0].lastIndexOf('{') + 1, tabMatch[0].length - 1)
      bInit = tabMatch[0].replace('\\sqrt{' + c + '}', '')
      if (nbLatex[0] === '-') {
        // j’ai un facteur -1 devant la barre de fraction
        if (bInit[0] === '-') {
          bInit = bInit.substring(1)
        } else {
          bInit = '-' + bInit
        }
      }
      let aSurD = nbLatex.replace(tabMatch[0], '')// je vire b\\sqrt{c} de mon expression. Il me reste alors a/d
      // Je peux cependant avoir \\frac{a+}{d} ou \\frac{+a}{d}. Virons ce signe + en trop
      const posAccOuv = aSurD.indexOf('{')
      const posAccFer = aSurD.indexOf('}')
      if (aSurD[posAccOuv + 1] === '+') { // je dois virer ce + car j’ai \\frac{+a}{d}
        avantPlus = aSurD.substring(0, posAccOuv + 1)
        apresPlus = aSurD.substring(posAccOuv + 2)
        aSurD = avantPlus + apresPlus
      } else if (aSurD[posAccFer - 1] === '+') { // je dois virer ce + car j’ai \\frac{a+}{d}
        avantPlus = aSurD.substring(0, posAccFer - 1)
        apresPlus = aSurD.substring(posAccFer)
        aSurD = avantPlus + apresPlus
      }
      const tabFrac = j3pExtraireNumDen(aSurD)
      a = tabFrac[1]
      d = tabFrac[2]
    } else if (bSurDracineC.test(nbLatex) || bRacineCsurD.test(nbLatex)) {
      // le nombre est de la forme \\frac{b}{d}\\sqrt{c} ou \\frac{b\\sqrt{c}}{d}
      const racine = nbLatex.match(racinec)[0]
      const fraction = nbLatex.replace(racine, '')
      c = racine.substring(racine.indexOf('{') + 1, racine.length - 1)
      a = 0
      const tabFract = j3pExtraireNumDen(fraction)
      bInit = tabFract[1]
      d = tabFract[2]
    } else if (abRacineC.test(nbLatex) || bRacineCplusA.test(nbLatex)) {
      // le nombre est de la forme a+b\\sqrt{c} ou b\\sqrt{c}+a
      const racine = nbLatex.match(bracinec)[0]
      const coefA = nbLatex.replace(racine, '')
      if (coefA[0] === '+') {
        a = coefA.substring(1)
      } else if (coefA[coefA.length - 1] === '+') {
        a = coefA.substring(0, coefA.length - 1)
      } else {
        a = coefA
      }
      d = 1
      c = racine.substring(racine.indexOf('{') + 1, racine.length - 1)
      bInit = racine.replace('\\sqrt{' + c + '}', '')
    } else if (bracinec.test(nbLatex)) {
      // le nombre est de la forme b\\sqrt{c}
      a = 0
      d = 1
      c = nbLatex.substring(nbLatex.indexOf('{') + 1, nbLatex.length - 1)
      bInit = nbLatex.replace('\\sqrt{' + c + '}', '')
    }
    if (!bInit) {
      b = 1
    } else if (bInit === '-') {
      b = -1
    } else {
      b = bInit
    }
    return [a, b, c, d]
  }

  function sommeTabCoef (tab1, tab2) {
    // tab1 et tab2 sont des tableau contenant les 4 coefs de la forme (a+b*racine(c))/d
    // cette fonction renvoie un tableau sous la forme a2,b2,c2,d2 où (a2+b2*racine(c2)/d est le résultat de la somme des deux nombres représentés avec tab1 et tab2
    const a2 = j3pGetLatexSomme(j3pGetLatexProduit(tab1[0], tab2[3]), j3pGetLatexProduit(tab1[3], tab2[0]))
    const b2 = j3pGetLatexSomme(j3pGetLatexProduit(tab1[1], tab2[3]), j3pGetLatexProduit(tab1[3], tab2[1]))
    let c2 = tab1[2]
    if (Math.abs(Number(c2)) < epsilon) {
      c2 = tab2[2]
    }
    const d2 = j3pGetLatexProduit(tab1[3], tab2[3])
    return [a2, b2, c2, d2]
  }

  function produitTabCoef (tab1, tab2) {
    // tab1 et tab2 sont des tableau contenant les 4 coefs de la forme (a+b*racine(c))/d
    // cette fonction renvoie un tableau sous la forme a2,b2,c2,d2 où (a2+b2*racine(c2)/d est le résultat du produit des deux nombres représentés avec tab1 et tab2
    let c2 = tab1[2]
    if (Math.abs(Number(c2)) < epsilon) {
      c2 = tab2[2]
    }
    const a2 = j3pGetLatexSomme(j3pGetLatexProduit(tab1[0], tab2[0]), j3pGetLatexProduit(j3pGetLatexProduit(tab1[1], tab2[1]), c2))
    const b2 = j3pGetLatexSomme(j3pGetLatexProduit(tab1[1], tab2[0]), j3pGetLatexProduit(tab1[0], tab2[1]))
    const d2 = j3pGetLatexProduit(tab1[3], tab2[3])
    return [a2, b2, c2, d2]
  }

  function inverseTabCoef (tab1) {
    // tab1 et tab2 sont des tableau contenant les 4 coefs de la forme (a+b*racine(c))/d
    // cette fonction renvoie un tableau sous la forme a2,b2,c2,d2 où (a2+b2*racine(c2)/d est le résultat de l’inverse du nombre représenté avec tab1
    const a2 = j3pGetLatexProduit(tab1[0], tab1[3])
    const b2 = j3pGetLatexProduit('-1', j3pGetLatexProduit(tab1[1], tab1[3]))
    const c2 = tab1[2]
    const d2 = j3pGetLatexSomme(j3pGetLatexProduit(tab1[0], tab1[0]), j3pGetLatexProduit('-1', j3pGetLatexProduit(j3pGetLatexProduit(tab1[1], tab1[1]), tab1[2])))
    return [a2, b2, c2, d2]
  }

  function divisionTabCoef (tab1, tab2) {
    // tab1 et tab2 sont des tableau contenant les 4 coefs de la forme (a+b*racine(c))/d
    // cette fonction renvoie un tableau sous la forme a2,b2,c2,d2 où (-a2+b2*racine(c2)/d est le résultat du quotient des deux nombres représentés avec tab1 et tab2
    // j’ai choisi -a2 et non a2, de même que d/2 et non d, pour utiliser ensuite la fonction j3pSimplificationRacineTrinome(b,plusmoins,delta,a)
    return produitTabCoef(tab1, inverseTabCoef(tab2))
  }

  function puissanceTabCoef (tab, puis) {
    // tab est un tableau contenant les 4 coefs de la forme (a+b*racine(c))/d, puis est un entier naturel
    // cette fonction renvoie un tableau sous la forme a2,b2,c2,d2 où (-a2+b2*racine(c2)/d est le résultat de tab^puis
    // j’ai choisi -a2 et non a2, de même que d/2 et non d, pour utiliser ensuite la fonction j3pSimplificationRacineTrinome(b,plusmoins,delta,a)
    if (isAlmostZero(puis)) {
      return [1, 0, 0, 1]
    } else {
      return produitTabCoef(tab, puissanceTabCoef(tab, puis - 1))
    }
  }

  function ecrireNbAvecCoef (tab) {
    // tab est un tableau de 4 nombres [a,b,c,d] correspondant à (a+b*racine(c))/d
    // cette fonction renvoie l’écriture du nombre au format latex
    const b2val = j3pCalculValeur(tab[1])
    if (isAlmostZero(b2val)) {
      // b est nul, on se retrouve alors avec un nombre de la forme a/d
      return j3pGetLatexQuotient(tab[0], tab[3])
    } else {
      const sousRadical = j3pGetLatexProduit(tab[2], j3pGetLatexProduit(tab[1], tab[1]))
      // pour utiliser j3pSimplificationRacineTrinome, je ne dois pas entrer a mais -a, de même je dois entre d/2 au lieu de d
      const a = j3pGetLatexProduit('-1', tab[0])
      const d = j3pGetLatexQuotient(tab[3], 2)
      if (b2val < 0) {
        return j3pSimplificationRacineTrinome(a, '-', sousRadical, d)
      } else {
        return j3pSimplificationRacineTrinome(a, '+', sousRadical, d)
      }
    }
  }

  // La première chose à faire est d’extraire les coefs a,b,c,d de l’écriture (a+b*racine(c))/d sous la forme d’un tableau
  const tabCoef1 = extraireCoef(nb1Latex)
  let tabCoef2
  if (!typeOp.includes('inv') && typeOp !== '^') {
    // on ne demande ni l’inverse ni une puissance, donc on va chercher le deuxième nombre
    // pour l’inverse, il n’y a pas de deuxième nombre
    // pour la puissance, le deuxième nombre sera un entier naturel et ce sera nb2Latex
    tabCoef2 = extraireCoef(nb2Latex)
  } else {
    tabCoef2 = []
  }
  if (typeOp === '-') {
    // on a une soustraction. Cela revient à ajouter l’oppose
    return ecrireNbAvecCoef(sommeTabCoef(tabCoef1, produitTabCoef([-1, 0, 0, 1], tabCoef2)))
  }
  if (typeOp === '+') {
    // on a une somme
    return ecrireNbAvecCoef(sommeTabCoef(tabCoef1, tabCoef2))
  }
  if (typeOp === '*') {
    // on a un produit
    return ecrireNbAvecCoef(produitTabCoef(tabCoef1, tabCoef2))
  }
  if (typeOp === '/') {
    // on a un produit
    return ecrireNbAvecCoef(divisionTabCoef(tabCoef1, tabCoef2))
  }
  if (typeOp.includes('inv')) {
    // petite tolérance, car on peut écrire "inverse" et non seulement "inv"
    // on a l’inverse
    return ecrireNbAvecCoef(inverseTabCoef(tabCoef1))
  }
  if (typeOp === '^') {
    // on a nb1^nb2. Il faut absolument que nb2 soit un entier
    if (typeof puis === 'string') puis = Number(puis)
    return ecrireNbAvecCoef(puissanceTabCoef(tabCoef1, puis))
  }
  console.error(Error('Paramètres invalides'))
} // operationAvecRadical

// Fonctions communes aux sections sur les études de fonctions

/**
 *
 * @param maLimite
 * @returns {*|string}
 */
export function fctsEtudeEcritLimite (maLimite) {
  if (maLimite === '-infini') {
    return '-\\infty'
  }
  if (maLimite === '+infini') {
    return '+\\infty'
  }
  return fctsEtudeEcrireNbLatex(maLimite)
}

/**
 * À partir de domaine de définition détermine quelles sont les limites à calculer (en fonction des intervalles fermés ou ouverts)
 * on a un cas particulier avec les valeurs interdites et limites à droite et à gauche à gérer
 * @param objConteneur
 * @param debugs
 * @return {*}
 */
export function fctsEtudeDetermineTabLimites (objConteneur, debugs = false) {
  const tabLimites = []
  if (debugs) console.debug('objConteneur.domaineDef=', objConteneur.domaineDef)
  // j’ai au moins deux bornes par domaine donc avec les deux crochets, 4 éléments
  for (let index = 0; index < objConteneur.domaineDef.length; index = index + 4) {
    // console.log("index=",index," et objConteneur.val_interdites=",objConteneur.val_interdites);
    if (objConteneur.domaineDef[index + 2] === ']') { // ma borne inf correspond à un calcul de limite car le crochet est ouvert
      // si c’est une valeur interdite c’est une limite à droite
      if (objConteneur.val_interdites.includes(objConteneur.domaineDef[index]) || objConteneur.val_interdites.includes(ecritBienFraction(objConteneur.domaineDef[index]))) {
        tabLimites.push(fctsEtudeEcritLimite(ecritBienFraction(objConteneur.domaineDef[index])) + '^+')
      } else {
        tabLimites.push(fctsEtudeEcritLimite(ecritBienFraction(objConteneur.domaineDef[index])))
      }
    }
    if (objConteneur.domaineDef[index + 3] === '[') { // ma borne sup correspond à un calcul de limite car le crochet est ouvert
      // si c’est une valeur interdite c’est une limite à gauche
      if (objConteneur.val_interdites.includes(objConteneur.domaineDef[index + 1]) || objConteneur.val_interdites.includes(ecritBienFraction(objConteneur.domaineDef[index + 1]))) {
        tabLimites.push(fctsEtudeEcritLimite(ecritBienFraction(objConteneur.domaineDef[index + 1])) + '^-')
      } else {
        tabLimites.push(fctsEtudeEcritLimite(ecritBienFraction(objConteneur.domaineDef[index + 1])))
      }
    }
  }
  return tabLimites
}

/**
 * Dans le cas où le domaine de definition est surchargé, on doit chercher la bonne réponse parmi toutes les limites possibles du modèle
 * @param valeur
 * @param tab
 */
export function fctsEtudeDetermineLimiteSurcharge (valeur, tab) {
  // console.log("valeur=",valeur," et tab=",tab);
  // puisqu’on demande une limite d’un modèle cette valeur figure forcément sur une des bornes du modèle (sinon c pas une limite, c’est une image...)
  for (let i = 0; i < tab.length; i++) {
    if (ecritBienBorne(valeur) === ecritBienBorne(fctsEtudeEcritLimite(tab[i][0]))) {
      return tab[i][1]
    }
  }
  throw Error('Aucune limite trouvée correspondant à la valeur demandée :' + valeur)
}

/**
 * Calculer l’image de n’importe quel nombre suivant la fonction
 * @param objDerivee
 * @param modele
 * @param valeur
 * @return {*}
 */
export function fctsEtudeDetermineImage (objDerivee, modele, valeur) {
  // var tab_retour=[];
  // var coef,dans_exp;
  let a, b, c, d, a1, b1, c1, delta, deltaVal, monome1, monome2, monome3, terme1, terme2, num, den
  let e, axplusb, dxpluse
  let image, expo
  switch (modele) {
    case 8: // ax²+bx+c
      a = objDerivee.variables[2]
      b = objDerivee.variables[1]
      c = objDerivee.variables[0]
      monome1 = fctsEtudeProduitNbs(a, fctsEtudeProduitNbs(valeur, valeur))
      monome2 = fctsEtudeProduitNbs(b, valeur)
      image = fctsEtudeSommeNbs(monome1, fctsEtudeSommeNbs(monome2, c))
      break
    case 7:// ax+b+cln(dx+e)
      a = objDerivee.variables[0]
      b = objDerivee.variables[1]
      c = objDerivee.variables[2]
      d = objDerivee.variables[3]
      e = objDerivee.variables[4]
      axplusb = fctsEtudeSommeNbs(fctsEtudeProduitNbs(a, valeur), b)
      dxpluse = fctsEtudeSommeNbs(fctsEtudeProduitNbs(d, valeur), e)
      if (Math.abs(fctsEtudeCalculValeur(dxpluse) - 1) < epsilon) {
        // on a ln 1
        image = axplusb
      } else {
        image = (axplusb === '0')
          ? fctsEtudeEcrireMonome(1, 1, c, '\\ln ' + dxpluse)
          : fctsEtudeEcrireMonome(1, 0, axplusb) + fctsEtudeEcrireMonome(2, 1, c, '\\ln ' + dxpluse)
      }
      break
    case 6:// (a+bln(x))/x^c
      a = objDerivee.variables[0]
      b = objDerivee.variables[1]
      c = objDerivee.variables[2]
      if (String(valeur).includes('mathrm{e}')) {
        // valeur est une exponentielle
        const nbExp = valeur.substring(String(valeur).indexOf('mathrm{e}^{') + 11, valeur.length - 1)
        const sansExpo = valeur.replace('\\mathrm{e}^{' + nbExp + '}', '')
        if (sansExpo === '') {
          // la valeur est juste de la forme exp(...) et pas a*exp(...)
          num = fctsEtudeSommeNbs(a, fctsEtudeProduitNbs(b, nbExp))
          const puisExp = fctsEtudeProduitNbs(nbExp, c)
          // console.log("puisExp:"+puisExp)
          if (isAlmostEqual(puisExp, 1)) {
            den = '\\mathrm{e}'
          } else if (isAlmostZero(puisExp + 1)) {
            num = fctsEtudeEcrireMonome(1, 1, num, '\\mathrm{e}')
            den = ''
          } else if (fctsEtudeCalculValeur(puisExp) < 0) {
            num = fctsEtudeEcrireMonome(1, 1, num, '\\mathrm{e}^{' + fctsEtudeProduitNbs(puisExp, -1) + '}')
            den = ''
          } else {
            den = '\\mathrm{e}^{' + puisExp + '}'
          }
          if (String(num).includes('frac')) {
            // il y a une fraction au numérateur
            const tabNumDen = fctsEtudeExtraireNumDen(num)
            num = tabNumDen[1]
            den = tabNumDen[2] + den
          }
          if (den === '') {
            image = num
          } else {
            image = '\\frac{' + num + '}{' + den + '}'
          }
        }
      } else {
        // ce ne peut être que 1 autrement
        image = a
      }
      // console.log("image:"+image+"   valeur:"+valeur)
      break
    case 5:// (ax+b)exp(cx+d)+e
      a = objDerivee.variables[0]
      b = objDerivee.variables[1]
      c = objDerivee.variables[2]
      d = objDerivee.variables[3]
      e = (objDerivee.variables.length > 4) ? objDerivee.variables[4] : 0
      terme1 = fctsEtudeSommeNbs(fctsEtudeProduitNbs(a, valeur), b)
      terme2 = fctsEtudeSommeNbs(fctsEtudeProduitNbs(c, valeur), d)
      {
        const terme1Aff = (String(terme1) === '1') ? '' : (String(terme1) === '-1') ? '-' : terme1
        image = terme1Aff + '\\mathrm{e}^{' + terme2 + '}' + fctsEtudeEcrireMonome(2, 0, e)
      }
      break
    case 4:// (ax+b)/cx^2+d)
      a = objDerivee.variables[0]
      b = objDerivee.variables[1]
      c = objDerivee.variables[2]
      d = objDerivee.variables[3]
      // la valeur décimale du discriminant
      deltaVal = objDerivee.variables[8]
      if ((deltaVal <= 0) || ((deltaVal > 0) && (!valeur.includes('sqrt')))) {
        // soit le trinôme n’admet aucune ou une seule racine
        // soit il a 2 racines mais elles sont simples (sans radical)
        num = fctsEtudeSommeNbs(fctsEtudeProduitNbs(a, valeur), b)
        den = fctsEtudeSommeNbs(fctsEtudeProduitNbs(c, fctsEtudeProduitNbs(valeur, valeur)), d)
        image = fctsEtudeDivisionNbs(num, den)
      } else {
        let nb1Latex = operationAvecRadical({ typeOp: '*', nb1Latex: a, nb2Latex: valeur })
        num = operationAvecRadical({ typeOp: '+', nb1Latex, nb2Latex: b })
        const nb2Latex = operationAvecRadical({ typeOp: '^', nb1Latex: valeur, puis: 2 })
        nb1Latex = operationAvecRadical({ typeOp: '*', nb1Latex: c, nb2Latex })
        den = operationAvecRadical({ typeOp: '+', nb1Latex, nb2Latex: d })
        image = operationAvecRadical({ typeOp: '/', nb1Latex: num, nb2Latex: den })
      }
      // console.log("num:"+num+"   den:"+den+"   image:"+image)
      break
    case 3:// (ax+b)/(cx+d)
      a = objDerivee.variables[0]
      b = objDerivee.variables[1]
      c = objDerivee.variables[2]
      d = objDerivee.variables[3]
      num = fctsEtudeSommeNbs(fctsEtudeProduitNbs(a, valeur), b)
      den = fctsEtudeSommeNbs(fctsEtudeProduitNbs(c, valeur), d)
      if (den !== 0) {
        image = fctsEtudeDivisionNbs(num, den)
      } else { // pare feu
        image = 'valInterdite'
      }

      break
    case 2 :// ax^3+bx^2+cx+d
      a = objDerivee.variables[3]
      b = objDerivee.variables[2]
      c = objDerivee.variables[1]
      d = objDerivee.variables[0]
      // la dérivée est a1x^2+b1x+c1
      a1 = objDerivee.variables[4]
      b1 = objDerivee.variables[5]
      c1 = objDerivee.variables[6]
      // le discriminant en mode txt
      delta = objDerivee.variables[7]
      // et sa valeur décimale
      deltaVal = objDerivee.variables[8]
      if ((deltaVal <= 0) || ((deltaVal > 0) && (!valeur.includes('sqrt')))) {
        // soit le trinôme n’admet aucune ou une seule racine
        // soit il a 2 racines mais elles sont simples (sans radical)
        monome1 = fctsEtudeProduitNbs(a, fctsEtudeProduitNbs(fctsEtudeProduitNbs(valeur, valeur), valeur))
        monome2 = fctsEtudeProduitNbs(b, fctsEtudeProduitNbs(valeur, valeur))
        monome3 = fctsEtudeProduitNbs(c, valeur)
        terme1 = fctsEtudeSommeNbs(monome1, monome2)
        terme2 = fctsEtudeSommeNbs(monome3, d)
        image = fctsEtudeSommeNbs(terme1, terme2)
      } else {
        // le trinôme admet 2 racines
        const racine1Val = (-fctsEtudeCalculValeur(b1) - Math.sqrt(deltaVal)) / (2 * fctsEtudeCalculValeur(a1))// valeur décimale de (-b-sqrt{delta})/(2a)
        const valeurVal = fctsEtudeCalculValeur(valeur)// valeur au format décimal
        if (isAlmostEqual(racine1Val, valeurVal)) {
          // on cherche l’image par le pol de degré 3 de la première racine du trinôme
          image = imagePolDegre3(a, b, c, d, a1, b1, c1, delta)[0]
        } else {
          image = imagePolDegre3(a, b, c, d, a1, b1, c1, delta)[1]
        }
      }
      break
    default:
      a = objDerivee.variables[0]
      b = objDerivee.variables[1]
      expo = '\\mathrm{e}^{' + valeur + '}'
      if (String(valeur) === '0') {
        expo = ''
      }
      if (String(valeur) === '1') {
        expo = '\\mathrm{e}'
      }
      {
        const fact1 = fctsEtudeSommeNbs(fctsEtudeProduitNbs(a, valeur), b)
        const fact1Aff = (String(fact1) === '1') ? '' : (String(fact1) === '-1') ? '-' : String(fact1)
        image = fact1Aff + expo
      }
      break
  }
  return image
}

export function fctsEtudeSigneFois (nombre) {
  if (nombre > 0) {
    if (nombre === 1) {
      return '+'
    } else {
      return '+' + nombre
    }
  } else {
    if (nombre === -1) {
      return '-'
    } else {
      return '-' + Math.abs(nombre)
    }
  }
}

/**
 * identique aux 3 sections exemples, car chacune peut générer l’aléatoire, a priori sur les mêmes modèles
 * @param numModele
 * @param {boolean} [isDebug=false]
 * @return {{}}
 */
export function fctsEtudeGenereAleatoire (numModele, isDebug = false) {
  // cette fonction génère les coefficients aléatoires utiles pour chaque modèle
  // elle renvoie un obj avec l’expression de la fonction au format mathématique (pas latex)
  // numModele est le numéro du modèle
  // isDebug permet d’afficher des infos en console
  const obj = {}
  let a, b, c, d, e, aprime, bprime, cprime, x1, x2, pgcdab

  if (numModele === 0) {
    numModele = j3pGetRandomInt(1, 7)
  }
  switch (Number(numModele)) {
    case 8:// modèle 8 : ax^2+bx+c
      do {
        a = (2 * j3pGetRandomInt(0, 1) - 1) * j3pGetRandomInt(2, 8)
        b = (2 * j3pGetRandomInt(0, 1) - 1) * j3pGetRandomInt(1, 8)
        c = (2 * j3pGetRandomInt(0, 1) - 1) * j3pGetRandomInt(1, 8)
      } while (Math.abs(Math.pow(b, 2) - 4 * a * c) < Math.pow(10, -10))
      obj.fonction = fctsEtudeEcrireMonome(1, 2, a) + fctsEtudeEcrireMonome(2, 1, b) + fctsEtudeEcrireMonome(3, 0, c)
      break
    case 7:// ax+b+cln(dx+e)
      a = (j3pGetRandomInt(0, 1) * 2 - 1) * j3pGetRandomInt(1, 4)// a est non nul
      b = j3pGetRandomInt(-4, 4)
      c = (j3pGetRandomInt(0, 1) * 2 - 1) * j3pGetRandomInt(1, 4)// c est non nul
      do {
        d = (j3pGetRandomInt(0, 1) * 2 - 1) * j3pGetRandomInt(1, 4)// d est non nul
      } while (isAlmostEqual(c, d))
      e = j3pGetRandomInt(-4, 4)
      {
        const expressionLn = 'ln(' + fctsEtudeEcrireMonome(1, 1, d) + fctsEtudeEcrireMonome(2, 0, e) + ')'
        obj.fonction = fctsEtudeEcrireMonome(1, 1, a) + fctsEtudeEcrireMonome(2, 0, b) + fctsEtudeEcrireMonome(3, 1, c, expressionLn)
      }
      break
    case 6:// (a+bln(x))/x^c
      {
        const k = j3pGetRandomInt(-4, 4)
        do {
          b = j3pGetRandomInt(-7, 7)
          c = j3pGetRandomInt(1, 3)// par défaut et pour rester un peu plus dans l’esprit du programme, on fixe c à 1
          c = 1
        } while ((Math.abs(b / c - Math.round(b / c)) > epsilon) || (isAlmostZero(b)))
        a = Math.round(b / c) - k * b
        let nume = fctsEtudeEcrireMonome(1, 0, a) + fctsEtudeEcrireMonome(2, 1, b, 'ln x')
        if (isAlmostZero(a)) {
          nume = fctsEtudeEcrireMonome(1, 1, b, 'ln x')
        }
        if (isAlmostEqual(c, 1)) {
          obj.fonction = '(' + nume + ')/x'
        } else {
          obj.fonction = '(' + nume + ')/x^' + c + ''
        }
      }
      break
    case 5:// (ax+b)exp(cx+d)+e
      a = (j3pGetRandomInt(0, 1) * 2 - 1) * j3pGetRandomInt(1, 3)
      b = j3pGetRandomInt(-5, 5)
      c = (j3pGetRandomInt(0, 1) * 2 - 1) * j3pGetRandomInt(1, 3)
      d = (j3pGetRandomInt(0, 1) * 2 - 1) * j3pGetRandomInt(1, 5)
      e = 0 // Par défaut, il vaudra toujours 0 mais je l’ai mis pour qu’on puisse utiliser ce cas de figure dans le cadre d’une équa diff y'=ay+b
      if (isAlmostZero(b)) {
        obj.fonction = fctsEtudeEcrireMonome(1, 1, a) + 'exp(' + fctsEtudeEcrireMonome(1, 1, c) + fctsEtudeEcrireMonome(2, 0, d) + ')'
      } else {
        obj.fonction = '(' + fctsEtudeEcrireMonome(1, 1, a) + fctsEtudeEcrireMonome(2, 0, b) + ')exp(' + fctsEtudeEcrireMonome(1, 1, c) + fctsEtudeEcrireMonome(2, 0, d) + ')'
      }
      break
    case 4:// modèle (ax+b)/(cx^2+d), a, b, c, d entiers
      // c et d seront strictement positifs pour que le domaine soit \R
      // la dérivée est trinôme/(cx^2+d)^2 où le trinôme a nécessairement deux racines
      // donc je commence par générer les racines x1 et x2 pour qu’elles soient simples
      a = (j3pGetRandomInt(0, 1) * 2 - 1) * j3pGetRandomInt(1, 4)
      do {
        x1 = (j3pGetRandomInt(0, 1) * 2 - 1) * j3pGetRandomInt(1, 4)
        x2 = (j3pGetRandomInt(0, 1) * 2 - 1) * j3pGetRandomInt(1, 4)
        b = -a * (x1 + x2) / 2
      } while (Math.abs(Math.round(b) - b > epsilon) || (x1 * x2 > 0)) // comme ça b sera entier et des racines de signes contraires font que d sera positif
      c = j3pGetRandomInt(1, 3)
      d = -c * x1 * x2
      if (isAlmostZero(b)) {
        // b est nul
        pgcdab = Math.abs(a)
      } else {
        pgcdab = j3pPGCD(Math.abs(a), Math.abs(b))
      }
      {
        const pgcdcd = j3pPGCD(Math.abs(c), Math.abs(d))
        const pgcdGlobal = j3pPGCD(pgcdab, pgcdcd)
        if (pgcdGlobal > 1) {
          a = a / pgcdGlobal
          b = b / pgcdGlobal
          c = c / pgcdGlobal
          d = d / pgcdGlobal
        }
      }
      obj.fonction = '(' + fctsEtudeEcrireMonome(1, 1, a) + fctsEtudeEcrireMonome(2, 0, b) + ')/(' + fctsEtudeEcrireMonome(1, 2, c) + fctsEtudeEcrireMonome(2, 0, d) + ')'
      break
    case 3:// modèle 3 : (ax+b)/(cx+d)
      {
        let OK // variable juste là pour vérifier ensuite si les coefs sont acceptables ou pas
        do {
          OK = true
          a = j3pGetRandomInt(-6, 6)
          b = j3pGetRandomInt(-6, 6)
          c = j3pGetRandomInt(-6, 6)
          d = j3pGetRandomInt(-6, 6)
          OK = OK && (Math.abs(c) > epsilon)// bien sûr, c doit être non nul
          OK = OK && (Math.abs(d) > epsilon)// on force aussi d à être non nul
          OK = OK && ((Math.abs(a) > epsilon) || (Math.abs(b) > epsilon))// le numérateur ne doit pas être nul
          OK = OK && (Math.abs(a * d - b * c) > epsilon)
          let pgcd1, pgcd2
          if (OK) {
            if (isAlmostZero(a)) {
              pgcd1 = Math.abs(b)
            } else if (isAlmostZero(b)) {
              pgcd1 = Math.abs(a)
            } else {
              pgcd1 = j3pPGCD(Math.abs(a), Math.abs(b))
            }
            if (isAlmostZero(d)) {
              pgcd2 = Math.abs(c)
            } else {
              pgcd2 = j3pPGCD(Math.abs(c), Math.abs(d))
            }
            OK = ((j3pPGCD(pgcd1, pgcd2) === 1) && (pgcd2 === 1))
          }
        } while (!OK)
      }
      if (isAlmostZero(a)) {
        obj.fonction = '(' + fctsEtudeEcrireMonome(1, 0, b) + ')/(' + fctsEtudeEcrireMonome(1, 1, c) + fctsEtudeEcrireMonome(2, 0, d) + ')'
      } else {
        obj.fonction = '(' + fctsEtudeEcrireMonome(1, 1, a) + fctsEtudeEcrireMonome(2, 0, b) + ')/(' + fctsEtudeEcrireMonome(1, 1, c) + fctsEtudeEcrireMonome(2, 0, d) + ')'
      }
      break
    case 2:// modèle 2 : ax^3+bx^2+cx+d
      // on gère les coefficients pour que les racines du trinôme égal à la dérivée (si elles existent) soient simples
      // deux cas de figure. Dans 2 cas sur 3, la dérivée admettra 2 racines, dans 1 cas sur 3, la dérivée n’aura pas de racine
      {
        const casFigure = j3pRandomTab(['2 racines', 'pas racine'], [0.6667, 0.3333])
        if (casFigure === '2 racines') {
        // j'écris la dérivée sous la forme a(x-x_1)(x-x_2)
          do {
            x1 = j3pGetRandomInt(-8, 8)
            x2 = j3pGetRandomInt(-8, 8)
          } while (Math.abs(Math.abs(x1) - Math.abs(x2)) < epsilon)
          do {
            aprime = j3pGetRandomInt(-6, 6)
          } while ((Math.abs(Math.abs(aprime) - Math.abs(x2)) < epsilon) || (Math.abs(Math.abs(x1) - Math.abs(aprime)) < epsilon) || (isAlmostZero(aprime)))
          bprime = Math.round(-1 * aprime * (x1 + x2))
          cprime = Math.round(aprime * (x1 * x2))
        } else {
          let delta
          do {
            do {
              aprime = j3pGetRandomInt(-8, 8)
            } while (isAlmostZero(aprime))
            bprime = j3pGetRandomInt(-8, 8)
            cprime = j3pGetRandomInt(-8, 8)
            delta = Math.pow(bprime, 2) - 4 * aprime * cprime
          } while (delta >= 0)
        }
        a = fctsEtudeDivisionNbs(String(aprime), '3')
        b = fctsEtudeDivisionNbs(String(bprime), '2')
        c = String(cprime)
        d = String(j3pGetRandomInt(-8, 8))
        obj.fonction = fctsEtudeEcrireMonome(1, 3, a) + fctsEtudeEcrireMonome(2, 2, b) + fctsEtudeEcrireMonome(3, 1, c) + fctsEtudeEcrireMonome(4, 0, d)
      }
      break
    default :// modèle 1 : (ax+b)exp(x)
      do {
        a = j3pGetRandomInt(-7, 7)
        b = j3pGetRandomInt(-7, 7)
      } while ((b === 0) || (a === 1) || (a === 0))
      // expression de la fonction en langage mathématique
      obj.fonction = '(' + fctsEtudeEcrireMonome(1, 1, a) + fctsEtudeEcrireMonome(2, 0, b) + ')exp(x)'
      break
  }
  const objGenere = fctsEtudeGenereDonnees(numModele, obj.fonction)
  for (const prop in objGenere) {
    obj[prop] = objGenere[prop]
  }
  return obj
}

/**
 * génère les données utiles de chaque modèle
 * @param modele
 * @param laFonction
 * @param {boolean} [isDebug=false]
 * @return {{}}
 */
export function fctsEtudeGenereDonnees (modele, laFonction, isDebug = false) {
  // pour chaque modèle, on génère le domaine s’il n’est pas imposé
  // on déterminer également les limites sur le domaine considéré si ce sont bien des limites et non des images
  // isDebug permet d’afficher des infos en console pour débugger
  const obj = {}
  obj.modele = modele
  obj.val_interdites = []// ce tableau contiendra la ou les éventuelles valeurs interdites
  obj.limites = []// ce tableau contiendra les limites aux bornes des intervalles du domaine
  // bien entendu, il ne contiendra pas les images lorsqu’elles existent (lorsque l’intervalle est fermé ou semi-fermé)
  // on a besoin de l’expression de la fonction, puis de calculer certaines images
  let a, b, c, d, nume, tabCoefs, fctAffine, coefC, dxpluse, fctAffine2, tabCoef2
  obj.fonction = laFonction
  if ((modele === 5) && (laFonction.indexOf('(ax+b)') === 0)) {
    // c’est le modèle 5 et on a imposé une fonction où la fonction affine ax+b est aléatoire alors que cx+d est réellement imposé
    fctAffine2 = laFonction.substring(laFonction.indexOf('exp') + 3)
    fctAffine2 = fctAffine2.substring(fctAffine2.indexOf('(') + 1, fctAffine2.indexOf(')'))
    tabCoef2 = extraireCoefsFctaffine(fctAffine2)
    do {
      a = (j3pGetRandomInt(0, 1) * 2 - 1) * j3pGetRandomInt(1, 3)
    } while (Math.abs(Math.abs(a) - Math.abs(tabCoef2[0])) < epsilon)
    b = (j3pGetRandomInt(0, 1) * 2 - 1) * j3pGetRandomInt(1, 5)
    let partieExp = laFonction.substring(laFonction.indexOf('exp'))
    // c’est qu’on a imposé la valeur de c ou celle de d (voire les 2)
    fctAffine = partieExp.substring(4, partieExp.indexOf(')'))
    if (fctAffine.includes('cx')) {
      // c est aléatoire
      c = (j3pGetRandomInt(0, 1) * 2 - 1) * j3pGetRandomInt(1, 3)
      if (fctAffine.includes('+cx')) {
        fctAffine = fctAffine.replace('+cx', j3pMonome(2, 1, c))
      } else {
        fctAffine = fctAffine.replace('cx', j3pMonome(1, 1, c))
      }
    }
    if (fctAffine.includes('d')) {
      // d est aléatoire
      d = (j3pGetRandomInt(0, 1) * 2 - 1) * j3pGetRandomInt(1, 5)
      if (fctAffine.includes('+d')) {
        fctAffine = fctAffine.replace('+d', j3pMonome(2, 0, d))
      } else {
        fctAffine = fctAffine.replace('d', j3pMonome(1, 0, d))
      }
    }
    const e = partieExp.substring(partieExp.indexOf(')') + 1)
    partieExp = 'exp(' + fctAffine + ')'
    laFonction = '(' + fctsEtudeEcrireMonome(1, 1, a) + fctsEtudeEcrireMonome(2, 0, b) + ')' + partieExp + e
    // Pour être en accord avec la fonction précéddente, on pourrait ajouter e mais il vaudra 0
    obj.fonction = laFonction
  } else if ((modele === 5) && (laFonction.indexOf('(cx+d)') === laFonction.length - 6)) {
    // c’est le modèle 5 et on a imposé une fonction où la fonction affine cx+d est aléatoire alors que ax+b est réellement imposé
    // var partieExp = laFonction.substring(laFonction.indexOf("exp"));
    // c’est qu’on a imposé la valeur de c ou celle de d (voire les 2)
    fctAffine = laFonction.substring(0, laFonction.indexOf('exp'))
    if (fctAffine.includes('ax')) {
      // a est aléatoire
      a = (j3pGetRandomInt(0, 1) * 2 - 1) * j3pGetRandomInt(1, 3)
      if (fctAffine.includes('+ax')) {
        fctAffine = fctAffine.replace('+ax', j3pMonome(2, 1, a))
      } else {
        fctAffine = fctAffine.replace('ax', j3pMonome(1, 1, a))
      }
    }
    if (fctAffine.includes('b')) {
      // b est aléatoire
      do {
        b = (j3pGetRandomInt(0, 1) * 2 - 1) * j3pGetRandomInt(1, 5)
      } while (isAlmostEqual(b, 1))
      if (fctAffine.includes('+b')) {
        fctAffine = fctAffine.replace('+b', j3pMonome(2, 0, b))
      } else {
        fctAffine = fctAffine.replace('b', j3pMonome(1, 0, b))
      }
    }
    if (String(fctAffine) === '-1') {
      fctAffine = '-'
    } else if (String(fctAffine) === '1') {
      fctAffine = ''
    }
    tabCoef2 = extraireCoefsFctaffine(fctAffine)
    do {
      c = (j3pGetRandomInt(0, 1) * 2 - 1) * j3pGetRandomInt(1, 3)
    } while ((Math.abs(c - 1) < Math.pow(10, -10)) || (Math.abs(Math.abs(c) - Math.abs(tabCoef2[0])) < Math.pow(10, -10)))
    d = (j3pGetRandomInt(0, 1) * 2 - 1) * j3pGetRandomInt(1, 5)

    laFonction = fctAffine + 'exp(' + fctsEtudeEcrireMonome(1, 1, c) + fctsEtudeEcrireMonome(2, 0, d) + ')'
    // Même remarque sur la présence du e
    obj.fonction = laFonction
  } else if ((modele === 6) && (laFonction.indexOf('(a+bln(x))') === 0)) {
    // c’est le modèle  et on a imposé la puissance du dénominateur (par contre le numérateur est aléatoire et de la forme a+bln x
    // il faut que je récupère la valeur du dénominateur
    const den = laFonction.substring(laFonction.indexOf('/') + 1)
    const regNb = /\d+/ig
    if (regNb.test(den)) { // il y a une puissance de x
      c = Number(den.match(regNb)[0])
    } else { // c’est donc que la puissance de x imposée est 1
      c = 1
    }
    const k = j3pGetRandomInt(-4, 4)
    do {
      if (c > 7) {
        b = (j3pGetRandomInt(0, 1) * 2 - 1) * j3pGetRandomInt(1, 2) * c
      } else {
        b = j3pGetRandomInt(-7, 7)
      }
    } while ((Math.abs(b / c - Math.round(b / c)) > epsilon) || (isAlmostZero(b)))
    a = Math.round(b / c) - k * b
    nume = fctsEtudeEcrireMonome(1, 0, a) + fctsEtudeEcrireMonome(2, 1, b, 'ln x')
    if (isAlmostZero(a)) {
      nume = fctsEtudeEcrireMonome(1, 1, b, 'ln x')
    }
    if (isAlmostEqual(c, 1)) {
      laFonction = '(' + nume + ')/x'
    } else {
      laFonction = '(' + nume + ')/x^' + c + ''
    }
    obj.fonction = laFonction
  } else if ((modele === 7) && (laFonction.indexOf('ax+b') === 0)) {
    // c’est le modèle 7 de la forme ax+b+cln(dx+e) mais on a imposé d et e, voire c
    // a et b sont aléatoires
    a = (j3pGetRandomInt(0, 1) * 2 - 1) * j3pGetRandomInt(1, 4)// a est non nul
    b = j3pGetRandomInt(-4, 4)
    if (laFonction.includes('c')) {
      // c est aussi aléatoire (en gros on n’impose que d et e dans l’expression ln(dx+e)
      c = (j3pGetRandomInt(0, 1) * 2 - 1) * j3pGetRandomInt(1, 4)// c est non nul
    } else {
      // il faut que je trouve la valeur de c
      const fctSansaxb = laFonction.replace('ax+b', '')
      c = fctSansaxb.substring(0, fctSansaxb.indexOf('ln'))
      if (c[0] === '+') {
        c = c.substring(1)
      }
      if (c === '') {
        c = 1
      } else if (c === '-') {
        c = -1
      } else {
        c = Number(c)
      }
    }
    let dansln = laFonction.substring(laFonction.indexOf('ln') + 2)
    if (dansln[0] === '(') {
      dansln = dansln.substring(1)
    }
    if (dansln[0] === ' ') {
      dansln = dansln.substring(1)
    }
    if (dansln[dansln.length - 1] === ')') {
      dansln = dansln.substring(0, dansln.length - 1)
    }
    if (dansln === 'x') {
      laFonction = fctsEtudeEcrireMonome(1, 1, a) + fctsEtudeEcrireMonome(2, 0, b) + fctsEtudeEcrireMonome(3, 1, c, 'ln x')
    } else {
      laFonction = fctsEtudeEcrireMonome(1, 1, a) + fctsEtudeEcrireMonome(2, 0, b) + fctsEtudeEcrireMonome(3, 1, c, 'ln(' + dansln + ')')
    }
    obj.fonction = laFonction
  }
  const fctPourCalc = fctsEtudeEcriturePourCalcul(laFonction)
  const arbreFct = new Tarbre(fctPourCalc, ['x'])
  // var lnRegexp = new RegExp('(\\-?\d{0,}\\*?ln\\sx)|(\\-?\d{0,}\\*?ln\\(x\\))', 'ig')
  // @todo remplacer par la ligne suivante, mais faut le tester --> J’ai fait des tests que me semblent concluants (Rémi)
  const lnRegexp = /-?\d*\*?ln(?:\sx|\(x\))/ig // signe - éventuel, suivi de chiffres éventuels, suivi d’un * éventuel, suivi de 'ln' suivi de espace+x ou (x)
  switch (modele) {
    case 8:// modèle ax^2+bx+c
      // ensembles de définition et de dérivabilité sous la forme de tableaux
      obj.domaineDef = ['-infini', '+infini', ']', '[']
      obj.domaineDeriv = ['-infini', '+infini', ']', '[']
      // je recherche les valeurs approchées des coefs
      c = arbreFct.evalue([0])
      {
        const imagePlus1 = arbreFct.evalue([1])
        const imageMoins1 = arbreFct.evalue([-1])
        b = 0.5 * (imagePlus1 - imageMoins1)
        a = 0.5 * (imagePlus1 + imageMoins1 - 2 * c)
      }
      if (a < 0) {
        obj.limites.push(['-infini', '-\\infty'])// en -\\infty, la limite vaut -\\infty
        obj.limites.push(['+infini', '-\\infty'])// en +\\infty, la limite vaut -\\infty
      } else {
        obj.limites.push(['-infini', '+\\infty'])// en -\\infty, la limite vaut +\\infty
        obj.limites.push(['+infini', '+\\infty'])// en +\\infty, la limite vaut +\\infty
      }
      break
    case 7:// modèle ax+b+cln(dx+e)
      if (laFonction.includes('(')) {
        // il y a des parenthèses, donc dx+e ne se réduit à pas à x
        dxpluse = laFonction.substring(laFonction.indexOf('(') + 1, laFonction.indexOf(')'))
      } else {
        dxpluse = 'x'
      }
      {
        const coefDe = extraireCoefsFctaffine(dxpluse)
        d = coefDe[0]
        const e = coefDe[1]
        const borneDomaine = fctsEtudeDivisionNbs(fctsEtudeProduitNbs('-1', e), d)
        const dVal = fctsEtudeCalculValeur(d)
        if (dVal > 0) {
          obj.domaineDef = [borneDomaine, '+infini', ']', '[']
        } else {
          obj.domaineDef = ['-infini', borneDomaine, ']', '[']
        }
        obj.domaineDeriv = obj.domaineDef
        const coefDevantlnReg = /-?\d+\*?ln/ig
        const coefDevantlnReg2 = /-?ln/ig
        if (coefDevantlnReg.test(laFonction)) {
          coefC = laFonction.match(coefDevantlnReg)
        } else {
          coefC = laFonction.match(coefDevantlnReg2)
        }
        c = coefC[0].replace('ln', '')
        if (c === '') {
          c = '1'
        } else if (c === '-') {
          c = '-1'
        } else if (c[c.length - 1] === '*') {
          c = c.substring(0, c.length - 1)
        }
        if (c === '') {
          fctAffine = laFonction.substring(0, '\\ln')
        } else {
          fctAffine = laFonction.substring(0, laFonction.indexOf(coefC[0]))
        }
        if (fctAffine[fctAffine.length - 1] === '+') {
          fctAffine = fctAffine.substring(0, fctAffine.length - 1)
        }
        const coefsab = extraireCoefsFctaffine(fctAffine)
        a = coefsab[0]
        b = coefsab[1]
        if (isDebug) console.debug('dans fctsEtudeGenereDonnees, a:' + a + '   b:' + b + '   c:' + c + '   d:' + d + '   e:' + e)
        const valA = fctsEtudeCalculValeur(a)
        const valC = fctsEtudeCalculValeur(c)
        if (dVal > 0) {
          if (valC > 0) {
            obj.limites.push([borneDomaine + '^+', '-\\infty'])
          } else {
            obj.limites.push([borneDomaine + '^+', '+\\infty'])
          }
          if (valA > 0) {
            obj.limites.push(['+infini', '+\\infty'])
          } else {
            obj.limites.push(['+infini', '-\\infty'])
          }
        } else {
          if (valA > 0) {
            obj.limites.push(['-infini', '-\\infty'])
          } else {
            obj.limites.push(['-infini', '+\\infty'])
          }
          if (valC > 0) {
            obj.limites.push([borneDomaine + '^-', '-\\infty'])
          } else {
            obj.limites.push([borneDomaine + '^-', '+\\infty'])
          }
        }
        obj.val_interdites.push(borneDomaine)
      }
      break
    case 6:// modèle (a+bln(x))/x^c
      obj.val_interdites.push('0')
      obj.domaineDef = ['0', '+infini', ']', '[']
      obj.domaineDeriv = obj.domaineDef
      {
      // je dois tout d’abord trouver la valeur de b
        const fctMath = j3pMathquillXcas(laFonction)// écriture de la fonction au format mathématique
        const num = fctMath.substring(0, fctMath.indexOf('/'))
        // console.log("num:"+num+"   test:"+lnRegexp.test(num))
        const tabNum = num.match(lnRegexp)
        const coefB = tabNum[0].substring(0, Math.max(tabNum[0].indexOf('\\ln'), tabNum[0].indexOf('ln')))
        if (coefB[0] === '-') {
        // c’est que b est négatif, la limite en 0 est donc +infini
          obj.limites.push(['0^+', '+\\infty'], ['+infini', '0'])
        } else {
        // b est positif, la limite en 0 est -infini
          obj.limites.push(['0^+', '-\\infty'], ['+infini', '0'])
        }
      }
      // console.log("fctMath:"+fctMath+"   tabNum:"+tabNum+"   obj.limites:"+obj.limites)
      break
    case 5:// modèle (ax+b)exp(cx+d)+e
      obj.limites_affine1 = []// limite de ax+b dans l’écriture (ax+b)exp(cx+d)
      obj.limites_affine2 = []// limite de cx+d dans l’écriture (ax+b)exp(cx+d)
      obj.domaineDef = ['-infini', '+infini', ']', '[']
      obj.domaineDeriv = obj.domaineDef
      {
        let fctAffine1 = laFonction.substring(0, laFonction.indexOf('exp'))
        fctAffine2 = laFonction.substring(laFonction.indexOf('exp') + 3, laFonction.lastIndexOf(')') + 1)
        let e = 0
        if (laFonction.lastIndexOf(')') !== laFonction.length - 1) e = fctsEtudeCalculValeur(laFonction.substring(laFonction.lastIndexOf(')') + 1))
        if (fctAffine1.indexOf('(') === 0) {
          fctAffine1 = fctAffine1.substring(fctAffine1.indexOf('(') + 1, fctAffine1.indexOf(')'))
        }
        if (fctAffine1.charAt(fctAffine1.length - 1) === '*') fctAffine1 = fctAffine1.substring(0, fctAffine1.length - 1)
        fctAffine2 = fctAffine2.substring(fctAffine2.indexOf('(') + 1, fctAffine2.indexOf(')'))
        const tabCoef1 = extraireCoefsFctaffine(fctAffine1)
        tabCoef2 = extraireCoefsFctaffine(fctAffine2)
        if (isDebug) console.debug(fctAffine1, tabCoef1, 'et pour cx+d:', fctAffine2, tabCoef2)
        if (Number(tabCoef1[0]) === 0) {
          obj.limites_affine1.push(String(tabCoef1[1]), String(tabCoef1[1]))
          if (Number(tabCoef1[1]) > 0) {
            if (Number(tabCoef2[0]) > 0) {
              obj.limites.push(['-infini', String(e)])
              obj.limites.push(['+infini', '+\\infty'])
            } else {
              obj.limites.push(['-infini', '+\\infty'])
              obj.limites.push(['+infini', String(e)])
            }
          } else {
            if (Number(tabCoef2[0]) > 0) {
              obj.limites.push(['-infini', String(e)])
              obj.limites.push(['+infini', '-\\infty'])
            } else {
              obj.limites.push(['-infini', '-\\infty'])
              obj.limites.push(['+infini', String(e)])
            }
          }
        } else if (Number(tabCoef1[0]) > 0) {
          obj.limites_affine1.push('-\\infty', '-\\infty')
          obj.limites_affine1.push('+\\infty', '+\\infty')
          if (Number(tabCoef2[0]) > 0) {
            obj.limites.push(['-infini', String(e)])
            obj.limites.push(['+infini', '+\\infty'])
          } else {
            obj.limites.push(['-infini', '-\\infty'])
            obj.limites.push(['+infini', String(e)])
          }
        } else {
          obj.limites_affine1.push('-\\infty', '+\\infty')
          obj.limites_affine1.push('+\\infty', '-\\infty')
          if (Number(tabCoef2[0]) > 0) {
            obj.limites.push(['-infini', String(e)])
            obj.limites.push(['+infini', '-\\infty'])
          } else {
            obj.limites.push(['-infini', '+\\infty'])
            obj.limites.push(['+infini', String(e)])
          }
        }
        if (Number(tabCoef2[0] > 0)) {
          obj.limites_affine2.push('-\\infty', '-\\infty')
          obj.limites_affine2.push('+\\infty', '+\\infty')
        } else {
          obj.limites_affine2.push('-\\infty', '+\\infty')
          obj.limites_affine2.push('+\\infty', '-\\infty')
        }
      }
      break
    case 4:
      // modèle (ax+b)/(cx^2+d), a, b, c, d entiersz
      // ensembles de définition et de dérivabilité sous la forme de tableaux
      obj.domaineDef = ['-infini', '+infini', ']', '[']
      obj.domaineDeriv = obj.domaineDef
      obj.limites.push(['-infini', '0'], ['+infini', '0'])
      obj.limites_num = []// spécifique au modèle : on y met la limite en chaque borne du domaine du numérateur
      obj.limites_den = []// spécifique au modèle : on y met la limite en chaque borne du domaine du dénominateur
      nume = fctPourCalc.substring(fctPourCalc.indexOf('(') + 1, fctPourCalc.indexOf(')'))// je me retrouve avec une fonction affine ax+b
      tabCoefs = extraireCoefsFctaffine(nume)
      if (fctsEtudeCalculValeur(tabCoefs[0]) > 0) {
        obj.limites_num.push('-infini', '+infini')
      } else {
        obj.limites_num.push('+infini', '-infini')
      }// on a la limite en -infini
      obj.limites_den.push('+infini', '+infini')
      break
    case 3:
      // modèle 3 : (ax+b)/(cx+d)
      // ensembles de définition et de dérivabilité sous la forme de tableaux
      // il faut que je détermine les valeurs de c et d pour m’en sortir
      {
        let deno = fctPourCalc.substring(fctPourCalc.indexOf('/') + 1)// dans le cas où je n’ai pas de parenthèses autour du dénominateur
        if (fctPourCalc[fctPourCalc.length - 1] === ')') { // et si maintenant j’en ai :
          deno = fctPourCalc.substring(fctPourCalc.lastIndexOf('(') + 1, fctPourCalc.lastIndexOf(')'))// je me retrouve avec une fonction affine cx+d
        }
        tabCoefs = extraireCoefsFctaffine(deno)
        const valInterdite = ecritBienFraction(fctsEtudeDivisionNbs(fctsEtudeProduitNbs('-1', tabCoefs[1]), tabCoefs[0]))
        obj.val_interdites.push(valInterdite)
        obj.domaineDef = ['-infini', valInterdite, ']', '[', valInterdite, '+infini', ']', '[']
        obj.domaineDeriv = obj.domaineDef
        nume = fctPourCalc.substring(fctPourCalc.indexOf('(') + 1, fctPourCalc.indexOf(')'))// je me retrouve avec une fonction affine ax+b
        const tabCoefs2 = extraireCoefsFctaffine(nume)
        const limiteInf = fctsEtudeDivisionNbs(tabCoefs2[0], tabCoefs[0])// limite en +infty et -infty au format latex
        const valeurValInterdite = fctsEtudeCalculValeur(valInterdite)// valeur interdite au format décimal (approché)
        const arbreNum = new Tarbre(j3pMathquillXcas(nume), ['x'])
        const imageNume = arbreNum.evalue([valeurValInterdite])
        let limiteGauche, limiteDroite
        if (imageNume > 0) {
          if (fctsEtudeCalculValeur(tabCoefs[0]) > 0) { // c est positif
            limiteGauche = '-\\infty'
            limiteDroite = '+\\infty'
          } else {
            limiteGauche = '+\\infty'
            limiteDroite = '-\\infty'
          }
        } else {
          if (fctsEtudeCalculValeur(tabCoefs[0]) > 0) { // c est positif
            limiteGauche = '+\\infty'
            limiteDroite = '-\\infty'
          } else {
            limiteGauche = '-\\infty'
            limiteDroite = '+\\infty'
          }
        }
        obj.limites.push(['-infini', limiteInf], [valInterdite + '^-', limiteGauche], [valInterdite + '^+', limiteDroite], ['+infini', limiteInf])
        obj.limites_num = []// spécifique au modèle : on y met la limite en chaque borne du domaine du numérateur
        obj.limites_den = []// spécifique au modèle : on y met la limite en chaque borne du domaine du dénominateur
        if (fctsEtudeCalculValeur(tabCoefs2[0]) > 0) {
          obj.limites_num.push('-infini')
        } else {
          obj.limites_num.push('+infini')
        }// on a la limite en -infini
        const imageNumValInterdite = ecritBienFraction(fctsEtudeSommeNbs(fctsEtudeProduitNbs(tabCoefs2[0], valInterdite), tabCoefs2[1]))
        obj.limites_num.push(imageNumValInterdite, imageNumValInterdite)// image du numérateur par -d/c
        if (fctsEtudeCalculValeur(tabCoefs2[0]) > 0) {
          obj.limites_num.push('+infini')
        } else {
          obj.limites_num.push('-infini')
        }// on a la limite en +infini
        // maintenant je m’occupe du dénominateur
        if (fctsEtudeCalculValeur(tabCoefs[0]) > 0) {
          obj.limites_den.push('-infini')
        } else {
          obj.limites_den.push('+infini')
        }// on a la limite en -infini
        if (fctsEtudeCalculValeur(tabCoefs[0]) > 0) {
          obj.limites_den.push('0^-', '0^+')
        } else {
          obj.limites_den.push('0^+', '0^-')
        }
        if (fctsEtudeCalculValeur(tabCoefs[0]) > 0) {
          obj.limites_den.push('+infini')
        } else {
          obj.limites_den.push('-infini')
        }// on a la limite en +infini
      }
      if (isDebug) console.debug('OBJ=', obj)
      break
    case 2:
      // modèle 2 : ax^3+bx^2+cx+d
      // ensembles de définition et de dérivabilité sous la forme de tableaux
      obj.domaineDef = ['-infini', '+infini', ']', '[']
      obj.domaineDeriv = ['-infini', '+infini', ']', '[']
      // je recherche les valeurs approchées des coefs
      d = arbreFct.evalue([0])
      b = 0.5 * (arbreFct.evalue([1]) + arbreFct.evalue([-1]) - 2 * d)
      a = (arbreFct.evalue([2]) - 2 * arbreFct.evalue([1]) - 2 * b + d) / 6
      c = arbreFct.evalue([1]) - a - b - d
      if (a < 0) {
        obj.limites.push(['-infini', '+\\infty'])// en -\\infty, la limite vaut +\\infty
        obj.limites.push(['+infini', '-\\infty'])// en +\\infty, la limite vaut -\\infty
      } else {
        obj.limites.push(['-infini', '-\\infty'])// en -\\infty, la limite vaut -\\infty
        obj.limites.push(['+infini', '+\\infty'])// en +\\infty, la limite vaut +\\infty
      }
      break
    default :// modèle 1 : (ax+b)exp(x)
      obj.limites_affine = []// spécifique au modèle
      // ensembles de définition et de dérivabilité sous la forme de tableaux
      obj.domaineDef = ['-infini', '+infini', ']', '[']
      obj.domaineDeriv = ['-infini', '+infini', ']', '[']

      obj.limites.push(['-infini', '0'])// en -\\infty, la limite vaut 0
      // En + infini cela va dépendra du signe de a dans l’écriture (ax+b)*exp(x)
      b = arbreFct.evalue([0])
      {
        const arbreFct2 = new Tarbre('(' + fctPourCalc + '-(' + b + ')*exp(x))*exp(-x)', ['x'])
        a = arbreFct2.evalue([1])
      }
      if (a < 0) {
        // obj.signes_derivee=["+","-"];//commenté car dépend du domaine
        obj.limites.push(['+infini', '-\\infty'])// en +\\infty, la limite vaut -infty si a<0
        obj.limites_affine.push('+\\infty', '-\\infty')// les limites de ax+b sont +inf et -inf
      } else {
        // obj.signes_derivee=["-","+"];
        obj.limites.push(['+infini', '+\\infty'])// en +\\infty, la limite vaut +infty si a>0
        obj.limites_affine.push('-\\infty', '+\\infty')
      }
      break
  }
  return obj
} // fin fctsEtudeGenereDonnees

/**
 * normalement pas besoin de modifier en cas d’ajout de modèle de fonction (seuls les textes et la fonction genere_textes_correction sont à modifier)
 * @param divConteneur
 * @param couleurCorr
 * @param lesTextes
 * @param lesVariables
 */
export function fctsEtudeAfficheCorrection (divConteneur, couleurCorr, lesTextes, lesVariables) {
  // construction de l’affichage de la correction avec les textes et les variables
  if (typeof divConteneur === 'string') divConteneur = j3pElement(divConteneur)
  const tabvariables = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
  for (let i = 0; i < lesTextes.length; i++) {
    const txt = lesTextes[i]
    const obj = {}
    // on récupère les variables, par texte...
    for (let j = 0; j < lesVariables[i].length; j++) {
      obj[tabvariables[j]] = lesVariables[i][j]
    }
    obj.styletexte = {}
    divConteneur.style.color = couleurCorr
    j3pAffiche(divConteneur, '', txt, obj)
  }
} // fin fctsEtudeAfficheCorrection

/**
 * vérifie si les 2 tableaux sont bien identiques (égalité stricte de tous les éléments)
 * @param tab1
 * @param tab2
 * @returns {boolean}
 */
export function fctsEtudeEgaliteTableaux (tab1, tab2) {
  if (Array.isArray(tab1) && Array.isArray(tab2)) return tab1.length === tab2.length && tab1.every((elt, index) => elt === tab2[index])
  console.error(Error('fctsEtudeEgaliteTableaux appelée avec autre chose que des tableaux'), tab1, tab2)
  return tab1 === tab2
} // fin fctsEtudeEgaliteTableaux

/**
 * Retourne la décomposition du nombre fractionnaire nb (sous la forme \\frac{…}{…}), s’il n
 * @param {number|string} nb
 * @returns {Array<boolean|number|string>} [false] si nb n’est pas fractionnaire et [true, num, den, signe] sinon
 */
export function fctsEtudeExtraireNumDen (nb) {
  // nb est un nombre
  // s’il est fractionnaire, il est sous la forme \\frac{..}{...}
  // cette fonction renvoie un tableau decompNb
  // decompNb[0] vaut true or false suivant que le nb est fractionnaire ou non
  // s’il est fractionnaire, decompNb[1] est le num et decompNb[2] le den'
  // decompNb[3] est le signe du nombre
  const decompNb = [false]
  if (String(nb).includes('frac')) {
    decompNb[0] = true
    // on peut très bien avoir un signe "-" devant \\frac (plus facile pour gérer les nombres opposés)
    let coefMoins1 = 1
    if (nb.charAt(0) === '-') {
      nb = nb.substring(1)
      coefMoins1 = -1
    }
    const tabNb = nb.split('}{')
    decompNb[1] = tabNb[0].substring(tabNb[0].indexOf('frac') + 5, tabNb[0].length)
    if (isAlmostZero(coefMoins1 + 1)) {
      // je dois remplacer le num par son opposé
      if (decompNb[1].charAt(0) === '-') {
        decompNb[1] = decompNb[1].substring(1)
      } else {
        decompNb[1] = '-' + decompNb[1]
      }
    }
    decompNb[2] = tabNb[1].substring(0, tabNb[1].length - 1)
    decompNb[3] = (j3pNombre(String(decompNb[1])) / j3pNombre(String(decompNb[2])) < 0) ? '-' : '+'
  } else {
    // là aussi je peux gérer les nbs de la forme "--"...
    if ((String(nb).charAt(0) === '-') && (String(nb).charAt(1) === '-')) {
      nb = nb.substring(2)
    }
    decompNb[3] = (j3pNombre(String(nb)) < 0) ? '-' : '+'
  }
  return decompNb
} // fin fctsEtudeExtraireNumDen
export function fctsEtudeSommeNbs (nb1, nb2) {
  // nb1 et nb2 sont des nombres (entiers, décimaux ou fractionnaires au format latex)
  // Cette fonction renvoie la somme sous la forme la plus simple qui soit
  // Pour un résultat fractionnaire, elle renvoie la forme latex
  const tabFrac1 = fctsEtudeExtraireNumDen(nb1)
  const tabFrac2 = fctsEtudeExtraireNumDen(nb2)
  let maSommeNum, maSommeDen, maSomme
  if (tabFrac1[0]) { // le 1er nb est une fraction
    if (tabFrac2[0]) { // le 2ème nb est une fraction
      maSommeNum = j3pNombre(tabFrac1[1]) * j3pNombre(tabFrac2[2]) + j3pNombre(tabFrac2[1]) * j3pNombre(tabFrac1[2])
      maSommeDen = j3pNombre(tabFrac1[2]) * j3pNombre(tabFrac2[2])
    } else { // le deuxième nb est un nombre non frac
      maSommeNum = j3pNombre(tabFrac1[1]) + j3pNombre(String(nb2)) * j3pNombre(tabFrac1[2])
      maSommeDen = j3pNombre(tabFrac1[2])
    }
  } else { // le premier nb est un nombre non frac
    if (String(nb2).includes('frac')) { // le 2ème nb est une fraction
      maSommeNum = j3pNombre(String(nb1)) * j3pNombre(tabFrac2[2]) + j3pNombre(tabFrac2[1])
      maSommeDen = j3pNombre(tabFrac2[2])
    }
  }
  if (tabFrac1[0] || tabFrac2[0]) {
    // j’ai tout de même un problème si l’un des nombre au moins est un décimal'
    maSommeNum = Math.round(maSommeNum * Math.pow(10, 13)) / Math.pow(10, 13)
    if (String(maSommeNum).includes('.')) {
      // le numérateur est un décimal
      const puis10 = String(maSommeNum).length - String(maSommeNum).indexOf('.')
      maSommeNum = maSommeNum * Math.pow(10, puis10)
      maSommeDen = maSommeDen * Math.pow(10, puis10)
    }
    if (isAlmostZero(maSommeNum)) {
      // au final le produit est nul
      maSomme = 0
    } else {
      const lePgcd = j3pPGCD(Math.abs(maSommeNum), Math.abs(maSommeDen))
      maSommeNum = maSommeNum / lePgcd
      maSommeDen = maSommeDen / lePgcd
      const leSigneSomme = (maSommeNum / maSommeDen < 0) ? '-' : ''
      maSomme = (Math.abs(Math.abs(maSommeDen) - 1) < epsilon) ? (maSommeNum / maSommeDen) : '\\frac{' + leSigneSomme + j3pVirgule(Math.abs(maSommeNum)) + '}{' + j3pVirgule(Math.abs(maSommeDen)) + '}'
    }
  } else {
    maSomme = j3pNombre(String(nb1)) + j3pNombre(String(nb2))
  }
  return String(maSomme)
} // fin fctsEtudeSommeNbs
export function fctsEtudeProduitNbs (nb1, nb2) {
  // nb1 et nb2 sont des nombres (entiers, décimaux ou fractionnaires au format latex)
  // Cette fonction renvoie le produit sous la forme la plus simple qui soit
  // Pour un résultat fractionnaire, elle renvoie la forme latex
  const tabFrac1 = fctsEtudeExtraireNumDen(nb1)
  const tabFrac2 = fctsEtudeExtraireNumDen(nb2)
  let monProduitNum, monProduitDen, monProduit
  if (tabFrac1[0]) { // le 1er nb est une fraction
    if (tabFrac2[0]) { // le 2ème nb est une fraction
      monProduitNum = j3pNombre(tabFrac1[1]) * j3pNombre(tabFrac2[1])
      monProduitDen = j3pNombre(tabFrac1[2]) * j3pNombre(tabFrac2[2])
    } else { // le deuxième nb est un nombre non frac
      monProduitNum = j3pNombre(tabFrac1[1]) * j3pNombre(String(nb2))
      monProduitDen = j3pNombre(tabFrac1[2])
    }
  } else { // le premier nb est un nombre non frac
    if (String(nb2).includes('frac')) { // le 2ème nb est une fraction
      monProduitNum = j3pNombre(String(nb1)) * j3pNombre(tabFrac2[1])
      monProduitDen = j3pNombre(tabFrac2[2])
    }
  }
  if (tabFrac1[0] || tabFrac2[0]) {
    monProduitNum = Math.round(monProduitNum * Math.pow(10, 13)) / Math.pow(10, 13)
    if (String(monProduitNum).includes('.')) {
      // le numérateur est un décimal
      const puis10 = String(monProduitNum).length - String(monProduitNum).indexOf('.')
      monProduitNum = monProduitNum * Math.pow(10, puis10)
      monProduitDen = monProduitDen * Math.pow(10, puis10)
    }
    if (isAlmostZero(monProduitNum)) {
      // au final le produit est nul
      monProduit = 0
    } else {
      const lePgcd = j3pPGCD(Math.abs(monProduitNum), Math.abs(monProduitDen))
      monProduitNum = monProduitNum / lePgcd
      monProduitDen = monProduitDen / lePgcd
      const leSigneProduit = (monProduitNum / monProduitDen < 0) ? '-' : ''
      monProduit = (Math.abs(Math.abs(monProduitDen) - 1) < epsilon) ? (monProduitNum / monProduitDen) : '\\frac{' + leSigneProduit + j3pVirgule(Math.abs(monProduitNum)) + '}{' + j3pVirgule(Math.abs(monProduitDen)) + '}'
    }
  } else {
    monProduit = j3pNombre(String(nb1)) * j3pNombre(String(nb2))
  }
  return String(monProduit)
} // fin fctsEtudeProduitNbs

/**
 *
 * @param nb1
 * @param nb2
 * @returns {*}
 */
export function fctsEtudeDivisionNbs (nb1, nb2) {
  // nb1 et nb2 sont des nombres (entiers, décimaux ou fractionnaires au format latex)
  // Cette fonction renvoie le quotient sous la forme la plus simple qui soit
  // Pour un résultat fractionnaire, elle renvoie la forme latex
  const tabFrac1 = fctsEtudeExtraireNumDen(nb1)
  const tabFrac2 = fctsEtudeExtraireNumDen(nb2)
  let monQuotientNum = nb1
  let monQuotientDen = nb2
  let monQuotient, puis10
  if (tabFrac1[0]) { // le 1er nb est une fraction
    if (tabFrac2[0]) { // le 2ème nb est une fraction
      monQuotientNum = j3pNombre(tabFrac1[1]) * j3pNombre(tabFrac2[2])
      monQuotientDen = j3pNombre(tabFrac1[2]) * j3pNombre(tabFrac2[1])
    } else { // le deuxième nb est un nombre non frac
      monQuotientNum = j3pNombre(tabFrac1[1])
      monQuotientDen = j3pNombre(tabFrac1[2]) * j3pNombre(String(nb2))
    }
    if (monQuotientDen < 0) {
      monQuotientDen = -monQuotientDen
      monQuotientNum = -monQuotientNum
    }
    if (String(monQuotientDen).includes('.')) {
      // le numérateur est un décimal
      puis10 = String(monQuotientDen).length - String(monQuotientDen).indexOf('.')
      monQuotientNum = monQuotientNum * Math.pow(10, puis10)
      monQuotientDen = monQuotientDen * Math.pow(10, puis10)
    }
  } else { // le premier nb est un nombre non frac
    if (String(nb2).includes('frac')) { // le 2ème nb est une fraction
      monQuotientNum = j3pNombre(String(nb1)) * j3pNombre(tabFrac2[2])
      monQuotientDen = j3pNombre(tabFrac2[1])
    } else {
      if (String(nb2).includes('.')) {
        // le numérateur est un décimal
        puis10 = String(nb2).length - String(nb2).indexOf('.')
        monQuotientNum = j3pNombre(String(nb1)) * Math.pow(10, puis10)
        monQuotientDen = j3pNombre(String(nb2)) * Math.pow(10, puis10)
      }
    }
    if (monQuotientDen < 0) {
      monQuotientDen = -monQuotientDen
      monQuotientNum = -monQuotientNum
    }
  }
  monQuotientNum = Math.round(monQuotientNum * Math.pow(10, 13)) / Math.pow(10, 13)
  if (String(monQuotientNum).includes('.')) {
    // le numérateur est un décimal
    puis10 = String(monQuotientNum).length - String(monQuotientNum).indexOf('.')
    monQuotientNum = monQuotientNum * Math.pow(10, puis10)
    monQuotientDen = monQuotientDen * Math.pow(10, puis10)
  }
  if (isAlmostZero(monQuotientNum)) {
    // au final le produit est nul
    monQuotient = 0
  } else {
    const lePgcd = j3pPGCD(Math.abs(monQuotientNum), Math.abs(monQuotientDen))
    monQuotientNum = monQuotientNum / lePgcd
    monQuotientDen = monQuotientDen / lePgcd
    const leSigneQuotient = (monQuotientNum / monQuotientDen < 0) ? '-' : ''
    monQuotient = (Math.abs(Math.abs(monQuotientDen) - 1) < epsilon) ? (monQuotientNum / monQuotientDen) : '\\frac{' + leSigneQuotient + j3pVirgule(Math.abs(monQuotientNum)) + '}{' + j3pVirgule(Math.abs(monQuotientDen)) + '}'
  }
  return String(monQuotient)
} // fin fctsEtudeDivisionNbs

/**
 *
 * @param expresFct
 * @param modele
 * @returns {string|string}
 */
export function fctsEtudeEcritureLatexFonction (expresFct, modele) {
  // cette fonction renvoie l’expression de la fonction au format latex'
  // expresFct est son expression écrite sous forme mathématique
  // on précise le modele de fonction pour permettre de mieux gérer la transformation'
  let i, num, den, tabSeparateur
  let expresLatex = expresFct
  const regExpo = /exp\([^)]+\)/i
  const regFrac1 = /\(\d+\)\/\(\d+\)/ig // parenthèses pour numérateur ET dénumérateur
  const regFrac2 = /\(\d+\/\d+\)/ig // parenthèses autour de la fraction
  const regFrac3 = /\d+\/\d+/ig // pas de parenthèses

  switch (modele) {
    case 7:// modèle ax+b+cln(dx+e)
      if ((!expresFct.includes('\\ln')) && (expresFct.includes('ln'))) {
        // ln est bien présent mais n’est pas au format latex
        expresLatex = expresLatex.replace('ln', '\\ln')
      }
      break
    case 6:// c’est le modèle 6 : (a+bln(x))/x^c
      num = expresFct.substring(0, expresFct.indexOf('/'))
      num = (num[num.length - 1] === ')') ? num.substring(num.indexOf('(') + 1, num.length - 1) : num.substring(num.indexOf('(') + 1)
      num = num.replace('ln x', '\\ln x')
      num = num.replace('ln(x)', '\\ln x')
      den = expresFct.substring(expresFct.indexOf('/') + 1)
      // console.log("expresFct:"+expresFct+"   num:"+num+"   den:"+den)
      expresLatex = '\\frac{' + num + '}{' + den + '}'
      break
    case 4:// c’est le modèle 4
      // fonction (ax+b)/(cx^2+d)
      num = expresFct.substring(expresFct.indexOf('(') + 1, expresFct.indexOf(')'))
      den = expresFct.substring(expresFct.lastIndexOf('(') + 1, expresFct.lastIndexOf(')'))
      expresLatex = '\\frac{' + num + '}{' + den + '}'
      break

    case 3:// c’est le modèle 3
      // fonction (ax+b)/(cx+d)
      if (expresFct[0] === '(') { // j’ai des parenthèses autour du numérateur
        num = expresFct.substring(expresFct.indexOf('(') + 1, expresFct.indexOf(')'))
      } else { // je n’en ai pas
        num = expresFct.substring(0, expresFct.indexOf('/'))
      }
      if (expresFct[expresFct.length - 1] === ')') { // j’ai des parenthèses autour du dénominateur
        den = expresFct.substring(expresFct.lastIndexOf('(') + 1, expresFct.lastIndexOf(')'))
      } else { // je n’en ai pas
        den = expresFct.substring(expresFct.indexOf('/') + 1)
      }
      expresLatex = '\\frac{' + num + '}{' + den + '}'
      break
    case 2:// c’est que modele vaut 2 : fonction ax^3+bx^2+cx+d
    case 8:// c’est que modele vaut 8 : fonction ax^2+bx+c
      while (regFrac1.test(expresLatex)) {
        // on trouve (..)/(..)
        const tabFrac1 = expresLatex.match(regFrac1)
        for (i = 0; i < tabFrac1.length; i++) {
          tabSeparateur = tabFrac1[i].split('/')
          num = tabSeparateur[0].substring(1, tabSeparateur[0].length - 1)
          den = tabSeparateur[1].substring(1, tabSeparateur[1].length - 1)
          expresLatex = expresLatex.replace(tabFrac1[i], '\\frac{' + num + '}{' + den + '}')
        }
      }
      while (regFrac2.test(expresLatex)) {
        // on trouve (../..)
        const tabFrac2 = expresLatex.match(regFrac2)
        for (i = 0; i < tabFrac2.length; i++) {
          tabSeparateur = tabFrac2[i].split('/')
          num = tabSeparateur[0].substring(1, tabSeparateur[0].length)
          den = tabSeparateur[1].substring(0, tabSeparateur[1].length - 1)
          expresLatex = expresLatex.replace(tabFrac2[i], '\\frac{' + num + '}{' + den + '}')
        }
      }
      while (regFrac3.test(expresLatex)) {
        // on trouve ../..
        const tabFrac3 = expresLatex.match(regFrac3)
        for (i = 0; i < tabFrac3.length; i++) {
          tabSeparateur = tabFrac3[i].split('/')
          num = tabSeparateur[0].substring(0, tabSeparateur[0].length)
          den = tabSeparateur[1].substring(0, tabSeparateur[1].length)
          expresLatex = expresLatex.replace(tabFrac3[i], '\\frac{' + num + '}{' + den + '}')
        }
      }
      // on enlève signe * devant x
      while (expresLatex.includes('*x')) {
        expresLatex = expresLatex.replace('*x', 'x')
      }
      if (expresLatex.includes('x²')) {
        expresLatex = expresLatex.replace('x²', 'x^2')
      }
      break
    default: // c’est que modele vaut 1' ou le modèle 5
      // fonction (ax+b)exp(x) ou bien (ax+b)exp(cx+d)
      // on enlève signe * devant x
      while (expresLatex.includes('*x')) {
        expresLatex = expresLatex.replace('*x', 'x')
      }
      // on enlève signe * devant exp
      while (expresLatex.includes('*e')) {
        expresLatex = expresLatex.replace('*e', 'e')
      }
      expresLatex = expresLatex.replace(/-1e/, '-e') // pour éviter d’écrire -1exp(...)
      {
        const tabExp = expresLatex.match(regExpo)
        const nbExp = tabExp[0].substring(4, tabExp[0].length - 1)
        expresLatex = expresLatex.replace(tabExp[0], '\\mathrm{e}^{' + fractionLatex(nbExp) + '}')
      }
      // console.log("expresLatex avant fraction:"+expresLatex)
      expresLatex = fractionLatex(expresLatex)
      break
  }
  // dernière étape, je fais en sorte que les parenthèses soient grandes si besoin
  let expresLatexGrandePar = ''
  for (i = 0; i < expresLatex.length; i++) {
    if (expresLatex[i] === '(') {
      expresLatexGrandePar += '\\left'
    } else if (expresLatex[i] === ')') {
      expresLatexGrandePar += '\\right'
    }
    expresLatexGrandePar += expresLatex[i]
  }
  expresLatex = expresLatexGrandePar
  // il ne reste plus qu'à faire en sorte qu’on ait une virgule comme séparateur des décimaux'
  while (expresLatex.includes('.')) {
    expresLatex = expresLatex.replace('.', ',')
  }
  // console.log("expresLatex:"+expresLatex)
  return (expresLatex)
} // fin fctsEtudeEcritureLatexFonction

/**
 *
 * @param expresFct
 * @param modele
 * @param domaineDerivee
 * @param {boolean} [isDebug=false]
 * @returns {{}}
 */
export function fctsEtudeEcritureLatexDerivee (expresFct, modele, domaineDerivee, isDebug = false) {
  // cette fonction renvoie un objet contenant :
  // - l’expression de la dérivée au format latex
  // - les facteurs présents sous la forme d’un tableau en [0], ceux du numérateur et en [1] ceux du dénominateur
  // - les zéros de chaque facteur
  // expresFct est l’expression de la fonction'
  // on précise le modele de fonction pour permettre de mieux gérer la transformation'
  // on précise aussi le domaine de dérivabilité de la fonction, c’est utile car certaons facteurs vont être positifs sur ce domaine alors qu’ils ne le sont pas si le domaine est le plus large possible
  // isDebug permet d’afficher des infos de debuggage en console
  const objet = {}// on y place tout ce dont on a besoin pour la dérivée (expression au format latex, facteurs au format latex, valeurs où ils s’annulent au format latex)
  // objet.expres_derivee est l’expression de la dérivée au format latex
  objet.tabFacteurs = []
  // objet.tabFacteurs est un tableau qui contiendra 2 éléments
  objet.tabFacteurs[0] = []
  // tabFacteurs[0] est un tableau contenant tous les facteurs du numérateur de la dérivée
  objet.tabFacteurs[1] = []
  // tabFacteurs[1] est un tableau contenant tous les facteurs du dénominateur de la dérivée
  objet.zeros_facteurs = []
  objet.zeros_facteurs[0] = []
  objet.zeros_facteurs[1] = []
  // objet.zeros_facteurs est un tableau. Chaque élément sera un tableau avec les zéros du facteur associé (on prend d’abord en compte les facteurs du numérateur puis ceux du dénominateur)
  // cependant, il peut parfois être utile d’avoir tous les zéros des facteurs, y compris ceux qui ne sont pas dans le domaine
  objet.zeros_facteurs_init = []
  objet.zeros_facteurs_init[0] = []
  objet.zeros_facteurs_init[1] = []
  objet.signes_facteurs = []
  // objet.signes_facteurs est un tableau. Chaque élément est un tableau qui contient les signes du facteurs par rapport à ses zéros
  objet.variables = []
  // objet.variables est un tableau contenant les variables qu’on a pu récupérer
  let i, dxpluse, coefA, coefB, coefC, fctAffine, expresFctXcas, numDerivee, tabNum, tabDen, tabFraction
  let delta, deltaTxt, num, den, denDerivee, racineMin, racineMax, racine1Txt, racine2Txt, numeDerive
  let coefAprime, coefBprime, coefCprime, racine1, racine2, leZero
  // var lnRegexp = new RegExp('(\\-?\d{0,}\\*?ln\\sx)|(\\-?\d{0,}\\*?ln\\(x\\))', 'ig')
  // @todo remplacer par
  const lnRegexp = /-?\d*\*?ln(?:\sx|\(x\))/ig
  // var lnRegexp = /-?\d*(?:\s*\*\s*)?ln(?:\sx|\(x\))/ig
  // (?:\s*\*\s*)? signifie éventuellement un signe * entouré d’évetuels espaces, ?: pour dire que c’est non capturant
  // cf https://developer.mozilla.org/fr/docs/Web/JavaScript/Guide/Regular_Expressions#special-non-capturing-parentheses
  switch (modele) {
    case 7:// modèle ax+b+cln(dx+e)
      expresFctXcas = j3pMathquillXcas(expresFct)
      if (expresFctXcas.includes('(')) {
        // il y a des parenthèses, donc dx+e ne se réduit à pas à x
        dxpluse = expresFctXcas.substring(expresFctXcas.indexOf('(') + 1, expresFctXcas.indexOf(')'))
      } else {
        dxpluse = 'x'
      }
      {
        const coefDe = extraireCoefsFctaffine(dxpluse)
        const d = coefDe[0]
        const e = coefDe[1]
        const coefDevantlnReg = /-?\d+\*?ln/ig // j’ai laissé + et non *
        const coefDevantlnReg2 = /-?ln/ig
        if (coefDevantlnReg.test(expresFctXcas)) {
          coefC = expresFctXcas.match(coefDevantlnReg)
        } else {
          coefC = expresFctXcas.match(coefDevantlnReg2)
        }
        let c = coefC[0].replace('ln', '')
        if (c === '') {
          c = '1'
        } else if (c === '-') {
          c = '-1'
        } else if (c[c.length - 1] === '*') {
          c = c.substring(0, c.length - 1)
        }
        // console.log("dxpluse:"+dxpluse+"   d:"+d+"   e:"+e+"   c:"+c+"   coefC:"+coefC)
        if (c === '') {
          fctAffine = expresFctXcas.substring(0, '\\ln')
        } else {
          fctAffine = expresFctXcas.substring(0, expresFctXcas.indexOf(coefC[0]))
        }
        if (fctAffine[fctAffine.length - 1] === '+') {
          fctAffine = fctAffine.substring(0, fctAffine.length - 1)
        }
        const coefsab = extraireCoefsFctaffine(fctAffine)
        const a = coefsab[0]
        const b = coefsab[1]
        const coefAnum = fctsEtudeProduitNbs(a, d)
        const coefBnum = fctsEtudeSommeNbs(fctsEtudeProduitNbs(a, e), fctsEtudeProduitNbs(c, d))
        numeDerive = fctsEtudeEcrireMonome(1, 1, coefAnum) + fctsEtudeEcrireMonome(2, 0, coefBnum)
        objet.expres_derivee = '\\frac{' + numeDerive + '}{' + fctsEtudeEcrireMonome(1, 1, d) + fctsEtudeEcrireMonome(2, 0, e) + '}'
        objet.tabFacteurs[0][0] = numeDerive
        objet.tabFacteurs[1][0] = fctsEtudeEcrireMonome(1, 1, d) + fctsEtudeEcrireMonome(2, 0, e)
        objet.zeros_facteurs_init[0] = [fctsEtudeDivisionNbs(fctsEtudeProduitNbs('-1', coefBnum), coefAnum)]
        if (fctsEtudeEstDansDomaine(objet.zeros_facteurs_init[0][0], domaineDerivee)) {
          objet.zeros_facteurs[0] = [objet.zeros_facteurs_init[0][0]]
        } else {
          objet.zeros_facteurs[0] = []
        }
        if (!objet.zeros_facteurs[0][0] || !fctsEtudeEstDansDomaine(objet.zeros_facteurs[0][0], domaineDerivee)) {
        // objet.zeros_facteurs[0][0] vaut undefined si le facteur ne s’annule pas sur IR donc dans ce cas, on ne cherche pas s’il est dans le domaine --> cela génère une erreur
        // c’est que le facteur affine est de signe constant
        // ici le domaine ne peut pas être une réunion d’intervalles, mais juste un intervalle
          objet.signes_facteurs[0] = fctsEtudeSigneFct(objet.expres_derivee, domaineDerivee, [])
        } else {
          if (fctsEtudeCalculValeur(coefAnum) < 0) {
            objet.signes_facteurs[0] = ['+', '-']
          } else {
            objet.signes_facteurs[0] = ['-', '+']
          }
        }
        objet.zeros_facteurs_init[1] = [fctsEtudeDivisionNbs(fctsEtudeProduitNbs('-1', e), d)]
        objet.zeros_facteurs[1] = []
        objet.signes_facteurs[1] = ['+']
        objet.variables.push(a, b, c, d, e, coefAnum, coefBnum, objet.zeros_facteurs_init[0][0])
      }
      break
    case 6:// modèle (a+bln(x))/x^c
      // coefs de l’écriture (a+bln(x))/x^c
      // coefs de l’écriture de la dérivée (sous la forme (a'+b’lnx)/x^c')
      expresFctXcas = j3pMathquillXcas(expresFct)
      num = expresFctXcas.substring(0, expresFctXcas.indexOf('/'))
      tabNum = num.match(lnRegexp)
      if (tabNum[0].includes('\\ln')) {
        coefB = tabNum[0].substring(0, tabNum[0].indexOf('\\ln'))
      } else {
        coefB = tabNum[0].substring(0, tabNum[0].indexOf('ln'))
      }
      if (coefB[coefB.length - 1] === '*') {
        coefB = coefB.substring(0, coefB.length - 1)// j’ai viré le signe de multiplication qui pourrait trainer.
      }
      if (coefB === '-') {
        coefB = '-1'
      } else if (coefB === '') {
        coefB = '1'
      }
      coefA = num.replace(tabNum[0], '')
      if (coefA[0] === '(') {
        coefA = coefA.substring(1)
      }
      if (coefA[coefA.length - 1] === ')') {
        coefA = coefA.substring(0, coefA.length - 1)
      }
      if (coefA[0] === '+') {
        coefA = coefA.substring(1)
      } else if (coefA[coefA.length - 1] === '+') {
        coefA = coefA.substring(0, coefA.length - 1)
      }
      den = expresFctXcas.substring(expresFctXcas.indexOf('/') + 1)
      coefC = 1
      if ((den !== 'x') && (den !== '(x)')) {
        // c’est que la puissance de x n’est pas 1
        coefC = den.substring(den.indexOf('^') + 1)
        if ((coefC[0] === '{') || (coefC[0] === '(')) {
          coefC = coefC.substring(1)
        }
        while ((coefC[coefC.length - 1] === '}') || (coefC[coefC.length - 1] === ')')) {
          coefC = coefC.substring(0, coefC.length - 1)
        }
      }
      // console.log("coefA:"+coefA+"    coefB:"+coefB+"   coefC:"+coefC)
      coefAprime = fctsEtudeSommeNbs(coefB, fctsEtudeProduitNbs('-1', fctsEtudeProduitNbs(coefA, coefC)))
      coefBprime = fctsEtudeProduitNbs('-1', fctsEtudeProduitNbs(coefB, coefC))
      coefCprime = fctsEtudeSommeNbs(coefC, 1)
      numeDerive = fctsEtudeEcrireMonome(1, 0, coefAprime) + fctsEtudeEcrireMonome(2, 1, coefBprime, '\\ln x')
      if (Math.abs(j3pCalculValeur(coefAprime)) < epsilon) {
        numeDerive = fctsEtudeEcrireMonome(1, 1, coefBprime, '\\ln x')
      }
      objet.expres_derivee = '\\frac{' + numeDerive + '}{x^{' + coefCprime + '}}'
      objet.tabFacteurs[0][0] = numeDerive
      {
        const puisZeronum = fctsEtudeDivisionNbs(fctsEtudeProduitNbs('-1', coefAprime), coefBprime)
        if (Math.abs(fctsEtudeCalculValeur(puisZeronum)) < epsilon) {
        // c’est zéro, donc le numérateur s’annule en e^0=1
          objet.zeros_facteurs_init[0] = ['1']
        } else if (Math.abs(fctsEtudeCalculValeur(puisZeronum) - 1) < epsilon) {
          objet.zeros_facteurs_init[0] = ['\\mathrm{e}']
        } else {
          objet.zeros_facteurs_init[0] = ['\\mathrm{e}^{' + puisZeronum + '}']
        }
      }
      if (fctsEtudeEstDansDomaine(objet.zeros_facteurs_init[0][0], domaineDerivee)) {
        objet.zeros_facteurs[0] = [objet.zeros_facteurs_init[0][0]]
      } else {
        objet.zeros_facteurs[0] = []
      }
      if (!fctsEtudeEstDansDomaine(objet.zeros_facteurs[0][0], domaineDerivee)) {
        // c’est que le numérateur est de signe constant
        // ici le domaine ne peut pas être une réunion d’intervalles, mais juste un intervalle
        objet.signes_facteurs[0] = fctsEtudeSigneFct(objet.expres_derivee, domaineDerivee, [])
      } else {
        if (fctsEtudeCalculValeur(coefBprime) < 0) {
          objet.signes_facteurs[0] = ['+', '-']
        } else {
          objet.signes_facteurs[0] = ['-', '+']
        }
      }
      objet.tabFacteurs[1][0] = 'x^{' + coefCprime + '}'
      objet.signes_facteurs[1] = ['+']
      objet.variables.push(coefA, coefB, coefC, coefAprime, coefBprime, coefCprime, objet.zeros_facteurs_init[0][0])
      break
    case 5:// modèle (ax+b)exp(cx+d)
      {
        expresFctXcas = j3pMathquillXcas(expresFct)
        if (expresFctXcas.indexOf('-e^') === 0) {
        // c’est que la fonction est de la forme -exp(cx+d)+e
          fctAffine = '-1'
        } else {
          fctAffine = expresFctXcas.substring(0, expresFctXcas.indexOf('*e^'))
        }
        const puissance = expresFct.substring(expresFct.lastIndexOf('{') + 1, expresFct.lastIndexOf('}'))
        if (fctAffine[0] === '(') { // je vais virer les parenthèses
          fctAffine = fctAffine.substring(fctAffine.indexOf('(') + 1, fctAffine.indexOf(')'))
        }
        const tabCoefAffine1 = extraireCoefsFctaffine(fctAffine, 'x')
        const tabCoefAffine2 = extraireCoefsFctaffine(puissance, 'x')
        coefA = tabCoefAffine1[0]
        coefB = tabCoefAffine1[1]
        coefC = tabCoefAffine2[0]
        const coefD = tabCoefAffine2[1] // coef de l’écriture (ax+b)exp(cx+d)+e'
        const coefE = (expresFct.lastIndexOf('}') === expresFct.length - 1) ? 0 : fctsEtudeCalculValeur(expresFct.substring(expresFct.lastIndexOf('}') + 1))
        const coefaDeriv = fctsEtudeProduitNbs(coefA, coefC)
        const coefbDeriv = fctsEtudeSommeNbs(coefA, fctsEtudeProduitNbs(coefB, coefC))
        if ((coefbDeriv === '0') || (coefbDeriv === 0)) {
          objet.expres_derivee = fctsEtudeEcrireMonome(1, 1, coefaDeriv) + '\\mathrm{e}^{' + fctsEtudeEcrireMonome(1, 1, coefC) + fctsEtudeEcrireMonome(2, 0, coefD) + '}'
          objet.tabFacteurs[0][0] = fctsEtudeEcrireMonome(1, 1, coefaDeriv)
          if (fctsEtudeEstDansDomaine('0', domaineDerivee)) {
            objet.zeros_facteurs[0] = ['0']
          } else {
            objet.zeros_facteurs[0] = []
          }
          objet.zeros_facteurs_init[0] = ['0']
        } else if ((coefaDeriv === '0') || (coefaDeriv === 0)) {
          objet.expres_derivee = (Number(coefbDeriv) === 1)
            ? '\\mathrm{e}^{' + fctsEtudeEcrireMonome(1, 1, coefC) + fctsEtudeEcrireMonome(2, 0, coefD) + '}'
            : (Number(coefbDeriv) === -1)
                ? '-\\mathrm{e}^{' + fctsEtudeEcrireMonome(1, 1, coefC) + fctsEtudeEcrireMonome(2, 0, coefD) + '}'
                : fctsEtudeEcrireMonome(1, 0, coefbDeriv) + '\\mathrm{e}^{' + fctsEtudeEcrireMonome(1, 1, coefC) + fctsEtudeEcrireMonome(2, 0, coefD) + '}'
          if (Number(coefbDeriv) !== 1) {
            objet.tabFacteurs[0][0] = fctsEtudeEcrireMonome(1, 0, coefbDeriv)
          }
          objet.zeros_facteurs[0] = []
          objet.zeros_facteurs_init[0] = []
        } else {
          objet.expres_derivee = '\\left(' + fctsEtudeEcrireMonome(1, 1, coefaDeriv) + fctsEtudeEcrireMonome(2, 0, coefbDeriv) + '\\right)\\mathrm{e}^{' + fctsEtudeEcrireMonome(1, 1, coefC) + fctsEtudeEcrireMonome(2, 0, coefD) + '}'
          objet.tabFacteurs[0][0] = fctsEtudeEcrireMonome(1, 1, coefaDeriv) + fctsEtudeEcrireMonome(2, 0, coefbDeriv)
          objet.zeros_facteurs[0] = [fctsEtudeDivisionNbs(fctsEtudeProduitNbs('-1', coefbDeriv), coefaDeriv)]
          if (!fctsEtudeEstDansDomaine(fctsEtudeCalculValeur(objet.zeros_facteurs[0][0]), domaineDerivee)) {
            objet.zeros_facteurs[0] = []
          }
          objet.zeros_facteurs_init[0] = [fctsEtudeDivisionNbs(fctsEtudeProduitNbs('-1', coefbDeriv), coefaDeriv)]
        }
        if ((Number(coefaDeriv) !== 0) || (Number(coefbDeriv) !== 1)) {
          if (!objet.zeros_facteurs[0][0] || !fctsEtudeEstDansDomaine(objet.zeros_facteurs[0][0], domaineDerivee)) {
          // c’est que le facteur affine est de signe constant
          // ici le domaine ne peut pas être une réunion d’intervalles, mais juste un intervalle
            objet.signes_facteurs[0] = fctsEtudeSigneFct(objet.expres_derivee, domaineDerivee, [])
          } else {
            if (fctsEtudeCalculValeur(coefaDeriv) === 0) {
              if (fctsEtudeCalculValeur(coefbDeriv) < 0) {
                objet.signes_facteurs[0] = ['-']
              } else {
                objet.signes_facteurs[0] = ['+']
              }
            } else if (fctsEtudeCalculValeur(coefaDeriv) < 0) {
              objet.signes_facteurs[0] = ['+', '-']
            } else {
              objet.signes_facteurs[0] = ['-', '+']
            }
          }
        }
        objet.tabFacteurs[0].push('\\mathrm{e}^{' + fctsEtudeEcrireMonome(1, 1, coefC) + fctsEtudeEcrireMonome(2, 0, coefD) + '}')
        objet.signes_facteurs.push(['+'])
        objet.variables.push(coefA, coefB, coefC, coefD, coefE)
      }
      break
    case 4:// c’est le modèle 4
      // fonction (ax+b)/(cx^2+d)
      tabFraction = j3pMathquillXcas(expresFct).split('/')
      num = tabFraction[0].substring(1, tabFraction[0].length - 1)
      den = tabFraction[1].substring(1, tabFraction[1].length - 1)
      tabNum = extraireCoefsFctaffine(num)
      if (den.includes('x^2')) {
        // c’est x^2 qui est présent
        tabDen = extraireCoefsFctaffine(den, 'x^2')
      } else if (den.includes('x^{2}')) {
        // c’est plutôt x^{2}
        tabDen = extraireCoefsFctaffine(den, 'x^{2}')
      }
      // le numérateur est un polynôme de degré 2
      {
        const coef2Num = fctsEtudeProduitNbs('-1', fctsEtudeProduitNbs(tabDen[0], tabNum[0]))
        const coef1Num = fctsEtudeProduitNbs('-2', fctsEtudeProduitNbs(tabDen[0], tabNum[1]))
        const coef0Num = fctsEtudeProduitNbs(tabDen[1], tabNum[0])
        numDerivee = fctsEtudeEcrireMonome(1, 2, coef2Num) + fctsEtudeEcrireMonome(2, 1, coef1Num) + fctsEtudeEcrireMonome(3, 0, coef0Num)
        denDerivee = '(' + fctsEtudeEcrireMonome(1, 2, tabDen[0]) + fctsEtudeEcrireMonome(2, 0, tabDen[1]) + ')^2'
        objet.expres_derivee = '\\frac{' + numDerivee + '}{' + denDerivee + '}'
        objet.tabFacteurs[0][0] = numDerivee
        objet.tabFacteurs[1][0] = denDerivee
        // on cherche les racines du trinôme. Calculons le discriminant :
        const valCoef2Num = fctsEtudeCalculValeur(coef2Num)
        const valCoef1Num = fctsEtudeCalculValeur(coef1Num)
        delta = Math.pow(valCoef1Num, 2) - 4 * coef2Num * coef0Num
        deltaTxt = ''
        if (delta < 0) {
        // pas de racine
          if (valCoef2Num < 0) {
            objet.signes_facteurs[0] = ['-']
          } else {
            objet.signes_facteurs[0] = ['+']
          }
          objet.zeros_facteurs[0] = []
        } else if (delta === 0) {
        // une racine unique
          leZero = fctsEtudeDivisionNbs(fctsEtudeProduitNbs('-1', coef1Num), fctsEtudeProduitNbs('2', coef2Num))
          if (valCoef2Num < 0) {
            if (fctsEtudeEstDansDomaine(leZero, domaineDerivee)) {
              objet.signes_facteurs[0] = ['-', '-']
            } else {
              objet.signes_facteurs[0] = ['-']
            }
          } else {
            if (fctsEtudeEstDansDomaine(leZero, domaineDerivee)) {
              objet.signes_facteurs[0] = ['+', '+']
            } else {
              objet.signes_facteurs[0] = ['+']
            }
          }
          if (fctsEtudeEstDansDomaine(leZero, domaineDerivee)) {
            objet.zeros_facteurs[0] = [leZero]
          } else {
            objet.zeros_facteurs[0] = []
          }
          objet.zeros_facteurs_init[0] = [leZero]
        } else {
        // deux racines
          racine1 = (-valCoef1Num + Math.sqrt(delta)) / (2 * valCoef2Num)
          racine2 = (-valCoef1Num - Math.sqrt(delta)) / (2 * valCoef2Num)
          deltaTxt = fctsEtudeSommeNbs(fctsEtudeProduitNbs(coef1Num, coef1Num), fctsEtudeProduitNbs('-1', fctsEtudeProduitNbs(4, fctsEtudeProduitNbs(coef2Num, coef0Num))))
          racine1Txt = simplificationRacineTrinome(coef1Num, '+', deltaTxt, coef2Num)
          racine2Txt = simplificationRacineTrinome(coef1Num, '-', deltaTxt, coef2Num)
          if (isDebug) {
            console.debug('racine1Txt:' + racine1Txt + '   racine2Txt:' + racine2Txt)
            console.debug('racine1:' + racine1 + '   racine2:' + racine2)
          }
          racineMin = racine2Txt
          racineMax = racine1Txt
          if (racine1 < racine2) {
            racineMin = racine1Txt
            racineMax = racine2Txt
          }
          objet.signes_facteurs[0] = fctsEtudeSigneFct(objet.expres_derivee, domaineDerivee, [racineMin, racineMax])
          if (fctsEtudeEstDansDomaine(racineMin, domaineDerivee)) {
            if (fctsEtudeEstDansDomaine(racineMax, domaineDerivee)) {
              objet.zeros_facteurs[0] = [racineMin + '|' + racineMax]
            } else {
              objet.zeros_facteurs[0] = [racineMin]
            }
          } else {
            if (fctsEtudeEstDansDomaine(racineMax, domaineDerivee)) {
              objet.zeros_facteurs[0] = [racineMax]
            } else {
              objet.zeros_facteurs[0] = []
            }
          }
          objet.zeros_facteurs_init[0] = [String(racineMin) + '|' + String(racineMax)]
        }
        objet.zeros_facteurs[1] = []
        objet.zeros_facteurs_init[1] = []// le dénominateur ne s’annule pas, quelque soit le domaine
        if (isDebug) console.debug('expresFct:' + expresFct + '    objet.expres_derivee:' + objet.expres_derivee + '   deltaTxt:' + deltaTxt + '       objet.zeros_facteurs:' + objet.zeros_facteurs + '    objet.signes_facteurs:' + objet.signes_facteurs)
        objet.variables = [tabNum[0], tabNum[1], tabDen[0], tabDen[1]]// (ax+b)/cx+d) dans l’ordre des variables
        // j’y ajoute les coefs du trinôme et deltaTxt
        objet.variables.push(coef2Num, coef1Num, coef0Num, deltaTxt, delta)
        if (delta > 0) {
          objet.variables.push(racine1Txt)
        }
        objet.signes_facteurs.push(['+'])
      }
      break
    case 3:// c’est le modèle 3
      // fonction (ax+b)/(cx+d)
      tabFraction = j3pMathquillXcas(expresFct).split('/')
      num = tabFraction[0].substring(1, tabFraction[0].length - 1)
      den = tabFraction[1].substring(1, tabFraction[1].length - 1)
      tabNum = extraireCoefsFctaffine(num)
      tabDen = extraireCoefsFctaffine(den)
      numDerivee = fctsEtudeSommeNbs(fctsEtudeProduitNbs(tabNum[0], tabDen[1]), fctsEtudeProduitNbs('-1', fctsEtudeProduitNbs(tabNum[1], tabDen[0])))
      {
        const valeurNum = fctsEtudeCalculValeur(numDerivee)
        denDerivee = '(' + fctsEtudeEcrireMonome(1, 1, tabDen[0]) + fctsEtudeEcrireMonome(2, 0, tabDen[1]) + ')^2'
        objet.expres_derivee = '\\frac{' + numDerivee + '}{' + denDerivee + '}'
        objet.tabFacteurs[0][0] = numDerivee
        objet.zeros_facteurs[0] = []
        objet.zeros_facteurs_init[0] = []
        if (valeurNum > 0) {
          objet.signes_facteurs[0] = ['+']
        } else {
          objet.signes_facteurs[0] = ['-']
        }
      }
      objet.tabFacteurs[1][0] = denDerivee
      objet.zeros_facteurs_init[1] = [fctsEtudeDivisionNbs(fctsEtudeProduitNbs('-1', tabDen[1]), tabDen[0])]

      objet.zeros_facteurs[1] = fctsEtudeEstDansDomaine(fctsEtudeCalculValeur(-tabDen[1] / tabDen[0]), fctsEtudeTransformationDomaine(domaineDerivee)) ? [fctsEtudeDivisionNbs(fctsEtudeProduitNbs('-1', tabDen[1]), tabDen[0])] : []
      objet.signes_facteurs.push(fctsEtudeSigneFct(objet.tabFacteurs[1][0], domaineDerivee, objet.zeros_facteurs[1]))
      // objet.signes_facteurs.push(["+","+"]);
      objet.variables = [tabNum[0], tabNum[1], tabDen[0], tabDen[1]]
      break
    case 2:// c’est que modele vaut 2 fonction ax^3+bx^2+cx+d avec coefs au format latex
    case 8:// c’est que modele vaut 8 fonction ax^2+bx+c avec coefs au format latex
      {
        const posSigne = [0]
        for (i = 1; i < expresFct.length; i++) {
          if ((expresFct[i] === '+') || (expresFct[i] === '-')) {
            posSigne.push(i)
          }
        }
        posSigne.push(expresFct.length)
        // je découpe alors l’expression à l’aide de ces signes, cela me donnera les monômes
        const tabMonome = []
        for (i = 1; i < posSigne.length; i++) {
          tabMonome.push(expresFct.substring(posSigne[i - 1], posSigne[i]))
        }
        const tabCoefPolynome = (modele === 2) ? ['0', '0', '0', '0'] : ['0', '0', '0']
        // tabCoefPolynome[0] est le coef constant, tabCoefPolynome[1], celui devant x, ...
        for (i = 0; i < tabMonome.length; i++) {
        // tabMonome.length<=4 c’est le nombre de monomes présents
          if ((tabMonome[i].includes('x^{3}')) || (tabMonome[i].includes('x^3'))) {
          // c’est le monome ax^3
            if (tabMonome[i].charAt(0) === '+') {
              tabMonome[i] = tabMonome[i].substring(1)
            }
            if (tabMonome[i].includes('x^{3}')) {
              tabCoefPolynome[3] = tabMonome[i].substring(0, tabMonome[i].length - 5)
            } else {
              tabCoefPolynome[3] = tabMonome[i].substring(0, tabMonome[i].length - 3)
            }
            if (tabCoefPolynome[3] === '') {
              tabCoefPolynome[3] = '1'
            } else if (tabCoefPolynome[3] === '-') {
              tabCoefPolynome[3] = '-1'
            }
          } else if ((tabMonome[i].includes('x^{2}')) || (tabMonome[i].includes('x^2'))) {
          // c’est le monome ax^2
            if (tabMonome[i].charAt(0) === '+') {
              tabMonome[i] = tabMonome[i].substring(1)
            }
            if (tabMonome[i].includes('x^{2}')) {
              tabCoefPolynome[2] = tabMonome[i].substring(0, tabMonome[i].length - 5)
            } else {
              tabCoefPolynome[2] = tabMonome[i].substring(0, tabMonome[i].length - 3)
            }
            if (tabCoefPolynome[2] === '') {
              tabCoefPolynome[2] = '1'
            } else if (tabCoefPolynome[2] === '-') {
              tabCoefPolynome[2] = '-1'
            }
          } else if (tabMonome[i].indexOf('x') > -1) {
          // c’est le monome ax
            if (tabMonome[i].charAt(0) === '+') {
              tabMonome[i] = tabMonome[i].substring(1)
            }
            tabCoefPolynome[1] = tabMonome[i].substring(0, tabMonome[i].length - 1)
            if (tabCoefPolynome[1] === '') {
              tabCoefPolynome[1] = '1'
            } else if (tabCoefPolynome[1] === '-') {
              tabCoefPolynome[1] = '-1'
            }
          } else {
          // c’est le monome a
            if (tabMonome[i].charAt(0) === '+') {
              tabCoefPolynome[0] = tabMonome[i].substring(1)
            } else {
              tabCoefPolynome[0] = tabMonome[i]
            }
          }
        }
        if (modele === 8) {
          coefAprime = fctsEtudeProduitNbs(tabCoefPolynome[2], '2')
          coefBprime = tabCoefPolynome[1]
          objet.expres_derivee = fctsEtudeEcrireMonome(1, 1, coefAprime)
          if (coefBprime !== '0') {
            objet.expres_derivee = objet.expres_derivee + fctsEtudeEcrireMonome(2, 0, coefBprime)
          }
          if (isDebug) console.debug('coefAprime:' + coefAprime + '   coefBprime:' + coefBprime + '   objet.expres_derivee:' + objet.expres_derivee)
          objet.tabFacteurs[0][0] = objet.expres_derivee
          // On cherche la valeur en laquelle s’annule la fonction
          leZero = fctsEtudeDivisionNbs(fctsEtudeProduitNbs('-1', coefBprime), coefAprime)
          const valCoefAprime = fctsEtudeCalculValeur(coefAprime)
          objet.signes_facteurs[0] = (valCoefAprime > 0) ? ['-', '+'] : ['+', '-']
          objet.zeros_facteurs[0] = (fctsEtudeEstDansDomaine(leZero, domaineDerivee)) ? [leZero] : []
          objet.zeros_facteurs_init[0] = [leZero]
          objet.variables = tabCoefPolynome
        } else {
        // coefs de la dérivée
          coefAprime = fctsEtudeProduitNbs(tabCoefPolynome[3], '3')
          coefBprime = fctsEtudeProduitNbs(tabCoefPolynome[2], '2')
          coefCprime = tabCoefPolynome[1]
          objet.expres_derivee = fctsEtudeEcrireMonome(1, 2, coefAprime)
          if (coefBprime !== '0') {
            objet.expres_derivee = objet.expres_derivee + fctsEtudeEcrireMonome(2, 1, coefBprime)
          }
          if (coefCprime !== '0') {
            objet.expres_derivee = objet.expres_derivee + fctsEtudeEcrireMonome(3, 0, coefCprime)
          }
          if (isDebug) console.debug('coefAprime:' + coefAprime + '   coefBprime:' + coefBprime + '   coefCprime:' + coefCprime + '   objet.expres_derivee:' + objet.expres_derivee)
          objet.tabFacteurs[0][0] = objet.expres_derivee
          // on cherche les racines du trinôme. Calculons le discriminant :
          const valCoefAprime = fctsEtudeCalculValeur(coefAprime)
          const valCoefBprime = fctsEtudeCalculValeur(coefBprime)
          const valCoefCprime = fctsEtudeCalculValeur(coefCprime)
          delta = Math.pow(valCoefBprime, 2) - 4 * valCoefAprime * valCoefCprime
          if (delta < 0) {
          // pas de racine
            if (valCoefAprime < 0) {
              objet.signes_facteurs[0] = ['-']
            } else {
              objet.signes_facteurs[0] = ['+']
            }
            objet.zeros_facteurs[0] = []
          } else if (delta === 0) {
          // une racine unique
            leZero = fctsEtudeDivisionNbs(fctsEtudeProduitNbs('-1', coefBprime), fctsEtudeProduitNbs('2', coefAprime))
            if (valCoefAprime < 0) {
              if (fctsEtudeEstDansDomaine(leZero, domaineDerivee)) {
                objet.signes_facteurs[0] = ['-', '-']
              } else {
                objet.signes_facteurs[0] = ['-']
              }
            } else {
              if (fctsEtudeEstDansDomaine(leZero, domaineDerivee)) {
                objet.signes_facteurs[0] = ['+', '+']
              } else {
                objet.signes_facteurs[0] = ['+']
              }
            }
            if (fctsEtudeEstDansDomaine(leZero, domaineDerivee)) {
              objet.zeros_facteurs[0] = [leZero]
            } else {
              objet.zeros_facteurs[0] = []
            }
            objet.zeros_facteurs_init[0] = [leZero]
          } else {
          // deux racines
            racine1 = (-valCoefBprime + Math.sqrt(delta)) / (2 * valCoefAprime)
            racine2 = (-valCoefBprime - Math.sqrt(delta)) / (2 * valCoefAprime)
            deltaTxt = fctsEtudeSommeNbs(fctsEtudeProduitNbs(coefBprime, coefBprime), fctsEtudeProduitNbs('-1', fctsEtudeProduitNbs(4, fctsEtudeProduitNbs(coefAprime, coefCprime))))
            racine1Txt = simplificationRacineTrinome(coefBprime, '+', deltaTxt, coefAprime)
            racine2Txt = simplificationRacineTrinome(coefBprime, '-', deltaTxt, coefAprime)
            if (isDebug) console.debug('racine1Txt:' + racine1Txt + '   racine2Txt:' + racine2Txt)
            if (isDebug) console.debug('racine1:' + racine1 + '   racine2:' + racine2)
            racineMin = racine2Txt
            racineMax = racine1Txt
            if (racine1 < racine2) {
              racineMin = racine1Txt
              racineMax = racine2Txt
            }
            objet.signes_facteurs[0] = fctsEtudeSigneFct(objet.expres_derivee, domaineDerivee, [racineMin, racineMax])
            if (fctsEtudeEstDansDomaine(racineMin, domaineDerivee)) {
              if (fctsEtudeEstDansDomaine(racineMax, domaineDerivee)) {
                objet.zeros_facteurs[0] = [racineMin + '|' + racineMax]
              } else {
                objet.zeros_facteurs[0] = [racineMin]
              }
            } else {
              if (fctsEtudeEstDansDomaine(racineMax, domaineDerivee)) {
                objet.zeros_facteurs[0] = [racineMax]
              } else {
                objet.zeros_facteurs[0] = []
              }
            }
            objet.zeros_facteurs_init[0] = [racineMin + '|' + racineMax]
          }
          if (isDebug) console.debug('expresFct:' + expresFct + '    tabMonome:' + tabMonome + '   tabCoefPolynome:' + tabCoefPolynome + '    objet.expres_derivee:' + objet.expres_derivee + '   deltaTxt:' + deltaTxt + '       objet.zeros_facteurs:' + objet.zeros_facteurs + '    objet.signes_facteurs:' + objet.signes_facteurs)
          objet.variables = tabCoefPolynome
        }
        // j’y ajoute les coefs du trinôme et deltaTxt
        objet.variables.push(coefAprime, coefBprime, coefCprime, deltaTxt, delta)
        if (delta > 0) {
          objet.variables.push(racine1Txt)
        }
      }
      break
    default:// c’est donc quand le modele est 1'
      // coef de l’écriture (ax+b)exp(x)'
      fctAffine = j3pMathquillXcas(expresFct).substring(0, j3pMathquillXcas(expresFct).indexOf('*e^(x)'))
      if ((fctAffine[0] === '(') && (fctAffine[fctAffine.length - 1] === ')')) {
        fctAffine = fctAffine.substring(1, fctAffine.length - 1)
      }
      {
        const posDecoup = Math.max(fctAffine.lastIndexOf('+'), fctAffine.lastIndexOf('-'))// cela me donne la position du signe entre b et ax
        if (posDecoup <= 0) {
        // c’est que c’est de la forme ax donc b vaut 0'
          coefA = fctAffine.substring(0, fctAffine.length - 1)
          if (coefA === '') {
            coefA = 1
          } else if (coefA === '-') {
            coefA = -1
          }
          coefB = '0'
        } else {
          if (fctAffine[fctAffine.length - 1] === 'x') {
          // fctAffine est écrit sous la forme b+ax
            coefB = fctAffine.substring(0, posDecoup)
            if (fctAffine[posDecoup] === '-') {
              coefA = fctAffine.substring(posDecoup, fctAffine.length - 1)
            } else {
              coefA = fctAffine.substring(posDecoup + 1, fctAffine.length - 1)
            }
            if (coefA === '') {
              coefA = '1'
            } else if (coefA === '-') {
              coefA = '-1'
            }
          } else {
          // fctAffine est écrit sous la forme ax+b
            coefA = fctAffine.substring(0, posDecoup - 1)
            if (fctAffine[posDecoup] === '-') {
              coefB = fctAffine.substring(posDecoup)
            } else {
              coefB = fctAffine.substring(posDecoup + 1)
            }
            if (coefA === '') {
              coefA = '1'
            } else if (coefA === '-') {
              coefA = '-1'
            }
          }
        }
        if (coefA.charAt(coefA.length - 1) === '*') {
          coefA = coefA.substring(0, coefA.length - 1)
        }
        coefA = fractionLatex(coefA)
        coefB = fractionLatex(coefB)
        // la dérivée vaut (ax+b+a)exp(x)
        const coefBPrime = fctsEtudeSommeNbs(coefA, coefB)
        if ((coefBPrime === '0') || (coefBPrime === 0)) {
          objet.expres_derivee = fctsEtudeEcrireMonome(1, 1, coefA) + '\\mathrm{e}^x'
          objet.tabFacteurs[0][0] = fctsEtudeEcrireMonome(1, 1, coefA)
          if (fctsEtudeEstDansDomaine('0', domaineDerivee)) {
            objet.zeros_facteurs[0] = ['0']
          } else {
            objet.zeros_facteurs[0] = []
          }
          objet.zeros_facteurs_init[0] = ['0']
        } else {
          objet.expres_derivee = '\\left(' + fctsEtudeEcrireMonome(1, 1, coefA) + fctsEtudeEcrireMonome(2, 0, coefBPrime) + '\\right)\\mathrm{e}^x'
          objet.tabFacteurs[0][0] = fctsEtudeEcrireMonome(1, 1, coefA) + fctsEtudeEcrireMonome(2, 0, coefBPrime)
          objet.zeros_facteurs[0] = [fctsEtudeDivisionNbs(fctsEtudeProduitNbs('-1', coefBPrime), coefA)]
          if (!fctsEtudeEstDansDomaine(fctsEtudeCalculValeur(objet.zeros_facteurs[0][0]), domaineDerivee)) {
            objet.zeros_facteurs[0] = []
          }
          objet.zeros_facteurs_init[0] = [fctsEtudeDivisionNbs(fctsEtudeProduitNbs('-1', coefBPrime), coefA)]
        }
        if (!objet.zeros_facteurs[0][0] || !fctsEtudeEstDansDomaine(objet.zeros_facteurs[0][0], domaineDerivee)) {
        // c’est que le facteur affine est de signe constant
        // ici le domaine ne peut pas être une réunion d’intervalles, mais juste un intervalle
          objet.signes_facteurs[0] = fctsEtudeSigneFct(objet.expres_derivee, domaineDerivee, [])
        // objet.zeros_facteurs[0] = [];
        } else {
          if (fctsEtudeCalculValeur(coefA) < 0) {
            objet.signes_facteurs[0] = ['+', '-']
          } else {
            objet.signes_facteurs[0] = ['-', '+']
          }
        }
        objet.tabFacteurs[0][1] = '\\mathrm{e}^x'
        objet.signes_facteurs[1] = ['+']
        objet.variables.push(coefA, coefB)
      }
      break
  }
  if (isDebug) console.debug('expres_derivee:' + objet.expres_derivee + '  tabFacteurs:' + objet.tabFacteurs + '   zeros_facteurs:' + objet.zeros_facteurs + '    zeros_facteurs_init:' + objet.zeros_facteurs_init)
  return objet
} // fin fctsEtudeEcritureLatexDerivee

export function fctsEtudeExpressionAvecFacteurs (tabFacteurs) {
  // tabFacteurs est un tableau de 2 éléments
  // tabFacteurs[0] est un tableau contenant les facteurs du numérateur
  // tabFacteurs[1] est un tableau contenant les facteurs du dénominateur
  // chaque facteur est au format latex
  // cette fonction renvoie l’expression au format latex du quotient (ou du produit s’il n’y a pas de dénominateur)
  // on utilisera cette fonction pour savoir de quelle expression il reste à donner le signe après avoir donné les facteurs strictement positifs
  function ecrireProduit (facteurs) {
    // cette fonction permet d’écrire le produit en y mettant les parenthèses nécessaires
    // on ne fait appel à cette fonction que lorsque facteurs.length>1
    let produit = ''
    // var exp_reg = /\\mathrm\{e\}^\{.*\}/i
    // @todo la regexp suivante semble mieux, pour éviter de matcher une double accolade fermante et imposer un argument e^<qqchose>
    // FIXME les regexp suivantes matchent du \truc, pas \\truc, pour choper \\truc c’est /\\\\truc/ qu’il faut
    const cleanRegexps = [
      /\\mathrm{e}\^{[^}]+}/i,
      /\\(cos|ln|sin)\([^)]+\)/i,
      /\\sqrt{[^}]+}/i
    ]

    // ces expressions régulières sont là pour gérer la présence de parenthèses autour de facteurs comme (1-exp(x+1))
    facteurs.forEach(function (facteur) {
      const isNegatif = /^-/.test(facteur)
      let cleanFacteur = isNegatif ? String(facteur).substring(1) : String(facteur) // on vire le signe - du début de la chaîne
      cleanRegexps.forEach(function (regexp) {
        cleanFacteur = cleanFacteur.replace(regexp, '')
      })
      /* Les facteurs ne sont pas ceux donnés par l’élève mais ceux qui sont générés par la section
      Si c’est le premier facteur et qu’il était de la forme -2x, alors le signe - a été viré et on ne met pas de parenthèses
      Sinon, s’il existe une somme ou une différence, alors on met des parenthèses (polynôme de degré supérieur ou égal à 1 non réduit à un monome)
      */
      if (/[+-]/.test(cleanFacteur)) {
        // c’est qu’il y a une somme ou une différence dans ce facteur donc je lui mets des parenthèses
        produit += '\\left(' + facteur + '\\right)'
      } else {
        // dans ce cas pas besoin de parenthèses
        produit += facteur
      }
    })
    return produit
  } // ecrireProduit

  let numerateur = ''
  let denominateur = ''
  let expression = ''
  if (tabFacteurs[0].length === 0) {
    // tous les facteurs du numérateur étaient positifs// donc il ne reste plus que le dénominateur
    if (tabFacteurs[1].length === 0) {
      // tous les facteurs du dénominateur sont également positifs
      // numerateur et dénominateur sont donc vides
      // au final on ne fait rien
    } else {
      // l’expression initiale est du signe du dénominateur donc dans l’écriture, je mets le dénominateur au numérateur
      expression = fctsEtudeExpressionAvecFacteurs([tabFacteurs[1], tabFacteurs[0]])
    }
  } else {
    if (tabFacteurs[0].length === 1) {
      // donc le numérateur ne contient qu’un seul facteur
      numerateur = tabFacteurs[0][0]
    } else {
      numerateur = ecrireProduit(tabFacteurs[0])
    }
    if (tabFacteurs[1].length === 0) {
      expression = numerateur
    } else {
      if (tabFacteurs[1].length === 1) {
        // donc le dénominateur ne contient qu’un seul facteur
        denominateur = tabFacteurs[1][0]
      } else {
        denominateur = ecrireProduit(tabFacteurs[1])
      }
      expression = '\\frac{' + numerateur + '}{' + denominateur + '}'
    }
  }
  return expression
} // fin fctsEtudeExpressionAvecFacteurs

/**
 * renvoie sous la forme d’un texte latex le monome demandé
 * @param rang position du monome dans l’écriture
 * @param puis puissance de x
 * @param {string} nb coef écrit sous forme latex (\\frac{...}{...} pour des nb fractionnaires)
 * @param {string} [nomVar=x]
 * @returns {string}
 */
export function fctsEtudeEcrireMonome (rang, puis, nb, nomVar = 'x') {
  let monomeTxt
  const elementNb = fctsEtudeExtraireNumDen(nb)
  if (elementNb[0]) { // c’est un nb fractionnaire'
    if (elementNb[3] === '-') { // nb est négatif, dans ce cas, il faut que je récupère le signe dans le numérateur de la fraction
      const newNum = elementNb[1].substring(1)
      const newDen = elementNb[2]
      monomeTxt = '-\\frac{' + newNum + '}{' + newDen + '}'
    } else {
      if (rang === 1) {
        monomeTxt = nb
      } else {
        monomeTxt = '+' + nb
      }
    }
    // monomeTxt est initialisé, on complète
    if (puis === 1) {
      monomeTxt += nomVar
    } else if (puis > 1) {
      monomeTxt += nomVar + '^{' + puis + '}'
    }
  } else {
    monomeTxt = j3pMonome(rang, puis, j3pNombre(String(nb)), nomVar)
  }
  return monomeTxt
} // fin fctsEtudeEcrireMonome

/**
 *
 * @param tab
 * @returns {string}
 */
export function fctsEtudeEcritureDomaine (tab) {
  // le domaine de définition (ou de dérivabilité) d’une fonction est donné sous la forme d’un tableau
  // on le renvoie au format latex
  let leDomaine = ''
  if (tab.length === 4) {
    if ((tab[0] === '-infini') && (tab[1] === '+infini')) {
      // le domaine est \\R
      leDomaine = '\\R'
    } else {
      let borneInf = '-\\infty'
      if (tab[0] !== '-infini') {
        borneInf = fctsEtudeEcrireNbLatex(tab[0])
      } else {
        tab[2] = ']'
      }
      let borneSup = '+\\infty'
      if (tab[1] !== '+infini') {
        borneSup = fctsEtudeEcrireNbLatex(tab[1])
      } else {
        tab[3] = '['
      }
      leDomaine = tab[2] + borneInf + ';' + borneSup + tab[3]
    }
  } else {
    if (tab.length > 4) {
      // c’est que j’ai une réunion de deux intervalles (on ne devrait pas avoir d’exemple plus compliqué que ça...)
      if (tab[1] === tab[5]) {
        // c’est que le domaine peut s'écrire sous la forme \\R\\setminus\\{...\\}
        leDomaine = '\\R\\setminus\\left\\{' + fctsEtudeEcrireNbLatex(tab[1]) + '\\right\\}'
      } else {
        // on écrit le domaine sous la forme de la réunion de deux intervalles (on devrait se limiter à ce cas de figure)
        let borne1 = '-\\infty'
        if (tab[0] !== '-infini') {
          borne1 = fctsEtudeEcrireNbLatex(tab[0])
        } else {
          tab[2] = ']'
        }
        const borne2 = fctsEtudeEcrireNbLatex(tab[1])
        const borne3 = fctsEtudeEcrireNbLatex(tab[4])
        let borne4 = '+\\infty'
        if (tab[5] !== '+infini') {
          borne4 = fctsEtudeEcrireNbLatex(tab[5])
        } else {
          tab[7] = '['
        }
        leDomaine = tab[2] + borne1 + ';' + borne2 + tab[3] + '\\cup' + tab[6] + borne3 + ';' + borne4 + tab[7]
      }
    }
  }
  return leDomaine
} // fin fctsEtudeEcritureDomaine
export function fctsEtudeEstDansDomaine (nb, domaine) {
  // cette fonction vérifie si le nombre nb est dans le domaine de définition (au sens strict)
  // nb peut être un nombre ou une chaine de caractère correspodnant à un nombre : "2/3", "esp(5)-1", "ln(3)", "racine(5)" pa exemple
  // domaine est un tableau de deux éléments
  // domaine[0] représente un nombre du même style que nb ou -infini
  // domaine[1] représente un nombre du même style que nb ou +infini
  let appartientDomaine = true
  const nbval = fctsEtudeCalculValeur(nb)
  if (domaine[0] !== '-infini') {
    const borneInfval = fctsEtudeCalculValeur(domaine[0])
    appartientDomaine = (nbval > borneInfval)
  }
  if (domaine[1] !== '+infini') {
    const borneSupval = fctsEtudeCalculValeur(domaine[1])
    appartientDomaine = (appartientDomaine && (nbval < borneSupval))
  }
  return appartientDomaine
} // fin fctsEtudeEstDansDomaine

/**
 *
 * @param domaineInit
 * @returns {string[]}
 */
export function fctsEtudeTransformationDomaine (domaineInit) {
  // domaineInit est le véritable domaine de définition de la fonction, écrit sous la forme ["-infini",a,"]","[",b,"+infini","]","["] par exemple
  // Cependant pour les fonction avec valeurs interdites, ces valeurs interdites ne doivent pas être prises en compte dans le domaine par moment
  // c’est le cas pour les zéros des fonctions car cette valeur interdite est bien un zéro du dénominateur
  const newDomaine = [domaineInit[0]]
  let pos = 1
  while (pos < domaineInit.length) {
    if (pos + 3 < domaineInit.length) {
      // il faut que je vérifie si la valeur en pos est la même qu’en pos+3, ce serait alors juste une valeur interdite
      if (domaineInit[pos] !== domaineInit[pos + 3]) {
        newDomaine.push(domaineInit[pos], domaineInit[pos + 1], domaineInit[pos + 2], domaineInit[pos + 3])
      }
      pos = pos + 4
    } else {
      newDomaine.push(domaineInit[pos], domaineInit[pos + 1], domaineInit[pos + 2])
      pos = pos + 4
    }
  }
  return newDomaine
} // fin fctsEtudeTransformationDomaine

/**
 * renvoie le domaine en y excluant les valeurs interdites si elles sont dedans
 * @param {Array<string|number>} domaineInit domaine de définition initial (peut-être celui qui est imposé)
 * @param {Array<string|number>} tabValInterdites valeurs interdites de la fonction
 * @param {boolean} isDebug
 * @returns Array<string|number>
 */
export function fctsEtudeReconstructionDomaine (domaineInit, tabValInterdites, isDebug) {
  if (!Array.isArray(domaineInit)) {
    console.error(`domaineInit devrait être un tableau et ici on a ${typeof domaineInit}`, domaineInit)
    domaineInit = ['-infini', '+infini', ']', '[']
  }
  if (!Array.isArray(tabValInterdites)) {
    console.error(`tabValInterdites devrait être un tableau et ici on a ${typeof tabValInterdites}`, tabValInterdites)
    tabValInterdites = []
  }
  let inf, sup
  let newDomaine = []
  const valInterditeAAjouter = fctsEtudeOrdonneTabSansDoublon(tabValInterdites, domaineInit).map(el => ecritBienFraction(el))
  let i, borneInterdite, valeurBorne, valInterditeI
  if (valInterditeAAjouter.length > 0) {
    let numVal = 0
    let posDomaine = 1
    newDomaine.push(domaineInit[0])
    while (numVal < valInterditeAAjouter.length && posDomaine <= domaineInit.length) {
      if (domaineInit[posDomaine - 1].includes('-infini')) {
        inf = -Math.pow(10, 15)
      } else {
        inf = fctsEtudeCalculValeur(domaineInit[posDomaine - 1])
      }
      if (domaineInit[posDomaine].includes('+infini')) {
        sup = Math.pow(10, 15)
      } else {
        sup = fctsEtudeCalculValeur(domaineInit[posDomaine])
      }
      const valeurinterdite = fctsEtudeCalculValeur(valInterditeAAjouter[numVal])
      if (isAlmostEqual(valeurinterdite, inf)) {
        numVal++
      } else if (isAlmostEqual(valeurinterdite, sup)) {
        borneInterdite = false
        valeurBorne = sup
        for (i = 0; i < valInterditeAAjouter.length; i++) {
          valInterditeI = fctsEtudeCalculValeur(valInterditeAAjouter[i])
          if (isAlmostEqual(valeurBorne, valInterditeI)) {
            borneInterdite = true
          }
        }
        if (borneInterdite) {
          // la dernière valeur est interdite, donc le crochet associé est ouvert
          newDomaine.push(ecritBienFraction(domaineInit[posDomaine]), ecritBienFraction(domaineInit[posDomaine + 1]), '[')
        } else {
          newDomaine.push(ecritBienFraction(domaineInit[posDomaine]), ecritBienFraction(domaineInit[posDomaine + 1]), ecritBienFraction(domaineInit[posDomaine + 2]))
        }
        posDomaine += 4
        numVal++
      } else if ((valeurinterdite > inf) && (valeurinterdite < sup)) {
        borneInterdite = false
        valeurBorne = fctsEtudeCalculValeur(newDomaine[newDomaine.length - 1])
        for (i = 0; i < valInterditeAAjouter.length; i++) {
          valInterditeI = fctsEtudeCalculValeur(valInterditeAAjouter[i])
          if (isAlmostEqual(valeurBorne, valInterditeI)) {
            borneInterdite = true
          }
        }
        if (borneInterdite) {
          // la dernière valeur est interdite, donc le crochet associé est ouvert
          newDomaine.push(valInterditeAAjouter[numVal], ']', '[', valInterditeAAjouter[numVal])
        } else {
          newDomaine.push(valInterditeAAjouter[numVal], domaineInit[posDomaine + 1], '[', valInterditeAAjouter[numVal])
        }
        numVal++
      } else {
        // on n’ajoute pas de valeur interdite ici
        borneInterdite = false
        valeurBorne = fctsEtudeCalculValeur(newDomaine[newDomaine.length - 1])
        for (i = 0; i < valInterditeAAjouter.length; i++) {
          valInterditeI = fctsEtudeCalculValeur(valInterditeAAjouter[i])
          if (isAlmostEqual(valeurBorne, valInterditeI)) {
            borneInterdite = true
          }
        }
        if (borneInterdite) {
          // la dernière valeur est interdite, donc le crochet associé est ouvert
          newDomaine.push(domaineInit[posDomaine], ']', domaineInit[posDomaine + 2], domaineInit[posDomaine + 3])
        } else {
          newDomaine.push(domaineInit[posDomaine], domaineInit[posDomaine + 1], domaineInit[posDomaine + 2], domaineInit[posDomaine + 3])
        }
        posDomaine += 4
      }
    }
    if (domaineInit[posDomaine]) { // On peut se retrouver avec un undefined ce qui explique cette rustine
      newDomaine.push(domaineInit[posDomaine])
      if (valInterditeAAjouter.join().includes(newDomaine[newDomaine.length - 2])) {
        newDomaine.push(']')
      } else {
        newDomaine.push(domaineInit[posDomaine + 1])
      }
      if (valInterditeAAjouter.join().includes(newDomaine[newDomaine.length - 1])) {
        newDomaine.push('[]]')
      } else {
        newDomaine.push(domaineInit[posDomaine + 2])
      }
    } else {
      // Je ne sais pas pourquoi mais parfois il faut virer une valeur de newDomaine qui vaut undefined
      if (!newDomaine[newDomaine.length - 1]) newDomaine.pop()
    }
  } else {
    newDomaine = newDomaine.concat(domaineInit)
    // attention car le domaine peut être fermé en l’une des bornes qui se trouve finalement dans le tableau des valeurs interdites
    if (String(domaineInit[0]).includes('-infini')) {
      inf = -Math.pow(10, 15)
    } else {
      inf = fctsEtudeCalculValeur(domaineInit[0])
    }
    if (domaineInit[domaineInit.length - 3]?.includes('+infini')) {
      sup = Math.pow(10, 15)
    } else {
      sup = fctsEtudeCalculValeur(domaineInit[domaineInit.length - 3])
    }
    for (i = 0; i < tabValInterdites.length; i++) {
      valInterditeI = fctsEtudeCalculValeur(tabValInterdites[i])
      if (isAlmostEqual(inf, valInterditeI)) {
        newDomaine[2] = ']'
      }
      if (isAlmostEqual(sup, valInterditeI)) {
        newDomaine[newDomaine.length - 1] = '['
      }
    }
  }
  if (isDebug) console.debug('newDomaine:' + newDomaine + '   tabValInterdites:' + tabValInterdites + '   valInterditeAAjouter:' + valInterditeAAjouter)
  return newDomaine
} // fin fctsEtudeReconstructionDomaine

/**
 *
 * @param fDex
 * @returns {string}
 */
export function fctsEtudeTransformeExp (fDex) {
  // fDex est une chaine de caractère correspondant à l’espression d’une fonction : f(x)
  // elle revoie la même chaine en remplaçant e^... par exp(...)
  // on appelle cette fonction après avoir utilisé j3pMathquillXcas, donc il n’y a plus frac{..}{..} mais (..)/(..)
  // Attention car e n’est pas seulement pour exp, il peut aussi l'être pour racine
  let newFdeX = ''
  let finFdex = fDex
  while (finFdex.includes('ne')) {
    newFdeX += finFdex.substring(0, finFdex.indexOf('ne') + 2)
    finFdex = finFdex.substring(finFdex.indexOf('ne') + 2)
  }
  let posE = finFdex.indexOf('e')
  while (posE > -1) {
    // e est présent et ce n’est aps celui de racine
    newFdeX += finFdex.substring(0, posE)
    // on regarde ce qui suit e
    if ((finFdex.charAt(posE + 1) === 'x') && (finFdex.charAt(posE + 2) === 'p')) {
      // il est déjà écrit exp, donc on passe
      newFdeX += 'exp'
      finFdex = finFdex.substring(posE + 3)
    } else if ((finFdex.charAt(posE + 1) === '+') || (finFdex.charAt(posE + 1) === '-') || (finFdex.charAt(posE + 1) === '*') || (finFdex.charAt(posE + 1) === ')') || (finFdex.charAt(posE + 1) === 'x')) {
      // e est tout seul, je le remplace par exp(1)
      newFdeX += 'exp(1)'
      finFdex = finFdex.substring(posE + 1)
    } else {
      // ce qui suit e est ^. Il faut alors trouver les parenthèses qui encadrent ce dont on prend l’exponentielle
      if (finFdex.charAt(posE + 2) === '(') {
        // c’est de la forme e^(...)
        let parOuverte = 1
        let posParFerme = posE + 3
        while (parOuverte > 0) {
          if (finFdex.charAt(posParFerme) === '(') {
            parOuverte++
          } else if (finFdex.charAt(posParFerme) === ')') {
            parOuverte--
          }
          posParFerme++
        }
        newFdeX += 'exp(' + finFdex.substring(posE + 3, posParFerme)
        finFdex = finFdex.substring(posParFerme)
      } else {
        // c’est uste de la forme e^., cad que seul le caractère suivant est est en puissance
        newFdeX += 'exp(' + finFdex.substring(posE + 2, posE + 3) + ')'
        finFdex = finFdex.substring(posE + 3)
      }
    }
    posE = finFdex.indexOf('e')
  }
  newFdeX += finFdex
  // J’ai encore un souci car il ne reconnait pas 3e comme un produit, donc il faut le lui expliquer...
  newFdeX = newFdeX.replace(/(\d)e/g, '$1*e')
  return newFdeX
}

/**
 * Transforme l’écriture de texteFonction pour avoir l’expression sous la forme permettant de créer un arbre et d’appliquer la fonction evalue
 * @param {string} texteFonction L’expression au format latex ou tout autre format intermédiaire
 * @returns {string}
 */
export function fctsEtudeEcriturePourCalcul (texteFonction) {
  // je convertis pour que ce soit plus facile à utiliser
  // @todo remplacer j3pMathquillXcas par mqNormalise et vérifier que ça fonctionne comme attendu (ça devrait)
  // Je peux aussi avoir un souci avec ln a qui serait écrit sans parenthèses. Il faut que j’en mette
  let newExpressionFct = String(texteFonction).replace(/\\ln\s?([0-9]+[,.][0-9]+)/g, 'ln($1)') // Pour les décimaux
  newExpressionFct = newExpressionFct.replace(/\\ln\s?([a-z]|[0-9]+)/g, 'ln($1)') // pour les entiers où une variable
  newExpressionFct = newExpressionFct.replace(/\\ln\s?\\frac\{([^}]+)}\{([^}]+)}/g, 'ln($1/$2)') // pour les fractions
  newExpressionFct = j3pMathquillXcas(newExpressionFct)
  // gestion de la racine carrée
  // il faut que je transforme sqrt en racine
  while (newExpressionFct.includes('sqrt')) {
    newExpressionFct = newExpressionFct.replace('sqrt', 'racine')
  }
  // mais j’ai encore un problème avec l’exponentielle : je peux avoir e isolé, ou bien e^x ou bien e^{...}. Ceschoses ne sont pas comprises, il faut modifier ça et écrire sous la forme exp(...)
  newExpressionFct = fctsEtudeTransformeExp(newExpressionFct)
  // on peut encore avoir un soucis si l’élève écrit exp(), cad s’il ne met rien en puissance. On va le considérer comme exp(1)
  while (newExpressionFct.includes('exp()')) {
    newExpressionFct = newExpressionFct.replace('exp()', 'exp(1)')
  }
  return newExpressionFct
}

/**
 * À décrire
 * @param {string|number} valeur
 * @returns {number} (NaN si valeur n’est ni string ni number)
 */
export function fctsEtudeCalculValeur (valeur) {
  if (typeof valeur === 'number') valeur = String(valeur)
  if (typeof valeur !== 'string') {
    console.error(Error(`valeur incorrecte (${typeof valeur})`))
    return Number.NaN
  }
  if (!valeur) {
    console.error(Error('paramètre incorrect (chaîne vide)'))
    return Number.NaN
  }
  // un nombre écrit sous forme d’une chaine de caractères va être calculé pour qu’on ait sa valeur décimale
  // correctif pour les cas où on entre (a+racine(b))/c (modele 2 avec domaine surchargé) on remplace d’abord par des sqrt pour ne pas avoir de pb avec j3pMathquillXcas
  while (valeur.includes('racine')) {
    valeur = valeur.replace('racine', 'sqrt')
  }
  valeur = fctsEtudeEcriturePourCalcul(valeur)
  const arbreChaine = new Tarbre(valeur, [])
  return arbreChaine.evalue([])
}

/**
 * Renvoie un tableau contenant les éléments de tab rangés dans l’ordre croissant
 * Si tab contient deux éléments égaux, un seul apparaîtra dans le tableau final
 * Vérifie aussi que les valeurs sont bien dans le domaine de définition
 * @param {Array<number|string>}tab
 * @param {Array<number|string>} [domaineDef] Les bornes, -infini géré
 * @returns {string[]}
 */
export function fctsEtudeOrdonneTabSansDoublon (tab, domaineDef) {
  if (!Array.isArray(tab)) throw Error('paramètre invalide')

  // si y’a rien à faire inutile d’aller plus loin
  if (!tab.length) return []

  const domaine = (Array.isArray(domaineDef) && domaineDef.length === 2) ? [...domaineDef] : ['-infini', '+infini']
  const tabOrd = []
  // il ne faut pas changer le tableau initial, d’où cette pseudo copie (pas forcément de la même taille,
  // avec manipulation de chaque elt par Tarbre qui peut générer des pbs d’arrondis).
  // Ça ne contient que des strings, ces strings correspondant aux valeurs exactes, surtout pas les arrondis
  /** @type {string[]} */
  const tabStr = []
  for (const nb of tab) {
    const orig = String(nb)
    if (orig !== '') {
      if (orig.includes('|')) {
        // il y a plusieurs valeurs à ajouter dans le tableau
        const valeurs = orig.split('|')
        for (const valeur of valeurs) {
          // je ne prends pas la valeur si elle n’est pas dans le domaine de def'
          if ((valeur !== '') && fctsEtudeEstDansDomaine(valeur, domaine)) {
            tabStr.push(String(valeur))
          }
        }
      } else {
        // là il n’y a qu’une valeur à ajouter'
        if (!orig.includes('infini')) {
          if (fctsEtudeEstDansDomaine(nb, domaine)) {
            // je ne prends pas la valeur si elle n’est pas dans le domaine de def'
            tabStr.push(String(orig))
          }
        } else if ((orig === '-infini') && (domaine[0] === '-infini')) {
          tabStr.push('-infini')
        }
        // Dans le tableau final, -infini et +infini ne seront pas conservés, voilà pourquoi je ne prends pas +infini
        // -infini, je l’ai gardé, mais j’aurai pu faire autrement (22 lignes plus loin, on voit qu’on ne le prend pas)
      }
    }
  }
  // tabStr contient alors toutes les valeurs EXACTES présentes dans tab (éventuellement au format latex), toutes en string
  // si on a rien trouvé on arrête là
  if (tabStr.length === 0) return []
  // si y’en a qu’un on le renvoie
  if (tabStr.length === 1) return tabStr

  // on en fait une copie pour la réduire dans la boucle (strings only)
  const tabTmp = [...tabStr]
  // à chaque passage dans la boucle, je récupère l’élément minimal de tabTmp et je vire toutes ses occurrences
  while (tabTmp.length !== 0) {
    // on prend le plus petit
    const minRestant = tabTmp.includes('-infini') ? '-infini' : minTab(tabTmp).min
    tabOrd.push(minRestant)
    // on supprime alors dans tabTmp toutes les occurrences
    // J’utilise une boucle classique car la taille de tabTmp évolue
    for (let pos = 0; pos < tabTmp.length; pos++) {
      const elt = tabTmp[pos]
      let aVirer = false
      if (elt === minRestant) {
        aVirer = true
      } else if (['-infini', '-Infinity'].includes(elt)) {
        aVirer = true
      } else if (isAlmostEqual(elt, minRestant)) {
        aVirer = true
      }
      if (aVirer) {
        tabTmp.splice(pos, 1)
        // il faut rester sur cet elt, au cas où y’en aurait un 2e à virer dans tabTmp
        pos--
      }
    }
  }
  // tabOrd contient alors les valeurs exactes de la liste des valeurs passées en argument (dans tab)
  return tabOrd
} // fctsEtudeOrdonneTabSansDoublon

/**
 *
 * @param {number} nb
 * @returns {*}
 */
export function fctsEtudeEcrireNbLatex (nb) {
  // un nombre est écrit au format mathématique, on le convertit au format latex
  const newNb = (typeof nb === 'string' && nb.includes('/')) ? fractionLatex(nb) : String(nb)
  // on fait aussi attention à la présence de exp, ln ou racine
  const chaineExp = /exp\([0-9x,.^+-]+\)/ig
  const chaineLn = /ln\([0-9x,.^+-]+\)/ig
  const chaineRacine = /racine\([0-9x,.^+-]+\)/ig
  let i3
  let expressTabZero
  // gestion de exp et ln
  let expressionZero = newNb
  if (chaineExp.test(expressionZero)) {
    // dan ce cas exp(...) est présent et on le transforme en e^...
    expressTabZero = expressionZero.match(chaineExp)
    for (i3 = 0; i3 < expressTabZero.length; i3++) {
      const puissanceExp = expressTabZero[i3].substring(4, expressTabZero[i3].length - 1)
      if (puissanceExp === '1') {
        expressionZero = expressionZero.replace(expressTabZero[i3], '\\mathrm{e}')
      } else {
        expressionZero = expressionZero.replace(expressTabZero[i3], '\\mathrm{e}^{' + puissanceExp + '}')
      }
    }
  }
  // maintenant gestion d’une chaine avec ln'
  if (chaineLn.test(expressionZero)) {
    // dan ce cas ln(...) est présent et on le transforme en \\ln(...)
    expressTabZero = expressionZero.match(chaineLn)
    for (i3 = 0; i3 < expressTabZero.length; i3++) {
      const expressLn = expressTabZero[i3].substring(3, expressTabZero[i3].length - 1)
      expressionZero = expressionZero.replace(expressTabZero[i3], '\\ln(' + expressLn + ')')
    }
  }
  // et enfin gestion de la racine carrée
  if (chaineRacine.test(expressionZero)) {
    // dan ce cas racine(...) est présent et on le transforme en \\sqrt{...}
    expressTabZero = expressionZero.match(chaineRacine)
    for (i3 = 0; i3 < expressTabZero.length; i3++) {
      const radical = expressTabZero[i3].substring(7, expressTabZero[i3].length - 1)
      expressionZero = expressionZero.replace(expressTabZero[i3], '\\sqrt{' + radical + '}')
    }
  }
  return expressionZero
}

export function fctsEtudeNombreIntervalle (intervalle) {
  // intervalle est un tableau contenant en 0 la borne inf et en 1 la borneSup
  // On renvoie un objet
  // objet.nombre est un nombre présent dans l’intervalle
  // objet.tests_images est un tableau de valeurs présentes dans l’intervalle. Ces valeurs servent à tester des égalités d’expressions
  let eltVal
  let expressBorne
  let tableauImages = []// ce tableau va servir pour les tests d’égalités entre facteurs
  // il faut tout de même que les valeurs dont on cherche les images soient dans le domaine, d’où la nécessité de ce tableau
  let valInit
  if (intervalle[0] === '-infini') {
    if (intervalle[1] === '+infini') {
      eltVal = '0'
      tableauImages = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    } else {
      // intervalle ]-infty;a[
      expressBorne = j3pMathquillXcas(intervalle[1])
      while (expressBorne.includes('sqrt')) {
        expressBorne = expressBorne.replace('sqrt', 'racine')
      }
      eltVal = expressBorne + '-6'
      valInit = fctsEtudeCalculValeur(eltVal)// arbre_val.evalue([]);
      tableauImages = [valInit, valInit - 1, valInit - 2, valInit - 3, valInit - 4, valInit - 5, valInit - 6, valInit - 7, valInit - 8, valInit - 9]
    }
  } else {
    if (intervalle[1] === '+infini') {
      // intervalle ]a;+infty[
      expressBorne = j3pMathquillXcas(intervalle[0])
      eltVal = expressBorne + '+6'
      valInit = fctsEtudeCalculValeur(eltVal)// arbre_val.evalue([]);
      tableauImages = [valInit, valInit + 1, valInit + 2, valInit + 3, valInit + 4, valInit + 5, valInit + 6, valInit + 7, valInit + 8, valInit + 9]
    } else {
      // intervalle ]a;b[
      expressBorne = j3pMathquillXcas('(' + intervalle[0] + '+(' + intervalle[1] + '))/2')
      while (expressBorne.includes('sqrt')) {
        expressBorne = expressBorne.replace('sqrt', 'racine')
      }
      eltVal = expressBorne
      const borneInf = j3pMathquillXcas(intervalle[0])
      const valInit1 = fctsEtudeCalculValeur(borneInf)
      const borneSup = j3pMathquillXcas(intervalle[1])
      const valInit2 = fctsEtudeCalculValeur(borneSup)
      let val = (valInit1 + valInit2) / 2
      while ((tableauImages.length < 10) && (val > valInit1)) {
        tableauImages.push(val)
        val--
      }
      while ((tableauImages.length < 10) && (val < valInit2)) {
        tableauImages.push(val)
        val++
      }
    }
  }
  return { nombre: fctsEtudeCalculValeur(eltVal), tests_images: tableauImages }
}

export function fctsEtudeSigneFct (laFonction, domaine, zeros, isDebug = false) {
  // la fonction est l’expression de la fonction écrite soit au format latex soit dans un format plus mathématique
  // elle sera de tout façon convertie pour être utilisée comme on le souhaite à l’aide de la fonction ecriture_pour_calc
  // domaine est le domaine de définition sous la forme ["-infini",a,"]","[",b,c,"]","]"] par exemple
  // zeros est un tableau qui contient toutes les valeurs où s’annule la fonction. Ces valeurs sont au format latex
  // isDebug permet d’afficher des infos de debuggage en console
  const newDomaine = fctsEtudeTransformationDomaine(domaine)
  const tabIntervalles = tableauIntervalles(zeros, newDomaine, isDebug)
  if (isDebug) console.debug('tabIntervalles=', tabIntervalles, ' et newDomaine=', newDomaine)
  // le tableau précédent contient toutes les valeurs intervenant pour le signe de la fonction
  // je crée alors un tableau accueillant les signes successifs
  const tabSignes = []
  const fonctionPourCalc = fctsEtudeEcriturePourCalcul(laFonction)
  if (isDebug) console.debug('laFonction:', laFonction, fonctionPourCalc)
  const arbreEvalueDerivee = new Tarbre(fonctionPourCalc, ['x'])
  for (let i = 0; i < tabIntervalles.length - 1; i++) {
    const valX = fctsEtudeNombreIntervalle([tabIntervalles[i], tabIntervalles[i + 1]]).nombre// je prend un nombre de l’intervalle considéré. Son image sera celle de la fonction sur tout l’intervalle
    const image = arbreEvalueDerivee.evalue([valX])
    if (!isNaN(image)) {
      // je regarde le signe de l’image de valX par la fonction dérivée lorsque cette image existe bien
      if (image < 0) {
        tabSignes.push('-')
      } else {
        tabSignes.push('+')
      }
    } else {
      tabSignes.push('')// cela doit pouvoir se produire si par exemple on étudie f(x)=\sqrt{4-x^2} sur ]-\\infty;-2]\\cup [2;+\\infty[  sur [-2;2], je ne dois pas avoir de signe
    }
  }
  if (isDebug) console.debug('tabSignes=', tabSignes)
  return tabSignes
} // fin fctsEtudeSigneFct
/**
 * Construction du tableau de signes avec fonctions utiles
 * @param obj
 * @param tabDimension
 * @param zeroTab
 * @param valZeroTab
 * @param signeTabCorr
 * @param objStyleCouleur
 * @param paletteComplete
 * @param {boolean} [isDebug=false]
 */
export function fctsEtudeTableauSignesFct (obj, tabDimension, zeroTab, valZeroTab, signeTabCorr, objStyleCouleur, paletteComplete = false, isDebug = false) {
  // on crée un tableau de signes dans obj
  // ici il ne s’agit que du tableau du signe d’une fonction, pas d’un produit ou quotient
  // tabDimension est un tableau : [0] est la largeur et [1] la hauteur
  // tabDimension[2] est le nombre de valeurs à ajouter sur l’axe des abscisses (en plus des bornes)
  // tabDimension[3] est un tableau de deux éléments : les bornes les plus extrêmes du domaine de def de la fonction (écrire infini pour \\infty)'
  // tabDimension[4] est un tableau optionnel. S’il n’est pas undefined, c’est le domaine réel de définition sous la forme par exemple ["-infini",a,"]","[",a,"+infini","]","["] pour une valeur interdite
  // zeroTab[0] est un booléen. Il vaut true lorsque les valeurs en lesquelles s’annule l’expression n’est pas à remplir par l’élève
  // zeroTab[1] est un booléen. Il vaut true lorsque les signes sont complétés
  // valZeroTab est un tableau de valeurs en lesquelles s’annule la fonction
  // signeTabCorr est un tableau qui contient tous les signes attendus (dans l’ordre)
  // objStyleCouleur peut contenir 4 éléments (les 2 derniers étant obligatoires) : un style de texte, couleur si on ne définit par le style, texte qui sera "signe de ", nom_f
  // paletteComplete est un argument optionnel qui vaut false par défaut. On le passe à true lorsque l’exponentielle ou le logarithme sont affichés dans la palette
  // isDebug vaut false par défaut. Lorsqu’il est vrai, des infos apparaissent en console
  let i, j, borneInfTxt, borneSupTxt
  const zoneInput = { ligneX: [], signeFacts: [] }
  const listeInput = []
  const nomdiv = obj
  const macouleurTxt = (!objStyleCouleur.couleur) ? objStyleCouleur.style.color : objStyleCouleur.couleur
  const macouleur = '#000000'
  const txt2Txt = objStyleCouleur.texte + ' $' + objStyleCouleur.nom_f + '\\quad \'(x)$'
  const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
  const L = tabDimension[0]
  const h = tabDimension[1]
  svg.setAttribute('width', L)
  svg.setAttribute('height', h)
  nomdiv.appendChild(svg)
  j3pCreeRectangle(svg, { x: 1, y: 1, width: L - 1, height: h - 1, couleur: '#000000', epaisseur: 1 })
  j3pCreeSegment(svg, { x1: 1, y1: 0, x2: 0, y2: h, couleur: '#000000', epaisseur: 1 })
  j3pCreeSegment(svg, { x1: 1, y1: h / 2, x2: L, y2: h / 2, couleur: '#000000', epaisseur: 1 })
  // placement de la ligne "signe de f'(x)"
  const signeDe = j3pAddElt(nomdiv, 'div', '', { style: objStyleCouleur.style })
  j3pStyle(signeDe, { position: 'absolute' })
  j3pAffiche(signeDe, '', txt2Txt)
  // pour placer le segment vertical, je cherche la largeur de la phrase "signe de ..."
  // placement du "x"
  const leX = j3pAddElt(nomdiv, 'div', '', { style: objStyleCouleur.style })
  j3pStyle(leX, { position: 'absolute' })
  j3pAffiche(leX, '', '$x$')
  const largeurSignede = signeDe.getBoundingClientRect().width + 4
  j3pCreeSegment(svg, {
    x1: largeurSignede,
    y1: 0,
    x2: largeurSignede,
    y2: h,
    couleur: '#000000',
    epaisseur: 1
  })
  const posTxt = h / 2 + h / 4 - signeDe.getBoundingClientRect().height / 2
  signeDe.style.top = posTxt + 'px'
  signeDe.style.left = '2px'
  const posTxt2 = h / 4 - leX.getBoundingClientRect().height / 2
  leX.style.top = posTxt2 + 'px'
  const posTxt3 = largeurSignede / 2 - leX.getBoundingClientRect().width / 2
  leX.style.left = posTxt3 + 'px'
  if (tabDimension[3] === undefined) {
    borneInfTxt = '$-\\infty$'
    borneSupTxt = '$+\\infty$'
  } else {
    if ((tabDimension[3][0] === undefined) || (tabDimension[3][1] === undefined)) {
      borneInfTxt = '$-\\infty$'
      borneSupTxt = '$+\\infty$'
    } else {
      if (tabDimension[3][0] === '-infini') {
        borneInfTxt = '$-\\infty$'
      } else {
        borneInfTxt = '$' + fctsEtudeEcrireNbLatex(tabDimension[3][0]) + '$'
      }
      if (tabDimension[3][1] === '+infini') {
        borneSupTxt = '$+\\infty$'
      } else {
        borneSupTxt = '$' + fctsEtudeEcrireNbLatex(tabDimension[3][1]) + '$'
      }
    }
  }
  if (zeroTab[0]) {
    // on remplit entièrement la première ligne (sans zone de saisie)
    // plus et moins l’infini'
    const divMoinsInf = j3pAddElt(nomdiv, 'div', '', { style: objStyleCouleur.style })
    j3pStyle(divMoinsInf, { position: 'absolute' })
    const divPlusInf = j3pAddElt(nomdiv, 'div', '', { style: objStyleCouleur.style })
    j3pStyle(divPlusInf, { position: 'absolute' })
    // Je n’affiche les bornes que si on affiche toute la première ligne'
    j3pAffiche(divMoinsInf, '', borneInfTxt)
    divMoinsInf.style.left = (largeurSignede + 1) + 'px'
    divMoinsInf.style.top = (h / 4 - divMoinsInf.getBoundingClientRect().height / 2) + 'px'
    j3pAffiche(divPlusInf, '', borneSupTxt)
    divPlusInf.style.left = (L - divPlusInf.getBoundingClientRect().width - 2 + 1) + 'px'
    divPlusInf.style.top = (h / 4 - divPlusInf.getBoundingClientRect().height / 2) + 'px'
  }
  const tabZeroOrdonne = fctsEtudeOrdonneTabSansDoublon(valZeroTab, tabDimension[3])
  let nbValx = tabDimension[2]
  // pour gérer les bornes qui peuvent être des zéros ou des valeurs interdites
  // on y met soit un zéro, soit une double barre
  let newTab4
  if (!tabDimension[4]) {
    newTab4 = tabDimension[3]
    newTab4.push(']', '[')
  } else {
    newTab4 = tabDimension[4]
  }
  // dans le cas où on a changé le domaine de def, il faut qu’on regarde si les bornes sont des zéros de la fonction
  let tabValZeros = []
  for (i = 0; i < valZeroTab.length; i++) {
    tabValZeros = tabValZeros.concat(String(valZeroTab[i]).split('|'))
  }
  if (newTab4[0] !== '-infini') {
    for (j = 0; j < tabValZeros.length; j++) {
      if (isAlmostEqual(tabValZeros[j], newTab4[0])) {
        // c’est que la borne inf du domaine est un zéro de la fonction ou une valeur interdite
        if (newTab4[newTab4.length - 2] === '[') {
          // si c’est un zéro :
          const zeroBorneInf = j3pAddElt(nomdiv, 'div', '0', { style: objStyleCouleur.style })
          j3pStyle(zeroBorneInf, { position: 'absolute' })
          zeroBorneInf.style.left = (largeurSignede) + 'px'
          zeroBorneInf.style.top = (3 * h / 4 - zeroBorneInf.getBoundingClientRect().height / 2) + 'px'
        } else {
          // et pour une valeur interdite
          j3pCreeSegment(svg, {
            x1: largeurSignede + 5,
            y1: h / 2,
            x2: largeurSignede + 5,
            y2: h,
            couleur: macouleurTxt,
            epaisseur: 1
          })
        }
      }
    }
  }
  if (newTab4[newTab4.length - 3] !== '+infini') {
    for (j = 0; j < tabValZeros.length; j++) {
      if (isAlmostEqual(tabValZeros[j], newTab4[newTab4.length - 3])) {
        // c’est que la borne inf du domaine est un zéro de la fonction ou une valeur interdite
        if (newTab4[newTab4.length - 1] === ']') {
          // si c’est un zéro
          const zeroBorneSup = j3pAddElt(nomdiv, 'div', '0', { style: objStyleCouleur.style })
          j3pStyle(zeroBorneSup, { position: 'absolute' })
          zeroBorneSup.style.left = (L - zeroBorneSup.getBoundingClientRect().width - 2 + 1) + 'px'
          zeroBorneSup.style.top = (3 * h / 4 - zeroBorneSup.getBoundingClientRect().height / 2) + 'px'
        } else {
          // et pour une valeur interdite
          j3pCreeSegment(svg, {
            x1: L - 5,
            y1: h / 2,
            x2: L - 5,
            y2: h,
            couleur: macouleurTxt,
            epaisseur: 1
          })
        }
      }
    }
  }
  // Fin de la gestion du zéro aux bornes ou de la valeur interdite
  if (zeroTab[0]) {
    // les endroits où s’annulent la fonction sont donnés
    // attention les_zeros doivent être du type string (un entier ou un décimal ou un nb fractionnaire de la forme a/b
    // dans un premier temps, on ordonne les nombre du tableau valZeroTab (qui sont ls endroits où s’annulent notre produit)
    // Dans le cas où sont présents la fonction exp, la fonction ln ou la fonction racine, il va falloir gérer l’affichage'
    nbValx = tabZeroOrdonne.length
    for (i = 0; i < nbValx; i++) {
      const leZero = (['string', 'number'].includes(typeof tabZeroOrdonne[i])) ? tabZeroOrdonne[i].replace(/\./g, ',') : tabZeroOrdonne[i][0].replace(/\./g, ',')
      const monZeroTxt = fctsEtudeEcrireNbLatex(leZero)
      const zoneNulle = j3pAddElt(nomdiv, 'div', '', { style: objStyleCouleur.style })
      j3pStyle(zoneNulle, { position: 'absolute' })
      j3pAffiche(zoneNulle, '', '$' + monZeroTxt + '$')
      zoneNulle.style.left = (largeurSignede + (i + 1) * (L - largeurSignede) / (nbValx + 1) - zoneNulle.getBoundingClientRect().width / 2) + 'px'
      zoneNulle.style.top = (h / 4 - zoneNulle.getBoundingClientRect().height / 2) + 'px'
    }
  } else {
    // dans le cas où les valeur en lesquelles s’annule le produit sont à donner
    for (i = 0; i <= nbValx + 1; i++) {
      // i==0 pour la borne inf du domaine et i==valX+1 pour la borneSup du domaine
      const zoneNulle = j3pAddElt(nomdiv, 'div', '', { style: objStyleCouleur.style })
      j3pStyle(zoneNulle, { position: 'absolute' })
      const elt = j3pAffiche(zoneNulle, '', '&1&', { inputmq1: { texte: '' } })

      mqRestriction(elt.inputmqList[0], '\\d.,/+-', { commandes: ['fraction', 'inf', 'exp', 'ln'] })
      if (i === 0) {
        // placement de la borne inf
        zoneNulle.style.left = (largeurSignede + 2) + 'px'
      } else if (i === (nbValx + 1)) {
        // placement de la borne sup
        zoneNulle.style.left = (L - 5 - zoneNulle.getBoundingClientRect().width) + 'px'
      } else {
        zoneNulle.style.left = (largeurSignede + i * (L - largeurSignede) / (nbValx + 1) - zoneNulle.getBoundingClientRect().width / 2) + 'px'
      }
      zoneNulle.style.top = (h / 4 - zoneNulle.getBoundingClientRect().height / 2) + 'px'
      zoneInput.ligneX.push(elt.inputmqList[0])
      elt.inputmqList[0].leSpan = elt.parent.parentNode
      elt.inputmqList[0].num = i
    }
    for (i = 0; i <= nbValx + 1; i++) {
      zoneInput.ligneX[i].addEventListener('input', function () {
        if (this.num === 0) {
          this.leSpan.style.left = (largeurSignede + 2) + 'px'
        } else if (this.num === nbValx + 1) {
          this.leSpan.style.left = (L - 10 - this.getBoundingClientRect().width) + 'px'
        } else {
          this.leSpan.style.left = (largeurSignede + this.num * (L - largeurSignede) / (nbValx + 1) - this.getBoundingClientRect().width / 2) + 'px'
        }
        this.leSpan.style.top = (h / 4 - this.getBoundingClientRect().height / 2) + 'px'
      })
    }
    j3pFocus(zoneInput.ligneX[0])
  }
  // on crée tout d’abord les segments verticaux
  const posValZero = []
  for (i = 1; i <= nbValx; i++) {
    posValZero[i] = largeurSignede + i * (L - largeurSignede) / (nbValx + 1)
    j3pCreeSegment(svg, {
      x1: posValZero[i],
      y1: h / 2,
      x2: posValZero[i],
      y2: h,
      couleur: macouleur,
      epaisseur: 1
    })
  }
  if (zeroTab[1]) {
    // on donne le signe de la fonction
    // les zéros
    for (i = 1; i <= nbValx; i++) {
      if (fctsEtudeEstDansDomaine(tabZeroOrdonne[i - 1], newTab4)) {
        const zeroFct = j3pAddElt(nomdiv, 'div', '0', { style: objStyleCouleur.style })
        j3pStyle(zeroFct, { position: 'absolute' })
        zeroFct.style.left = (largeurSignede + i * (L - largeurSignede) / (nbValx + 1) - zeroFct.getBoundingClientRect().width / 2) + 'px'
        zeroFct.style.top = (3 * h / 4 - zeroFct.getBoundingClientRect().height / 2) + 'px'
      } else {
        j3pCreeSegment(svg, {
          x1: largeurSignede + i * (L - largeurSignede) / (nbValx + 1) + 3,
          y1: h / 2,
          x2: largeurSignede + i * (L - largeurSignede) / (nbValx + 1) + 3,
          y2: h,
          couleur: objStyleCouleur.couleur,
          epaisseur: 1
        })
        j3pCreeSegment(svg, {
          x1: largeurSignede + i * (L - largeurSignede) / (nbValx + 1),
          y1: h / 2,
          x2: largeurSignede + i * (L - largeurSignede) / (nbValx + 1),
          y2: h,
          couleur: objStyleCouleur.couleur,
          epaisseur: 1
        })
      }
    }

    // les signes
    for (i = 1; i <= nbValx + 1; i++) {
      const leSigne = j3pAddElt(nomdiv, 'div', '', { style: objStyleCouleur.style })
      j3pStyle(leSigne, { position: 'absolute' })
      j3pAffiche(leSigne, '', '$' + signeTabCorr[i - 1] + '$')
      leSigne.style.left = largeurSignede + (i - 1 / 2) * (L - largeurSignede) / (nbValx + 1) - leSigne.getBoundingClientRect().width / 2 + 'px'
      leSigne.style.top = 3 * h / 4 - leSigne.getBoundingClientRect().height / 2 + 'px'
    }
  } else {
    // le signe de la fonction est à compléter
    // emplacement pour le premier signe
    for (i = 1; i <= nbValx + 1; i++) {
      const leSigne = j3pAddElt(nomdiv, 'div', '', { style: objStyleCouleur.style })
      j3pStyle(leSigne, { position: 'absolute' })
      const elt = j3pAffiche(leSigne, '', '&1&', { inputmq1: { texte: '' } })
      mqRestriction(elt.inputmqList[0], '+-')
      leSigne.style.left = (largeurSignede + (2 * i - 1) * (L - largeurSignede) / (2 * (nbValx + 1)) - leSigne.getBoundingClientRect().width / 2) + 'px'
      leSigne.style.top = (3 * h / 4 - leSigne.getBoundingClientRect().height / 2) + 'px'
      zoneInput.signeFacts.push(elt.inputmqList[0])
      elt.inputmqList[0].num = i
      elt.inputmqList[0].leSpan = elt.parent.parentNode
    }
    // ajustement de la position de chaque signe
    for (i = 1; i <= nbValx + 1; i++) {
      zoneInput.signeFacts[i - 1].addEventListener('input', function (e) {
        e = e || window.e
        const key = e.which ? e.which : e.keyCode
        const signeInit = $(this).mathquill('latex')
        if (['+', '-'].includes(String.fromCharCode(key))) {
          $(this).mathquill('latex', String.fromCharCode(key).substring(1))
        } else {
          // on cherche le signe s’il en existe un
          const leSigneTab = signeInit.match(/[+-]/)
          if (leSigneTab) $(this).mathquill('latex', leSigneTab[0])
        }
        this.leSpan.style.left = (largeurSignede + (2 * this.num - 1) * (L - largeurSignede) / (2 * nbValx + 2) - this.getBoundingClientRect().width / 2) + 'px'
        this.leSpan.style.top = (3 * h / 4 - this.getBoundingClientRect().height / 2) + 'px'
      })
    }
    // liste déroulante
    for (i = 1; i <= nbValx; i++) {
      const divListe = j3pAddElt(nomdiv, 'div', '', { style: objStyleCouleur.style })
      j3pStyle(divListe, { position: 'absolute' })
      const tabTextListe = ['|', '||', '0']
      const elt = j3pAffiche(divListe, '', '#1#', { className: 'liste1', liste1: { texte: tabTextListe } })
      divListe.style.left = (largeurSignede + i * (L - largeurSignede) / (nbValx + 1) - 8) + 'px'
      divListe.style.top = (3 * h / 4 - divListe.getBoundingClientRect().height / 2) + 'px'
      listeInput.push(elt.selectList[0])
    }
  }
  // affichage d’une palette pour compléter les zones de la première ligne
  let listeBoutons, zonePalette
  if (!zeroTab[0]) {
    // ceci ne se fait que si la première ligne n’est pas complétée
    listeBoutons = ['fraction', 'inf', 'racine', 'puissance']
    if (paletteComplete) listeBoutons.push('exp', 'ln')
    zonePalette = j3pAddElt(obj, 'div')
    // palette MQ :
    j3pPaletteMathquill(zonePalette, zoneInput.ligneX[0], {
      liste: listeBoutons,
      style: { paddingTop: '10px' }
    })
    for (i = 0; i <= nbValx + 1; i++) {
      $(zoneInput.ligneX[i]).focusin(function () {
        ecoute(this.num)
      })
    }
    for (i = 1; i <= (nbValx + 1) * (tabZeroOrdonne.length + 1); i++) {
      $(zoneInput.signeFacts[i]).focusin(function () {
        j3pEmpty(zonePalette)
      })
    }
    for (i = 1; i <= (nbValx) * (tabZeroOrdonne.length + 1); i++) {
      $(listeInput[i]).focusin(function () {
        j3pEmpty(zonePalette)
      })
    }
  }

  function ecoute (num) {
    if (zoneInput.ligneX[num].className.includes('mq-editable-field')) {
      j3pEmpty(zonePalette)
      j3pPaletteMathquill(zonePalette, zoneInput.ligneX[num], {
        liste: listeBoutons,
        style: { paddingTop: '10px' }
      })
    }
  }

  if (zeroTab[0]) {
    if (!zeroTab[1]) j3pFocus(zoneInput.signeFacts[0])
  } else {
    j3pFocus(zoneInput.ligneX[0])
  }
  return { input: zoneInput, liste: listeInput, palette: zonePalette }
} // fin fctsEtudeTableauSignesFct

/**
 * Crée le tableau de signes d’un produit ou d’un quotient dans obj
 * @param obj
 * @param expresTab tableau contenant tous les facteurs
 * @param {number[]} tabDimension [largeur, hauteur, nombre de valeurs à ajouter sur l’axe des abscisses (en plus des bornes), opt], opt peut être un tableau de deux éléments : les bornes du domaine de def de la fonction (écrire infini pour \\infty)'
 * @param prodQuot ["produit"] ou ["quotient", [true, false, true, …]] (true signifie que le facteur associé de expresTab est au dénominateur)
 * @param {boolean[]} zeroTab le premier vaut true lorsque "la ligne des x" est entièrement complétée, le 2e vaut true lorsque le 1er est false et que les signes du tableau ne sont pas à donner par l’élève
 * @param valZeroTab tableau des valeurs en lesquelles s’annule au moins une fonction
 * @param {boolean} [lesSignes] paramètres optionnel si on souhaite avoir des zones de saisies pour les signes (on peut aussi mettre false)
 * @param objStyleCouleur peut contenir 4 éléments (les 2 derniers étant obligatoires) : un style de texte, couleur si on ne définit par le style, texte (qui correspondra à "signe du produit" ou "signe d’un quotient") et enfin texte2 qui sera "signe de " (pour que les textes restante dans la section)
 * @returns {{input: {ligneX: string[], signeFacts: string[], signeProd: string[]}, liste: string[], palette: HTMLElement}}
 */
export function fctsEtudeTabSignesProduit (obj, expresTab, tabDimension, prodQuot, zeroTab, valZeroTab, lesSignes, objStyleCouleur) {
  const signeProdTxt = objStyleCouleur.texte
  const macouleurTxt = (objStyleCouleur.couleur) ? objStyleCouleur.couleur : objStyleCouleur.style.color
  // liste des boutons pour la première ligne du tableau quand on a des zones de saisie :
  let listeBoutons
  if (!zeroTab[0]) {
    // ceci ne se fait que si la première ligne n’est pas complétée
    listeBoutons = ['fraction', 'inf']
    for (let i = 0; i < expresTab.length; i++) {
      expresTab[i] = String(expresTab[i])
      if (expresTab[i].includes('^')) {
        if (listeBoutons.indexOf('racine') === -1) {
          listeBoutons.push('racine', 'puissance')
        }
      }
      if (expresTab[i].includes('exp') || expresTab[i].includes('ln') || expresTab[i].includes('e^') || expresTab[i].includes('mathrm{e}')) {
        if (!listeBoutons.includes('exp')) {
          listeBoutons.push('exp', 'ln')
        }
      }
    }
  }
  let posTxt, divMoinsInf, divPlusInf, posTxtborneInf, posTxtborneInf2, posTxtborneSup, posTxtborneSup2, numLigne,
    numColonne, nbSigne, k, tabTextListe
  // objets qui vont servir à stocker les zones de saisie et les listes à compléter
  const zoneInput = { ligneX: [], signeFacts: [], signeProd: [] }
  const listeInput = []
  const macouleur = '#000000'
  const signedeTabtxt = []
  for (let i = 0; i < expresTab.length; i++) signedeTabtxt[i] = objStyleCouleur.texte2 + '$' + expresTab[i] + '$'
  const L = tabDimension[0] // Largeur du tableau
  const h = tabDimension[1] // hauteur du tableau
  const nomdiv = obj
  const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
  svg.setAttribute('width', L)
  svg.setAttribute('height', h)
  nomdiv.appendChild(svg)
  j3pCreeRectangle(svg, { x: 1, y: 0, width: L - 2, height: h, couleur: macouleur, epaisseur: 1 })
  for (let i = 1; i < expresTab.length + 2; i++) {
    j3pCreeSegment(svg, {
      x1: 0,
      y1: i * h / (expresTab.length + 2),
      x2: L,
      y2: i * h / (expresTab.length + 2),
      couleur: macouleur,
      epaisseur: 1
    })
  }
  // Affichage des textes "signe de ax+b"
  const tabDivSigneDe = []
  for (let i = 1; i <= expresTab.length; i++) {
    const divSigneDe = j3pAddElt(nomdiv, 'div', '', { style: objStyleCouleur.style })
    j3pStyle(divSigneDe, { position: 'absolute' })
    j3pAffiche(divSigneDe, '', signedeTabtxt[i - 1], { style: { color: macouleurTxt } })
    tabDivSigneDe.push(divSigneDe)
  }
  // texte "signe du produit" ou "signe du quotient" (ce texte étant renseigné à l’appel de la fonction
  const signeDeProduit = j3pAddElt(nomdiv, 'div', '', { style: objStyleCouleur.style })
  j3pStyle(signeDeProduit, { position: 'absolute' })
  j3pAffiche(signeDeProduit, '', signeProdTxt, { style: { color: macouleurTxt } })
  tabDivSigneDe.push(signeDeProduit)
  // pour placer le segment vertical, je cherche la largeur de la phrase "signe de ..."
  let nbPuiss = signedeTabtxt[0].split('^').length - 1
  let largDecalage = nbPuiss * 6
  let largeurSignede = tabDivSigneDe[0].getBoundingClientRect().width + 8
  for (let i = 2; i <= expresTab.length; i++) {
    // j’ai vu qu’avec des puissances, la largeur est mal calculée. Il faut que je décale un peu vers la droite
    nbPuiss = signedeTabtxt[i - 2].split('^').length - 1
    largDecalage = nbPuiss * 6
    largeurSignede = Math.max(largeurSignede, tabDivSigneDe[i - 1].getBoundingClientRect().width + 8 + largDecalage)
  }
  largeurSignede = Math.max(largeurSignede, tabDivSigneDe[tabDivSigneDe.length - 1].getBoundingClientRect().width + 4)
  j3pCreeSegment(svg, {
    x1: largeurSignede,
    y1: 0,
    x2: largeurSignede,
    y2: h,
    couleur: macouleur,
    epaisseur: 1
  })
  for (let i = 1; i <= expresTab.length; i++) {
    posTxt = (i + 0.5) * h / (expresTab.length + 2) - tabDivSigneDe[i - 1].getBoundingClientRect().height / 2
    tabDivSigneDe[i - 1].style.top = posTxt + 'px'
    tabDivSigneDe[i - 1].style.left = '2px'
  }
  const hauteurSigneProduit = tabDivSigneDe[tabDivSigneDe.length - 1].getBoundingClientRect().height
  posTxt = (expresTab.length + 1 + 0.5) * h / (expresTab.length + 2) - hauteurSigneProduit / 2
  tabDivSigneDe[tabDivSigneDe.length - 1].style.top = posTxt + 'px'
  tabDivSigneDe[tabDivSigneDe.length - 1].style.left = '2px'
  // placement du "x"
  const leX = j3pAddElt(nomdiv, 'div', '', { style: objStyleCouleur.style })
  j3pStyle(leX, { position: 'absolute' })
  j3pAffiche(leX, '', '$x$', { style: { color: macouleurTxt } })
  const posTxt2 = h / (2 * expresTab.length + 4) - leX.getBoundingClientRect().height / 2
  leX.style.top = posTxt2 + 'px'
  const posTxt3 = largeurSignede / 2 - leX.getBoundingClientRect().width / 2
  leX.style.left = posTxt3 + 'px'
  // moins l’infini (ou autre chose) dans le cas où la "ligne des x" est à compléter
  if (zeroTab[0]) {
    // ce div n’est créé que dans ce cas, pour gérer la tabulation dans le cas d’une zone de saisie
    divMoinsInf = j3pAddElt(nomdiv, 'div', '', { style: objStyleCouleur.style })
    j3pStyle(divMoinsInf, { position: 'absolute' })
  }
  const arbreExpressFacteur = []
  if (!tabDimension[3]) {
    if (zeroTab[0]) j3pAffiche(divMoinsInf, '', '$-\\infty$', { style: { color: macouleurTxt } })
  } else {
    let textBorneInf = String(tabDimension[3][0])
    if (textBorneInf.includes('infini')) {
      textBorneInf = textBorneInf.replace('infini', '\\infty')
    } else {
      const posFrac = textBorneInf.indexOf('/')
      if (posFrac > -1) {
        // on a une fraction
        textBorneInf = '\\frac{' + textBorneInf.substring(0, posFrac) + '}{' + textBorneInf.substring(posFrac + 1, textBorneInf.length) + '}'
      }
    }
    if (zeroTab[0]) j3pAffiche(divMoinsInf, '', '$' + textBorneInf + '$', { style: { color: macouleurTxt } })
    // on peut aussi avoir à ajouter une double barre ou un zéro au niveau de cette borne
    let borneInfInterdite = false// permettra de mettre une double barre au produit/quotient au niveau de la borne inf
    const newExpressionFct = []

    for (let i = 0; i < expresTab.length; i++) {
      newExpressionFct[i] = fctsEtudeEcriturePourCalcul(expresTab[i])
      arbreExpressFacteur[i] = new Tarbre(newExpressionFct[i], ['x'])
    }
    if (!textBorneInf.includes('infty')) {
      // la borne inf n’est pas -infini'
      let imageInfProduitQuotNulle = false
      for (let i = 0; i < expresTab.length; i++) {
        const imageBorneInf = arbreExpressFacteur[i].evalue([fctsEtudeCalculValeur(tabDimension[3][0])])
        if (isAlmostZero(imageBorneInf) && !String(expresTab[i]).includes('mathrm{e}')) {
          // on met un zéro au niveau du facteur
          // J’ai exclu le cas où j’ai une exponentielle qui peut donner une image quasiment nulle
          const imageBorneInf = j3pAddElt(nomdiv, 'div', '', { style: objStyleCouleur.style })
          j3pStyle(imageBorneInf, { position: 'absolute' })
          j3pAffiche(imageBorneInf, '', '0', { style: { color: macouleurTxt } })
          posTxtborneInf = (i + 1.5) * h / (expresTab.length + 2) - imageBorneInf.getBoundingClientRect().height / 2
          imageBorneInf.style.top = posTxtborneInf + 'px'
          posTxtborneInf2 = largeurSignede
          imageBorneInf.style.left = posTxtborneInf2 + 'px'
          if ((prodQuot[0] === 'quotient') && (prodQuot[1][i] === 'den')) {
            // on a un quotient et ce facteur est au dénominateur
            borneInfInterdite = true
            // au niveau de la dernière ligne, je dois mettre une double barre (à gauche)
          } else {
            imageInfProduitQuotNulle = true
          }
        } else if ((imageBorneInf === '-Infinity') || (imageBorneInf === '+Infinity')) {
          borneInfInterdite = true
          // on met une double barre au niveau du facteur
          j3pCreeSegment(svg, {
            x1: largeurSignede + 5,
            y1: (i + 1) * h / (expresTab.length + 2),
            x2: largeurSignede + 5,
            y2: (i + 2) * h / (expresTab.length + 2),
            couleur: macouleurTxt,
            epaisseur: 1
          })
        }
      }
      if (borneInfInterdite) {
        j3pCreeSegment(svg, {
          x1: largeurSignede + 5,
          y1: (expresTab.length + 1) * h / (expresTab.length + 2),
          x2: largeurSignede + 5,
          y2: (expresTab.length + 2) * h / (expresTab.length + 2),
          couleur: macouleurTxt,
          epaisseur: 1
        })
      } else if (imageInfProduitQuotNulle) {
        const imageBorneInfProd = j3pAddElt(nomdiv, 'div', '', { style: objStyleCouleur.style })
        j3pStyle(imageBorneInfProd, { position: 'absolute' })
        j3pAffiche(imageBorneInfProd, '', '0', { style: { color: macouleurTxt } })
        posTxtborneInf = (expresTab.length + 1.5) * h / (expresTab.length + 2) - imageBorneInfProd.getBoundingClientRect().height / 2
        imageBorneInfProd.style.top = posTxtborneInf + 'px'
        posTxtborneInf2 = largeurSignede
        imageBorneInfProd.style.left = posTxtborneInf2 + 'px'
      }
    }
  }
  if (zeroTab[0]) {
    // ce div n’est créé que dans ce cas, pour gérer la tabulation dans le cas d’une zone de saisie
    // plus l’infini (ou autre chose) dans le cas où la "ligne des x" est à compléter
    divPlusInf = j3pAddElt(nomdiv, 'div', '', { style: objStyleCouleur.style })
    j3pStyle(divPlusInf, { position: 'absolute' })
  }
  if (!tabDimension[3]) {
    if (zeroTab[0]) j3pAffiche(divPlusInf, '', '$+\\infty$', { style: { color: macouleurTxt } })
  } else {
    let textBorneSup = String(tabDimension[3][1])
    if (textBorneSup.includes('infini')) {
      textBorneSup = textBorneSup.replace('infini', '\\infty')
    } else {
      const posFrac2 = textBorneSup.indexOf('/')
      if (posFrac2 > -1) {
        // on a une fraction
        textBorneSup = '\\frac{' + textBorneSup.substring(0, posFrac2) + '}{' + textBorneSup.substring(posFrac2 + 1, textBorneSup.length) + '}'
      }
    }
    if (zeroTab[0]) j3pAffiche(divPlusInf, '', '$' + textBorneSup + '$', { style: { color: macouleurTxt } })
    // on peut aussi avoir à ajouter une double barre ou un zéro au niveau de cette borne
    let borneSupInterdite = false// permettra de mettre une double barre au produit/quotient au niveau de la borne sup
    if (!textBorneSup.includes('infty')) {
      // la borne sup n’est pas +infini'
      let imageSupProduitQuotNulle = false
      for (let i = 0; i < expresTab.length; i++) {
        const imageBorneSup = arbreExpressFacteur[i].evalue([fctsEtudeCalculValeur(tabDimension[3][1])])
        if (isAlmostZero(imageBorneSup) && (!String(expresTab[i]).includes('mathrm{e}'))) {
          // on met un zéro au niveau du facteur
          // J’ai exclu le cas où j’ai une exponentielle qui peut donner une image quasiment nulle
          const imageBorneSup = j3pAddElt(nomdiv, 'div', '', { style: objStyleCouleur.style })
          j3pStyle(imageBorneSup, { position: 'absolute' })
          j3pAffiche(imageBorneSup, '', '0', { style: { color: macouleurTxt } })
          posTxtborneSup = (i + 1.5) * h / (expresTab.length + 2) - imageBorneSup.getBoundingClientRect().height / 2
          imageBorneSup.style.top = posTxtborneSup + 'px'
          posTxtborneSup2 = L - imageBorneSup.getBoundingClientRect().width
          imageBorneSup.style.left = posTxtborneSup2 + 'px'
          if ((prodQuot[0] === 'quotient') && (prodQuot[1][i] === 'den')) {
            // on a un quotient et ce facteur est au dénominateur
            borneSupInterdite = true
          } else {
            imageSupProduitQuotNulle = true
          }
        } else if ((imageBorneSup === '-Infinity') || (imageBorneSup === '+Infinity')) {
          borneSupInterdite = true
          // on met une double barre au niveau du facteur
          j3pCreeSegment(svg, {
            x1: L - 5,
            y1: (i + 1) * h / (expresTab.length + 2),
            x2: L - 5,
            y2: (i + 2) * h / (expresTab.length + 2),
            couleur: macouleurTxt,
            epaisseur: 1
          })
        }
      }
      if (borneSupInterdite) {
        j3pCreeSegment(svg, {
          x1: L - 5,
          y1: (expresTab.length + 1) * h / (expresTab.length + 2),
          x2: L - 5,
          y2: (expresTab.length + 2) * h / (expresTab.length + 2),
          couleur: macouleurTxt,
          epaisseur: 1
        })
      } else if (imageSupProduitQuotNulle) {
        const imageBorneSupProd = j3pAddElt(nomdiv, 'div', '', { style: objStyleCouleur.style })
        j3pStyle(imageBorneSupProd, { position: 'absolute' })
        j3pAffiche(imageBorneSupProd, '', '0', { style: { color: macouleurTxt } })
        posTxtborneSup = (expresTab.length + 1.5) * h / (expresTab.length + 2) - imageBorneSupProd.getBoundingClientRect().height / 2
        imageBorneSupProd.style.top = posTxtborneSup + 'px'
        posTxtborneSup2 = L - imageBorneSupProd.getBoundingClientRect().width
        imageBorneSupProd.style.left = posTxtborneSup2 + 'px'
      }
    }
  }
  if (zeroTab[0]) {
    divMoinsInf.style.left = (largeurSignede + 1) + 'px'
    divMoinsInf.style.top = (h / (2 * expresTab.length + 4) - divMoinsInf.getBoundingClientRect().height / 2) + 'px'
    divPlusInf.style.left = (L - 1 - divPlusInf.getBoundingClientRect().width - 2 + 1) + 'px'
    divPlusInf.style.top = (h / (2 * expresTab.length + 4) - divPlusInf.getBoundingClientRect().height / 2) + 'px'
  }
  // fctsEtudeOrdonneTabSansDoublon fonction avec des array où chaque élément est un number ou une string
  // Si c’est une string, cela peut être plusieurs valeurs qui seront alors séparées par |
  // Or ici valZeroTab est un array d’arrays donc ça coïnce
  // Ces array doivent être transformées en string
  const valZeroPourOrdonne = valZeroTab.map(elt => elt.join('|'))
  const tabZeroOrdonne = fctsEtudeOrdonneTabSansDoublon(valZeroPourOrdonne, tabDimension[3])
  let nbValx = tabDimension[2]
  if (zeroTab[0]) {
    // les endroits où s’annulent la fonction sont donnés
    // attention les_zeros doivent être du type string (un entier ou un décimal ou un nb fractionnaire de la forme a/b
    // dans un premier temps, on ordonne les nombre du tableau valZeroTab (qui sont ls endroits où s’annulent notre produit)
    // Dans le cas où sont présents la fonction exp, la fonction ln ou la fonction racine, il va falloir gérer l’affichage'
    nbValx = tabZeroOrdonne.length
    for (let i = 0; i < nbValx; i++) {
      const leZero = (['string', 'number'].includes(typeof tabZeroOrdonne[i])) ? tabZeroOrdonne[i].replace(/\./g, ',') : tabZeroOrdonne[i][0].replace(/\./g, ',')
      const monZeroTxt = fctsEtudeEcrireNbLatex(leZero)
      const laZone = j3pAddElt(nomdiv, 'div', '', { style: objStyleCouleur.style })
      j3pStyle(laZone, { position: 'absolute' })
      j3pAffiche(laZone, '', '$' + monZeroTxt + '$', { style: { color: macouleurTxt } })
      laZone.style.left = (largeurSignede + (i + 1) * (L - largeurSignede) / (nbValx + 1) - laZone.getBoundingClientRect().width / 2) + 'px'
      laZone.style.top = (h / (2 * expresTab.length + 4) - laZone.getBoundingClientRect().height / 2) + 'px'
    }
  } else {
    // dans le cas où les valeur en lesquelles s’annule le produit sont à donner
    // bien sûr dans ce cas, il y a autant de valeurs que de fonctions seulement si on n’a que des fonctions affines'
    for (let i = 0; i <= nbValx + 1; i++) {
      // i==0 pour la borne inf du domaine et i==valX+1 pour la borneSup du domaine
      const laZone = j3pAddElt(nomdiv, 'div', '', { style: objStyleCouleur.style })
      j3pStyle(laZone, { position: 'absolute' })
      const elt = j3pAffiche(laZone, '', '&1&', { inputmq1: { texte: '' } })
      mqRestriction(elt.inputmqList[0], '\\d.,/+-', { commandes: listeBoutons })
      zoneInput.ligneX.push(elt.inputmqList[0])
      if (i === 0) {
        // placement de la borne inf
        laZone.style.left = (largeurSignede + 2) + 'px'
      } else if (i === (nbValx + 1)) {
        // placement de la borne sup
        laZone.style.left = (L - 5 - laZone.getBoundingClientRect().width) + 'px'
      } else {
        laZone.style.left = (largeurSignede + i * (L - largeurSignede) / (nbValx + 1) - laZone.getBoundingClientRect().width / 2) + 'px'
      }
      laZone.style.top = (h / (2 * expresTab.length + 4) - laZone.getBoundingClientRect().height / 2) + 'px'
      elt.inputmqList[0].num = i
      elt.inputmqList[0].leSpan = laZone
    }
    for (let i = 0; i <= nbValx + 1; i++) {
      zoneInput.ligneX[i].addEventListener('input', function () {
        if (this.num === 0) {
          this.leSpan.style.left = (largeurSignede + 2) + 'px'
        } else if (this.num === nbValx + 1) {
          this.leSpan.style.left = (L - 10 - this.getBoundingClientRect().width) + 'px'
        } else {
          this.leSpan.style.left = (largeurSignede + this.num * (L - largeurSignede) / (nbValx + 1) - this.getBoundingClientRect().width / 2) + 'px'
        }
        this.leSpan.style.top = (h / (2 * expresTab.length + 4) - this.getBoundingClientRect().height / 2) + 'px'
      })
    }
    j3pFocus(zoneInput.ligneX[0])
  }
  // on crée tout d’abord les segments verticaux
  const posValZero = []
  for (let i = 1; i <= nbValx; i++) {
    posValZero[i] = largeurSignede + i * (L - largeurSignede) / (nbValx + 1)
    j3pCreeSegment(svg, {
      x1: posValZero[i],
      y1: h / (expresTab.length + 2),
      x2: posValZero[i],
      y2: h,
      couleur: macouleur,
      epaisseur: 1
    })
  }
  if (!zeroTab[1]) {
    // Dans ce cas, les signes et zéros sont à compléter
    // création de toutes les zones pour les signes
    nbSigne = (expresTab.length + 1) * (nbValx + 1)
    for (let i = 1; i <= nbSigne; i++) {
      const leDiv = j3pAddElt(nomdiv, 'div', '', { style: objStyleCouleur.style })
      j3pStyle(leDiv, { position: 'absolute' })
      const elt = j3pAffiche(leDiv, '', '&1&', { inputmq1: { texte: '' } })
      mqRestriction(elt.inputmqList[0], '+-')
      // positionnement initial de toutes les zones
      numLigne = Math.ceil(i / (nbValx + 1))
      numColonne = (i - 1) % (nbValx + 1) + 1
      leDiv.style.left = (largeurSignede + (2 * numColonne - 1) * (L - largeurSignede) / (2 * nbValx + 2) - leDiv.getBoundingClientRect().width / 2) + 'px'
      leDiv.style.top = (numLigne * h / (expresTab.length + 2) + h / (2 * expresTab.length + 4) - leDiv.getBoundingClientRect().height / 2) + 'px'
      elt.inputmqList[0].num = i
      elt.inputmqList[0].leSpan = leDiv
      zoneInput.signeFacts.push(elt.inputmqList[0])
    }
    // ajustement de la position de chaque signe
    for (let i = 1; i <= nbSigne; i++) {
      zoneInput.signeFacts[i - 1].addEventListener('input', function () {
        const numLigne = Math.ceil(this.num / (nbValx + 1))
        const numColonne = (this.num - 1) % (nbValx + 1) + 1
        this.leSpan.style.left = (largeurSignede + (2 * numColonne - 1) * (L - largeurSignede) / (2 * nbValx + 2) - this.getBoundingClientRect().width / 2) + 'px'
        this.leSpan.style.top = (numLigne * h / (expresTab.length + 2) + h / (2 * expresTab.length + 4) - this.getBoundingClientRect().height / 2) + 'px'
      })
    }
    // listes qui ne sont pas celles de la dernières ligne
    const nbAutresListes = nbValx * expresTab.length
    for (let i = 1; i <= nbAutresListes; i++) {
      numLigne = Math.ceil(i / (nbValx))
      numColonne = (i - 1) % (nbValx) + 1
      const leDiv = j3pAddElt(nomdiv, 'div', '', { style: objStyleCouleur.style })
      j3pStyle(leDiv, { position: 'absolute' })
      const elt = j3pAffiche(leDiv, '', '#1#', { className: 'liste1', liste1: { texte: ['|', '0'] } })
      leDiv.style.left = (largeurSignede + numColonne * (L - largeurSignede) / (nbValx + 1) - 8) + 'px'
      leDiv.style.top = ((2 * numLigne + 1) * h / (2 * expresTab.length + 4) - leDiv.getBoundingClientRect().height / 2) + 'px'
      listeInput.push(elt.selectList[0])
    }
    // listes de la dernière ligne
    for (let i = 1; i <= nbValx; i++) {
      const leDiv = j3pAddElt(nomdiv, 'div', '', { style: objStyleCouleur.style })
      j3pStyle(leDiv, { position: 'absolute' })
      tabTextListe = ['|', '||', '0']
      const elt = j3pAffiche(leDiv, '', '#1#', { className: 'liste1', liste1: { texte: tabTextListe } })
      leDiv.style.left = (largeurSignede + i * (L - largeurSignede) / (nbValx + 1) - 8) + 'px'
      leDiv.style.top = (h - h / (2 * expresTab.length + 4) - leDiv.getBoundingClientRect().height / 2) + 'px'
      listeInput.push(elt.selectList[0])
    }
    if (zeroTab[0]) {
      j3pFocus(zoneInput.signeFacts[0])
    } else {
      j3pFocus(zoneInput.ligneX[0])
    }
  } else {
    // zeroTab[0] vaut nécessairement false
    // On est dans le cas où tout le tableau va être affiché (signes et zéros), sauf peut-être le signe du produit/quotient
    // on s’occupe dans un premier temps du signe de chaque facteur (pas celui du produit)
    // je place tous les zeros
    const doubleBarre = []
    for (let i = 1; i <= expresTab.length; i++) {
      for (k = 0; k < tabZeroOrdonne.length; k++) {
        // il faut s’assurer si tabZeroOrdonne[k]annule ou non le facteur'
        if (Math.abs(arbreExpressFacteur[i - 1].evalue([fctsEtudeCalculValeur(tabZeroOrdonne[k])])) < Math.pow(10, -10)) {
          const zeroFacteur = j3pAddElt(nomdiv, 'div', '', { style: objStyleCouleur.style })
          j3pStyle(zeroFacteur, { position: 'absolute' })
          j3pAffiche(zeroFacteur, '', '0', { style: { color: macouleurTxt } })
          zeroFacteur.style.left = largeurSignede + (k + 1) * (L - largeurSignede) / (tabZeroOrdonne.length + 1) - zeroFacteur.getBoundingClientRect().width / 2 + 'px'
          zeroFacteur.style.top = (i + 0.5) * h / (expresTab.length + 2) - zeroFacteur.getBoundingClientRect().height / 2 + 'px'
          if (prodQuot[0] === 'quotient') doubleBarre[k] = (prodQuot[1][i - 1] !== 'num')
        }
      }
    }
    nbSigne = (nbValx + 1) * expresTab.length
    // le tableau suivant va servir pour le signe du produit ou du quotient
    const nbSignesMoinsTab = []
    for (let i = 1; i <= nbValx + 1; i++) nbSignesMoinsTab[i] = 0
    for (let i = 1; i <= nbSigne; i++) {
      const signeFact = j3pAddElt(nomdiv, 'div', '', { style: objStyleCouleur.style })
      j3pStyle(signeFact, { position: 'absolute' })
      // je cherche l’endroit où s’annule mon expression (si elle s’annule)
      numLigne = Math.ceil(i / (nbValx + 1))
      numColonne = (i - 1) % (nbValx + 1) + 1
      let aucuneRacineDansDomaine
      if ((valZeroPourOrdonne[numLigne - 1]) && (valZeroPourOrdonne[numLigne - 1] !== '')) {
        // deux possibilités : soit valZeroPourOrdonne[numLigne-1] ne possède qu’une valeur, soit plusieurs'
        if (!String(valZeroPourOrdonne[numLigne - 1]).includes('|')) {
          // une seule valeur
          aucuneRacineDansDomaine = (!tabZeroOrdonne.join().includes(valZeroPourOrdonne[numLigne - 1]))
          if (aucuneRacineDansDomaine) {
            // cela doit engendrer une modification du tableau lesSignes[numLigne-1] qui ne devra plus contenir qu’un seul élément et non plus 2
            if ((tabDimension[3] !== undefined) && (tabDimension[3][0] !== '-infini')) {
              // si le zéro est inférieur à tabDimension[3][0], alors on vire le premier élément de lesSignes[numLignes-1]
              if (fctsEtudeCalculValeur(valZeroPourOrdonne[numLigne - 1]) < fctsEtudeCalculValeur(tabDimension[3][0])) {
                // sauf qu’il ne faut pas le virer si ce zéro est seul
                if (lesSignes[numLigne - 1].length > 1) {
                  lesSignes[numLigne - 1].splice(0, 1)
                }
              }
            }
            if ((tabDimension[3] !== undefined) && (tabDimension[3][1] !== '+infini')) {
              // si le zéro est supérieur à tabDimension[3][1], alors on vire le dernier élément de lesSignes[numLignes-1]
              if (fctsEtudeCalculValeur(valZeroPourOrdonne[numLigne - 1]) > fctsEtudeCalculValeur(tabDimension[3][1])) {
                // sauf qu’il ne faut pas le virer si ce zéro est seul
                if (lesSignes[numLigne - 1].length > 1) {
                  lesSignes[numLigne - 1].splice(lesSignes[numLigne - 1].length - 1, 1)
                }
              }
            }
          }
        } else {
          const tabValeursZero = String(valZeroPourOrdonne[numLigne - 1]).split('|')
          const tabValeursZeroOrdonne = fctsEtudeOrdonneTabSansDoublon(tabValeursZero)
          // c’est le tableau précédent que je viens d’ordonner.
          aucuneRacineDansDomaine = true
          for (k = 0; k < tabValeursZero.length; k++) {
            aucuneRacineDansDomaine = (aucuneRacineDansDomaine && (!tabZeroOrdonne.includes(tabValeursZero[k])))
          }
          for (k = 0; k < tabValeursZero.length; k++) {
            if (!tabZeroOrdonne.join().includes(tabValeursZeroOrdonne[k])) {
              // la valeur du tableau n’est finalement pas dans le tableau (et donc pas dans le domaine)
              // ça m’oblige à ne pas prendre en compte un éléments de lesSignes
              if ((tabDimension[3] !== undefined) && (tabDimension[3][0] !== '-infini')) {
                // si le zéro est inférieur à tabDimension[3][0], alors on vire le premier élément de lesSignes[numLignes-1]
                if (fctsEtudeCalculValeur(tabValeursZeroOrdonne[k]) < fctsEtudeCalculValeur(tabDimension[3][0])) {
                  lesSignes[numLigne - 1].splice(0, 1)
                }
              }
              if ((tabDimension[3] !== undefined) && (tabDimension[3][1] !== '+infini')) {
                // si le zéro est supérieur à tabDimension[3][1], alors on vire le dernier élément de lesSignes[numLignes-1]
                if (fctsEtudeCalculValeur(tabValeursZeroOrdonne[k]) > fctsEtudeCalculValeur(tabDimension[3][1])) {
                  lesSignes[numLigne - 1].splice(lesSignes[numLigne - 1].length - 1, 1)
                }
              }
            }
          }
        }
      } else {
        aucuneRacineDansDomaine = true
      }
      if (aucuneRacineDansDomaine) {
        // on a le même signe sur toute la ligne
        j3pAffiche(signeFact, '', '$' + lesSignes[numLigne - 1][0] + '$', { style: { color: macouleurTxt } })
        if (lesSignes[numLigne - 1][0] === '-') nbSignesMoinsTab[numColonne] += 1
      } else {
        if (!tabZeroOrdonne[numColonne - 1]) {
          // pour le signe sur la dernière colonne
          j3pAffiche(signeFact, '', '$' + lesSignes[numLigne - 1][lesSignes[numLigne - 1].length - 1] + '$', { style: { color: macouleurTxt } })
          if (lesSignes[numLigne - 1][lesSignes[numLigne - 1].length - 1] === '-') nbSignesMoinsTab[numColonne] += 1
        } else {
          if (!String(valZeroPourOrdonne[numLigne - 1]).includes('|')) {
            // la fonction de cette ligne ne s’annule pas plus d’une fois'
            if (fctsEtudeCalculValeur(valZeroPourOrdonne[numLigne - 1]) >= fctsEtudeCalculValeur(tabZeroOrdonne[numColonne - 1])) {
              j3pAffiche(signeFact, '', '$' + lesSignes[numLigne - 1][0] + '$', { style: { color: macouleurTxt } })
              if (lesSignes[numLigne - 1][0] === '-') nbSignesMoinsTab[numColonne] += 1
            } else {
              j3pAffiche(signeFact, '', '$' + lesSignes[numLigne - 1][1] + '$', { style: { color: macouleurTxt } })
              if (lesSignes[numLigne - 1][1] === '-') nbSignesMoinsTab[numColonne] += 1
            }
          } else {
            let tableauValZeroNumligne = String(valZeroPourOrdonne[numLigne - 1]).split('|')
            tableauValZeroNumligne = fctsEtudeOrdonneTabSansDoublon(tableauValZeroNumligne, tabDimension[3])
            k = 0
            while (fctsEtudeCalculValeur(tabZeroOrdonne[numColonne - 1]) > fctsEtudeCalculValeur(tableauValZeroNumligne[k])) {
              k++
            }
            j3pAffiche(signeFact, '', '$' + lesSignes[numLigne - 1][k] + '$', { style: { color: macouleurTxt } })
            if (lesSignes[numLigne - 1][k] === '-') {
              nbSignesMoinsTab[numColonne] += 1
            }
          }
        }
      }
      signeFact.style.left = (largeurSignede + (2 * numColonne - 1) * (L - largeurSignede) / (2 * nbValx + 2) - signeFact.getBoundingClientRect().width / 2) + 'px'
      signeFact.style.top = (numLigne * h / (expresTab.length + 2) + h / (2 * expresTab.length + 4) - signeFact.getBoundingClientRect().height / 2) + 'px'
    }
    if (!lesSignes[lesSignes.length - 1]) {
      // on demande de compléter des zones de saisie de la dernière ligne
      for (let i = 1; i <= nbValx + 1; i++) {
        const leDiv = j3pAddElt(nomdiv, 'div', '', { style: objStyleCouleur.style })
        j3pStyle(leDiv, { position: 'absolute' })
        const elt = j3pAffiche(leDiv, '', '&1&', { inputmq1: { texte: '' } })
        zoneInput.signeProd.push(elt.inputmqList[0])
        mqRestriction(elt.inputmqList[0], '+-')
        // positionnement initial de toutes les zones
        numColonne = (i - 1) % (nbValx + 1) + 1
        leDiv.style.left = (largeurSignede + (2 * numColonne - 1) * (L - largeurSignede) / (2 * nbValx + 2) - leDiv.getBoundingClientRect().width / 2) + 'px'
        leDiv.style.top = ((expresTab.length + 1.5) * h / (expresTab.length + 2) - leDiv.getBoundingClientRect().height / 2) + 'px'
        elt.inputmqList[0].num = i
        elt.inputmqList[0].leSpan = leDiv
      }
      // ajustement de la position de chaque signe
      for (let i = 1; i <= nbValx + 1; i++) {
        zoneInput.signeProd[i - 1].addEventListener('input', function () {
          const numColonne = (this.num - 1) % (expresTab.length + 1) + 1
          this.leSpan.style.left = (largeurSignede + (2 * numColonne - 1) * (L - largeurSignede) / (2 * nbValx + 2) - this.getBoundingClientRect().width / 2) + 'px'
        })
      }
      // listes de la dernière ligne
      for (let i = 1; i <= nbValx; i++) {
        const leDiv = j3pAddElt(nomdiv, 'div', '', { style: objStyleCouleur.style })
        j3pStyle(leDiv, { position: 'absolute' })
        tabTextListe = (prodQuot[0] === 'produit') ? ['|', '0'] : ['|', '||', '0']
        const elt = j3pAffiche(leDiv, '', '#1#', { className: 'liste1', liste1: { texte: tabTextListe, correction: '0' } })
        leDiv.style.left = (largeurSignede + i * (L - largeurSignede) / (nbValx + 1) - 8) + 'px'
        leDiv.style.top = (h - h / (2 * expresTab.length + 4) - leDiv.getBoundingClientRect().height / 2) + 'px'
        listeInput.push(elt.selectList[0])
      }
      j3pFocus(zoneInput.signeProd[0])
    } else {
      // on donne les signes et les zéros ou double barres(la réponse)
      if (prodQuot[0] === 'produit') {
        // tout d’abord les zéros'
        for (let i = 1; i <= nbValx; i++) {
          const zeroProd = j3pAddElt(nomdiv, 'div', '', { style: objStyleCouleur.style })
          j3pStyle(zeroProd, { position: 'absolute' })
          j3pAffiche(zeroProd, '', '0', { style: { color: macouleurTxt } })
          zeroProd.style.left = largeurSignede + (i) * (L - largeurSignede) / (tabZeroOrdonne.length + 1) - zeroProd.getBoundingClientRect().width / 2 + 'px'
          zeroProd.style.top = (expresTab.length + 1.5) * h / (expresTab.length + 2) - zeroProd.getBoundingClientRect().height / 2 + 'px'
        }
      } else {
        // tout d’abord les zéros ou double barres'
        for (let i = 1; i <= tabZeroOrdonne.length; i++) {
          if (doubleBarre[i - 1]) {
            // on met une double barre
            j3pCreeSegment(svg, {
              x1: posValZero[i] + 3,
              y1: (expresTab.length + 1) * h / (expresTab.length + 2),
              x2: posValZero[i] + 3,
              y2: h,
              couleur: macouleurTxt,
              epaisseur: 1
            })
            j3pCreeSegment(svg, {
              x1: posValZero[i],
              y1: (expresTab.length + 1) * h / (expresTab.length + 2),
              x2: posValZero[i],
              y2: h,
              couleur: macouleurTxt,
              epaisseur: 1
            })
          } else {
            const zeroQuotient = j3pAddElt(nomdiv, 'div', '', { style: objStyleCouleur.style })
            j3pStyle(zeroQuotient, { position: 'absolute' })
            j3pAffiche(zeroQuotient, '', '0', { style: { color: macouleurTxt } })
            zeroQuotient.style.left = largeurSignede + (i) * (L - largeurSignede) / (tabZeroOrdonne.length + 1) - zeroQuotient.getBoundingClientRect().width / 2 + 'px'
            zeroQuotient.style.top = (expresTab.length + 1.5) * h / (expresTab.length + 2) - zeroQuotient.getBoundingClientRect().height / 2 + 'px'
          }
        }
      }
      // maintenant on gère les signes
      for (let i = 1; i <= nbValx + 1; i++) {
        const signeProd = j3pAddElt(nomdiv, 'div', '', { style: objStyleCouleur.style })
        j3pStyle(signeProd, { position: 'absolute' })
        if (nbSignesMoinsTab[i] % 2 === 0) {
          j3pAffiche(signeProd, '', '$+$', { style: { color: macouleurTxt } })
        } else {
          j3pAffiche(signeProd, '', '$-$', { style: { color: macouleurTxt } })
        }
        signeProd.style.left = (largeurSignede + (2 * i - 1) * (L - largeurSignede) / (2 * nbValx + 2) - signeProd.getBoundingClientRect().width / 2) + 'px'
        signeProd.style.top = ((expresTab.length + 1.5) * h / (expresTab.length + 2) - signeProd.getBoundingClientRect().height / 2) + 'px'
      }
    }
  }
  // affichage d’une palette pour compléter les zones de la première ligne
  let zonePalette
  if (!zeroTab[0]) {
    // ceci ne se fait que si la première ligne n’est pas complétée
    zonePalette = j3pAddElt(obj, 'div')
    // palette MQ :
    j3pPaletteMathquill(zonePalette, zoneInput.ligneX[0], {
      liste: listeBoutons,
      style: { paddingTop: '10px' }
    })
    for (let i = 0; i <= nbValx + 1; i++) {
      $(zoneInput.ligneX[i]).focusin(function () {
        ecoute(this.num)
      })
    }
    for (let i = 1; i <= (nbValx + 1) * (expresTab.length + 1); i++) {
      $(zoneInput.signeFacts[i - 1]).focusin(function () {
        j3pEmpty(zonePalette)
      })
    }
    for (let i = 1; i <= (nbValx) * (expresTab.length + 1); i++) {
      $(listeInput[i - 1]).focusin(function () {
        j3pEmpty(zonePalette)
      })
    }
  }

  function ecoute (num) {
    if (zoneInput.ligneX[num].className.includes('mq-editable-field')) {
      j3pEmpty(zonePalette)
      j3pPaletteMathquill(zonePalette, zoneInput.ligneX[num], {
        liste: listeBoutons,
        style: { paddingTop: '10px' }
      })
    }
  }

  return { input: zoneInput, liste: listeInput, palette: zonePalette }
} // fin fctsEtudeTabSignesProduit

// fonction privées, mises après car une fois importé dans sesaparcours ça doit être après les exports des fcts ci-dessus

/**
 * @private
 * @param {string} fctAffine
 * @param {string} [variable=x]
 * @returns {number[]} tableau de 2 éléments : coefA et coefB de l’écriture ax+b au format latex
 */
function extraireCoefsFctaffine (fctAffine, variable) {
  const nomVar = (variable) || 'x'
  let coefA, coefB
  if ((fctAffine[0] === '(') && (fctAffine[fctAffine.length - 1] === ')')) {
    fctAffine = fctAffine.substring(1, fctAffine.length - 1)
  }
  if (fctAffine === '') {
    coefA = '0'
    coefB = '1'
  } else if (fctAffine === '-') {
    coefA = '0'
    coefB = '-1'
  } else if ((!fctAffine.substring(1).includes('+')) && (!fctAffine.substring(1).includes('-'))) {
    // c’est qu’il n’y a qu’un seul terme dans la fonction affine
    if (!fctAffine.includes(nomVar)) {
      // c’est une fonction constante
      coefA = '0'
      coefB = fctAffine
    } else {
      // c’est une fonction linéaire
      coefA = fctAffine.substring(0, fctAffine.length - nomVar.length)
      if (coefA === '') {
        coefA = '1'
      } else if (coefA === '-') {
        coefA = '-1'
      }
      coefB = '0'
    }
  } else {
    const posDecoup = Math.max(fctAffine.lastIndexOf('+'), fctAffine.lastIndexOf('-'))// cela me donne la position du signe entre b et ax
    if (posDecoup <= 0) {
      // c’est que c’est de la forme ax donc b vaut 0'
      coefA = fctAffine.substring(0, fctAffine.length - 1)
      if (coefA === '') {
        coefA = '1'
      } else if (coefA === '-') {
        coefA = '-1'
      }
      coefB = '0'
    } else {
      if (fctAffine.substring(fctAffine.length - nomVar.length) === nomVar) {
        // fctAffine est écrit sous la forme b+ax
        coefB = fctAffine.substring(0, posDecoup)
        if (fctAffine[posDecoup] === '-') {
          coefA = fctAffine.substring(posDecoup, fctAffine.length - nomVar.length)
        } else {
          coefA = fctAffine.substring(posDecoup + 1, fctAffine.length - nomVar.length)
        }
        if (coefA === '') {
          coefA = '1'
        } else if (coefA === '-') {
          coefA = '-1'
        }
      } else {
        // fctAffine est écrit sous la forme ax+b
        coefA = fctAffine.substring(0, posDecoup - nomVar.length)
        if (fctAffine[posDecoup] === '-') {
          coefB = fctAffine.substring(posDecoup)
        } else {
          coefB = fctAffine.substring(posDecoup + 1)
        }
        if (coefA === '') {
          coefA = '1'
        } else if (coefA === '-') {
          coefA = '-1'
        }
      }
    }
  }
  // si on avait a*x au lieu de ax, il faut se débarrasser du signe de multiplication
  if (coefA.charAt(coefA.length - 1) === '*') {
    coefA = coefA.substring(0, coefA.length - 1)
  }
  coefA = fractionLatex(coefA)
  coefB = fractionLatex(coefB)
  return [coefA, coefB]
} // fin extraireCoefsFctaffine

/**
 * @private
 * @param texte
 * @returns {*}
 */
function fractionLatex (texte) {
  // texte est une expression dans laquelle on peut retrouver une fraction sous la forme .../... ou (.../...)
  // cette fonction renvoie le texte au format latex
  const fractReg1 = /\([0-9+*a-z-]+\)\/\([0-9+*a-z-]+\)/ig // on cherche (...)/(...)
  const fractReg2 = /\([0-9*a-z]+\/[0-9*a-z]+\)/ig// on cherche (.../...)
  // @todo ces deux regex sont très laxistes, ça matche du (42+*55)/(x3-*2), et la 2e n’a pas le signe - autorisé

  let texteLatex = texte
  let i, num, den, tabNumDen
  if (fractReg1.test(texteLatex)) {
    // l’expression (...)/(...) est présente'
    const tabFract1 = texteLatex.match(fractReg1)
    for (i = 0; i < tabFract1.length; i++) {
      tabNumDen = tabFract1[i].split('/')
      if (tabNumDen[0][tabNumDen[0].length - 1] === ')') {
        num = tabNumDen[0].substring(1, tabNumDen[0].length - 1)
      } else {
        num = tabNumDen[0].substring(1, tabNumDen[0].length)
      }
      if (tabNumDen[1][0] === '(') {
        den = tabNumDen[1].substring(1, tabNumDen[1].length - 1)
      } else {
        den = tabNumDen[1].substring(0, tabNumDen[1].length - 1)
      }
      texteLatex = texteLatex.replace(tabFract1[i], '\\frac{' + num + '}{' + den + '}')
    }
  }
  if (fractReg2.test(texteLatex)) {
    // l’expression (...)/(...) est présente'
    const tabFract2 = texteLatex.match(fractReg2)
    for (i = 0; i < tabFract2.length; i++) {
      tabNumDen = tabFract2[i].split('/')
      num = tabNumDen[0].substring(1)
      den = tabNumDen[1].substring(0, tabNumDen[1].length - 1)
      texteLatex = texteLatex.replace(tabFract2[i], '\\frac{' + num + '}{' + den + '}')
    }
  }
  if (String(texteLatex).includes('/')) {
    // il reste encore une fraction à gérer à la main, sans doute du style .../...
    const signeReg = /[+()*-]/ig
    const tabSansSigne = texteLatex.split(signeReg)
    for (i = 0; i < tabSansSigne.length; i++) {
      if (tabSansSigne[i].includes('/')) {
        const newTab = tabSansSigne[i].split('/')
        texteLatex = texteLatex.replace(newTab[0] + '/' + newTab[1], '\\frac{' + newTab[0] + '}{' + newTab[1] + '}')
      }
    }
  }
  return texteLatex
} // fin fractionLatex

/**
 * on a un polynôme de degré 3 a1x^3+b1x^2+c1x+d1
 * le polynôme dérivée du premier est ax^2+bx+c. Le discriminant de ce trinôme est delta
 * on cherche l’image de (-b-sqrt{delta})/(2a) puis de (-b+sqrt{delta})/(2a) par le polynôme de degré 3
 * l’image de (-b-sqrt{delta})/(2a) est (-((a1b^3+3a1b*delta)/(4a^2)-(b1b^2+b1*delta)/(2a)+c1b-2d1a)+(-3b^2a1-delta*a1)/(4a^2)+(bb1)/a-c1)sqrt{delta})/(2a)
 * l’image de (-b+sqrt{delta})/(2a) est (-((a1b^3+3a1b*delta)/(4a^2)-(b1b^2+b1*delta)/(2a)+c1b-2d1a)+(3b^2a1+delta*a1)/(4a^2)-(bb1)/a+c1)sqrt{delta})/(2a)
 * j'écrirai l’image sous la forme (-bSol+/-sqrt{delta_sol})/(2a) à l’aide de la fonction simplificationRacineTrinome
 * @private
 * @param a1
 * @param b1
 * @param c1
 * @param d1
 * @param a
 * @param b
 * @param c
 * @param delta
 * @returns {string[]}
 */
function imagePolDegre3 (a1, b1, c1, d1, a, b, c, delta) {
  const quatreACarre = fctsEtudeProduitNbs('4', fctsEtudeProduitNbs(a, a))
  const a1bCubePlus3a1bdelta = fctsEtudeSommeNbs(fctsEtudeProduitNbs(a1, fctsEtudeProduitNbs(b, fctsEtudeProduitNbs(b, b))), fctsEtudeProduitNbs('3', fctsEtudeProduitNbs(a1, fctsEtudeProduitNbs(b, delta))))
  const b1bCarrePlusb1Delta = fctsEtudeSommeNbs(fctsEtudeProduitNbs(b1, fctsEtudeProduitNbs(b, b)), fctsEtudeProduitNbs(b1, delta))
  const c1bmoins2d1a = fctsEtudeSommeNbs(fctsEtudeProduitNbs(c1, b), fctsEtudeProduitNbs('-1', fctsEtudeProduitNbs('2', fctsEtudeProduitNbs(d1, a))))
  const bSol = fctsEtudeSommeNbs(fctsEtudeDivisionNbs(a1bCubePlus3a1bdelta, quatreACarre), fctsEtudeSommeNbs(fctsEtudeProduitNbs('-1', fctsEtudeDivisionNbs(b1bCarrePlusb1Delta, fctsEtudeProduitNbs('2', a))), c1bmoins2d1a))
  const troisbcarrea1plusdeltaa1 = fctsEtudeSommeNbs(fctsEtudeProduitNbs('3', fctsEtudeProduitNbs(b, fctsEtudeProduitNbs(b, a1))), fctsEtudeProduitNbs(delta, a1))
  const bb1moinsac1 = fctsEtudeSommeNbs(fctsEtudeProduitNbs(b, b1), fctsEtudeProduitNbs('-1', fctsEtudeProduitNbs(a, c1)))
  const coefDevantdelta = fctsEtudeSommeNbs(fctsEtudeDivisionNbs(troisbcarrea1plusdeltaa1, quatreACarre), fctsEtudeProduitNbs('-1', fctsEtudeDivisionNbs(bb1moinsac1, a)))
  const image1 = simplificationRacineTrinome(bSol, '-', delta, a, coefDevantdelta)
  const image2 = simplificationRacineTrinome(bSol, '+', delta, a, coefDevantdelta)
  return [image1, image2]
} // imagePolDegre3

/**
 * Retourne la valeur minimale de tab
 * @private
 * @param {Array<string|number>} tab tableau de valeurs (au format nombre ou latex)
 * @returns {{min: string|number, indice: number}}
 */
function minTab (tab) {
  let min, indice
  for (const [i, valeur] of tab.entries()) {
    if (i === 0) {
      indice = i
      min = valeur
    } else if (fctsEtudeCalculValeur(valeur) < fctsEtudeCalculValeur(min)) {
      indice = i
      min = valeur
    }
  }
  return { min, indice }
} // minTab

export function fctsEtudeSigne (nombre) {
  // pour gérer le cas ou c’est une fraction
  const tab = fctsEtudeExtraireNumDen(nombre)
  if (tab[0]) { // c une fraction
    if (tab[3] === '+') {
      return '+' + nombre
    } else {
      // on extrait la valeur absolue de num et den pour construire -a/b
      const num = Math.abs(tab[1])
      const den = Math.abs(tab[2])
      return '-\\frac{' + num + '}{' + den + '}'
    }
  } else {
    if (nombre === 0) {
      return 0
    }
    if (nombre > 0) {
      return '+' + nombre
    } else {
      return '-' + Math.abs(nombre)
    }
  }
} // fin fctsEtudeSigne

/**
 * @private
 * @param b
 * @param plusmoins
 * @param delta
 * @param a
 * @param coefDelta
 * @returns {string}
 */
function simplificationRacineTrinome (b, plusmoins, delta, a, coefDelta) {
  // b, delta et a sont les coef présents dans le calcul (-b-sqrt{delta})/(2*a)  ou (-b+sqrt{delta})/(2*a)
  /// /plusmoins vaut "+" ou "-"
  // cette fonction renvoie le résultat au format latex
  // coefDelta est un argument optionnel. Il n’est pas utile dans le cas des racines d’un trinôme mais pour certains calculs de la forme (-b-coefDelta*sqrt{delta})/(2*a), c’est bien utile
  const deno = fctsEtudeProduitNbs('2', a)
  let nume
  let quotient// réponse renvoyée
  const sqrtDelta = simplifieRadical(delta)
  if (!sqrtDelta.includes('sqrt')) {
    if (plusmoins === '+') {
      nume = fctsEtudeSommeNbs(fctsEtudeProduitNbs('-1', b), sqrtDelta)
    } else {
      nume = fctsEtudeSommeNbs(fctsEtudeProduitNbs('-1', b), fctsEtudeProduitNbs('-1', sqrtDelta))
    }
    quotient = fctsEtudeDivisionNbs(nume, fctsEtudeProduitNbs(a, '2'))
  } else {
    let coefDevantSqrt
    if (sqrtDelta.indexOf('\\sqrt') === 0) {
      coefDevantSqrt = '1'
    } else if ((sqrtDelta.indexOf('\\sqrt') === 1) && (sqrtDelta.charAt(0) === '-')) {
      coefDevantSqrt = '-1'
    } else {
      coefDevantSqrt = sqrtDelta.substring(0, sqrtDelta.indexOf('\\sqrt'))
    }
    if (coefDelta !== undefined) {
      coefDevantSqrt = fctsEtudeProduitNbs(coefDevantSqrt, coefDelta)
    }
    const fracB = fctsEtudeDivisionNbs(fctsEtudeProduitNbs('-1', b), deno)// c’est -b/(2a) simplifié
    const fracSqrt = fctsEtudeDivisionNbs(coefDevantSqrt, deno)// dans sqrt{Delta}/(2a), c/(2a) où c est tel que sqrt{delta}=c*sqrt{b}
    let denFracB = 1
    let numFracB
    if (String(fracB).includes('\\frac')) {
      denFracB = Number(fracB.substring(fracB.lastIndexOf('{') + 1, fracB.length - 1))
      numFracB = Number(fracB.substring(fracB.indexOf('{') + 1, fracB.indexOf('}')))
    } else {
      numFracB = Number(fracB)
    }
    let denFracSqrt = 1
    let numFracSqrt
    if (String(fracSqrt).includes('\\frac')) {
      denFracSqrt = Number(fracSqrt.substring(fracSqrt.lastIndexOf('{') + 1, fracSqrt.length - 1))
      numFracSqrt = Number(fracSqrt.substring(fracSqrt.indexOf('{') + 1, fracSqrt.indexOf('}')))
    } else {
      numFracSqrt = Number(fracSqrt)
    }
    const pgcdDen = j3pPGCD(denFracB, denFracSqrt)
    const denCommun = denFracB * denFracSqrt / pgcdDen
    const newNumFracB = numFracB * denCommun / denFracB
    const newNumFracSqrt = numFracSqrt * denCommun / denFracSqrt
    if (plusmoins === '+') {
      if (String(newNumFracSqrt).charAt(0) === '-') {
        if (String(newNumFracSqrt) === '-1') {
          quotient = String(newNumFracB) + '-' + sqrtDelta.substring(sqrtDelta.indexOf('\\sqrt'))
        } else {
          quotient = String(newNumFracB) + String(newNumFracSqrt) + sqrtDelta.substring(sqrtDelta.indexOf('\\sqrt'))
        }
      } else {
        if (String(newNumFracSqrt) === '1') {
          quotient = String(newNumFracB) + '+' + sqrtDelta.substring(sqrtDelta.indexOf('\\sqrt'))
        } else {
          quotient = String(newNumFracB) + '+' + String(newNumFracSqrt) + sqrtDelta.substring(sqrtDelta.indexOf('\\sqrt'))
        }
      }
    } else {
      if (String(newNumFracSqrt).charAt(0) === '-') {
        if (String(newNumFracSqrt) === '-1') {
          quotient = String(newNumFracB) + '+' + sqrtDelta.substring(sqrtDelta.indexOf('\\sqrt'))
        } else {
          quotient = String(newNumFracB) + '+' + String(newNumFracSqrt).substring(1) + sqrtDelta.substring(sqrtDelta.indexOf('\\sqrt'))
        }
      } else {
        if (String(newNumFracSqrt) === '1') {
          quotient = String(newNumFracB) + '-' + sqrtDelta.substring(sqrtDelta.indexOf('\\sqrt'))
        } else {
          quotient = String(newNumFracB) + '-' + String(newNumFracSqrt) + sqrtDelta.substring(sqrtDelta.indexOf('\\sqrt'))
        }
      }
    }
    if (denCommun > 1) {
      // la réponse simplifiée s'écrit sous la forme (a+b\sqrt{c})/d
      // quotient déterminé précédemment correspond juste à a+b\sqrt{c}
      quotient = '\\frac{' + quotient + '}{' + String(denCommun) + '}'
    }
  }
  return quotient
} // fin simplificationRacineTrinome

/**
 * @private
 * @param nb
 * @returns {string}
 */
function simplifieRadical (nb) {
  // nb est un nombre au format string
  // il est être entier ou sous la forme \\{frac{...}{...}, mais il faut que la fraction soit simplifiée
  // la fonction renvoie le nombre sqrt{nb} sous forme simplifiée
  function simplifieEntier (entier) {
    // la fonction simplifie le nb sqrt{entier} où entier est bien sûr strictement positif
    let sqrtEntierSimplifie
    if (Math.abs(Math.sqrt(Number(entier)) - Math.round(Math.sqrt(Number(entier)))) < Math.pow(10, -10)) {
      // sqrt{entier} est un nombre entier
      sqrtEntierSimplifie = String(Math.round(Math.sqrt(Number(entier))))
    } else {
      let a = Math.floor(Math.sqrt(Number(entier)))
      let radical = Number(entier)
      let nbAvantRadical = 1
      while (a > 1) {
        if (radical % Math.pow(a, 2) === 0) {
          radical = Math.round(radical / Math.pow(a, 2))
          nbAvantRadical = Math.round(nbAvantRadical * a)
        }
        a--
      }
      sqrtEntierSimplifie = String(nbAvantRadical) + '\\sqrt{' + String(radical) + '}'
    }
    return sqrtEntierSimplifie
  }

  let nbSimplifie
  const tabNb = fctsEtudeExtraireNumDen(nb)// ce tableau me donne les infos sur nb (s’il est fractionnaire, son num et den, son signe)
  let sqrtNum, sqrtDen
  if (tabNb[0]) {
    // c’est un nb fractionnaire \\frac{tabNb[1]}{tabNb[2]}
    // On fait alors en sorte d’avoir un résultat sans radical au dénominateur
    if (simplifieEntier(String(tabNb[2])).includes('sqrt')) {
      // c’est que la racine carrée du dénominateur n’est pas un entier.
      sqrtNum = simplifieEntier(String(Number(tabNb[1]) * Number(tabNb[2])))
      sqrtDen = tabNb[2]
      let nbDevantRadicalNum = '1'
      if (sqrtNum.indexOf('\\sqrt') > 0) {
        // on a un nombre devant le radical
        nbDevantRadicalNum = sqrtNum.substring(0, sqrtNum.indexOf('\\sqrt'))
      }
      // on a un nombre de la forme a*sqrt{b}/d
      // il faut encore voir si on peut simplifier a/d
      const fracSimplifiee = fctsEtudeExtraireNumDen(fctsEtudeDivisionNbs(nbDevantRadicalNum, sqrtDen))
      nbSimplifie = '\\frac{' + fracSimplifiee[1] + '}{' + fracSimplifiee[2] + '}' + sqrtNum.substring(sqrtNum.indexOf('\\sqrt'))
    } else {
      sqrtNum = simplifieEntier(String(tabNb[1]))
      sqrtDen = simplifieEntier(String(tabNb[2]))
      nbSimplifie = '\\frac{' + sqrtNum + '}{' + sqrtDen + '}'
    }
  } else {
    // c’est un entier (positif)
    nbSimplifie = simplifieEntier(String(nb))
  }
  return nbSimplifie
} // fin simplifieRadical

/**
 * @private
 * @param tabZeros
 * @param domaine
 * @param isDebug
 * @returns {string[]}
 */
function tableauIntervalles (tabZeros, domaine, isDebug) {
  // cette fonction renvoie sous forme d’un tableau les intervalles sur lesquels on donnera le signe de la fonction
  // tabZeros est le tableau contenant les zéros de la fonction
  // domaine est un tableau. Le domaine est sous la forme ["-infini",a,b,...,"+infini"]
  // isDebug permet d’afficher des infos de debuggage en console
  const newTabZeros = fctsEtudeOrdonneTabSansDoublon(tabZeros, domaine)
  if (isDebug) console.debug('LA avec tabZeros=', tabZeros, ' et domaine=', domaine, ' et newTabZeros=', newTabZeros)
  let i
  let tabIntervalles = []
  tabIntervalles[0] = domaine[0]
  if (domaine[1] === '+infini') {
    for (i = 0; i < newTabZeros.length; i++) {
      tabIntervalles.push(newTabZeros[i])
    }
    tabIntervalles.push('+infini')
  } else {
    // nombre d’intervalles présents dans la domaine de définition (sachant que pour un intervalle, j’ai besoin de 4 éléments dans le tableau domaine
    const nbIntervalleDomaine = Math.floor((domaine.length - 1) / 4)
    tabIntervalles.push(domaine[1])
    for (i = 1; i <= nbIntervalleDomaine; i++) {
      tabIntervalles.push(domaine[4 * i])
      if (domaine[4 * i + 1] !== '+infini') {
        tabIntervalles.push(domaine[4 * i + 1])
      }
    }
    tabIntervalles = tabIntervalles.concat(newTabZeros)
    if (isDebug) console.debug('tabIntervalles1=', tabIntervalles)
    tabIntervalles.push(domaine[nbIntervalleDomaine * 4 + 1])
    if (isDebug) console.debug('tabIntervalles2=', tabIntervalles)
    tabIntervalles = fctsEtudeOrdonneTabSansDoublon(tabIntervalles)
    if (isDebug) console.debug('tabIntervalles3=', tabIntervalles)
  }
  return tabIntervalles
} // fin tableauIntervalles

/**
 * Retourne true si la différence entre les deux est < epsilon (comparaison avec fctsEtudeCalculValeur pour les string, (+|-)(infini|Infinity) est géré)
 * @private
 * @param {number|string} nb1
 * @param nb2
 * @returns {boolean}
 */
function isAlmostEqual (nb1, nb2) {
  const number1 = Number(nb1)
  const number2 = Number(nb2)
  if (Number.isFinite(number1) && Number.isFinite(number2)) return Math.abs(number1 - number2) < epsilon
  if (/(infini|Infinity)/.test(nb1)) {
    // y’a un infini, faut que l’autre soit le même
    const signe = nb1.charAt(0)
    const reIdem = new RegExp(`\\${signe}(infini|Infinity)`)
    return reIdem.test(nb2)
  }
  if (/(infini|Infinity)/.test(nb2)) return false // nb1 l’était pas
  if (typeof nb1 !== 'string' || typeof nb2 !== 'string') return nb1 === nb2
  // deux strings on passe par fctsEtudeCalculValeur
  return Math.abs(fctsEtudeCalculValeur(nb1) - fctsEtudeCalculValeur(nb2)) < epsilon
}

/**
 * Retourne true si distance(nb, 0) < epsilon
 * @private
 * @param {number|string} nb
 * @returns {boolean}
 */
function isAlmostZero (nb) {
  if (typeof nb === 'string') nb = Number(nb)
  if (!Number.isFinite(nb)) return false
  return Math.abs(nb) < epsilon
}