diff --git a/src/ExprNode.cc b/src/ExprNode.cc
index ebd364de6db36f068f6df6d7c612e635236ea1b4..638c8917fe13c19b2dd6832e5cf2f974fdbe3757 100644
--- a/src/ExprNode.cc
+++ b/src/ExprNode.cc
@@ -3186,15 +3186,20 @@ UnaryOpNode::normalizeEquationHelper(const set<expr_t> &contain_var, expr_t rhs)
     case UnaryOpcode::log10:
       rhs = datatree.AddPower(datatree.AddNonNegativeConstant("10"), rhs);
       break;
-    case UnaryOpcode::cos:
-      rhs = datatree.AddAcos(rhs);
-      break;
-    case UnaryOpcode::sin:
-      rhs = datatree.AddAsin(rhs);
-      break;
-    case UnaryOpcode::tan:
-      rhs = datatree.AddAtan(rhs);
-      break;
+    /* Trigonometric functions:
+       – acos(cos(x))=x holds ∀x∈[0,π], but not ∀x∈ℝ (Counter example: x=2π).
+         So we don’t transform cos(x)=RHS into x=acos(RHS).
+       – asin(sin(x))=x holds ∀x∈[−π/2,π/2], but not ∀x∈ℝ (Counter example: x=π).
+         So we don’t transform sin(x)=RHS into x=asin(RHS).
+       – atan(tan(x))=x holds ∀x∈(−π/2,π/2), but not ∀x∈ℝ (Counter example: x=π).
+         So we don’t transform tan(x)=RHS into x=atan(RHS).
+       – cos(acos(x))=x holds ∀x∈[−1,1]. However, for x∈ℝ\[−1,1], acos(x)=NaN.
+         So it’s ok to transform acos(x)=RHS into x=cos(RHS) (it naturally enforces
+         the already existing restriction that x must belong to [−1,1]).
+       – sin(asin(x))=x holds ∀x∈[−1,1]. However, for x∈ℝ\[−1,1], asin(x)=NaN.
+         So it’s ok to transform asin(x)=RHS into x=sin(RHS), by the same reasoning.
+       – tan(atan(x))=x holds ∀x∈ℝ.
+         So it’s ok to transform atan(x)=RHS into x=tan(RHS). */
     case UnaryOpcode::acos:
       rhs = datatree.AddCos(rhs);
       break;
@@ -3204,9 +3209,20 @@ UnaryOpNode::normalizeEquationHelper(const set<expr_t> &contain_var, expr_t rhs)
     case UnaryOpcode::atan:
       rhs = datatree.AddTan(rhs);
       break;
-    case UnaryOpcode::cosh:
-      rhs = datatree.AddAcosh(rhs);
-      break;
+    /* Hyperbolic functions:
+       – acosh(cosh(x))=x holds ∀x⩾0, but not ∀x∈ℝ (Counter example: x=−1).
+         So we don’t transform cosh(x)=RHS into x=acosh(RHS).
+       – asinh(sinh(x))=x holds ∀x∈ℝ.
+         So it’s ok to transform sinh(x)=RHS into x=asinh(RHS).
+       – atanh(tanh(x))=x holds ∀x∈ℝ.
+         So it’s ok to transform tanh(x)=RHS into x=atanh(RHS).
+       – cosh(acosh(x))=x holds ∀x⩾1. However, for x<1, acosh(x)=NaN.
+         So it’s ok to transform acosh(x)=RHS into x=cosh(RHS) (it naturally enforces
+         the already existing restriction that x must belong to [1,+∞)).
+       – sinh(asinh(x))=x holds ∀x∈ℝ.
+         So it’s ok to transform asinh(x)=RHS into x=sinh(RHS).
+       – tanh(atanh(x))=x holds ∀x∈ℝ.
+         So it’s ok to transform atanh(x)=RHS into x=tanh(RHS). */
     case UnaryOpcode::sinh:
       rhs = datatree.AddAsinh(rhs);
       break;
@@ -3222,6 +3238,9 @@ UnaryOpNode::normalizeEquationHelper(const set<expr_t> &contain_var, expr_t rhs)
     case UnaryOpcode::atanh:
       rhs = datatree.AddTanh(rhs);
       break;
+      /* (√x)²=x holds ∀x⩾0. However, for x<0, √x=NaN.
+         So it’s ok to transform √x=RHS into x=RHS² (it naturally enforces
+         the already existing restriction that x must be non-negative). */
     case UnaryOpcode::sqrt:
       rhs = datatree.AddPower(rhs, datatree.Two);
       break;
@@ -4947,6 +4966,15 @@ BinaryOpNode::normalizeEquationHelper(const set<expr_t> &contain_var, expr_t rhs
       else
         rhs = datatree.AddMinus(arg1, rhs);
       break;
+    /* (x*a)/a=x holds ∀x>0, but requires a≠0. So, transforming x*a=RHS into
+       x=RHS/a is incorrect when a=0, and may generate NaNs. However, since
+       this equation is supposed to pin down the variable of interest contained
+       in x (through the matching between equations and variables), this case
+       should not happen (it will not happen at the initial values if the
+       matching has been performed using the numerical Jacobian, which is tried
+       first; it could happen with other values than the initial ones, or if
+       the symbolic Jacobian has been used as a last resort, but this indicates
+       a problem in the matching which is beyond our control at this point). */
     case BinaryOpcode::times:
       if (arg1_contains_var)
         rhs = datatree.AddDivide(rhs, arg2);
@@ -4957,13 +4985,21 @@ BinaryOpNode::normalizeEquationHelper(const set<expr_t> &contain_var, expr_t rhs
       if (arg1_contains_var)
         rhs = datatree.AddTimes(rhs, arg2);
       else
+        /* Transforming a/x=RHS into x=a/RHS is incorrect if RHS=0. However,
+           per the same reasoning as for the multiplication case above, it
+           nevertheless makes sense to do the transformation. */
         rhs = datatree.AddDivide(arg1, rhs);
       break;
     case BinaryOpcode::power:
       if (arg1_contains_var)
-        rhs = datatree.AddPower(rhs, datatree.AddDivide(datatree.One, arg2));
+        /* (x^a)^(1/a)=x holds ∀x>0 when a≠0, and ∀x∈ℝ when a is an odd integer.
+           However, it does not hold if x<0 and a is an even integer (different
+           from zero). For example, ((−1)^2)^½ = 1.
+           So in the general case, we cannot transform x^a=RHS into
+           x=RHS^(1/a). */
+        throw NormalizationFailed();
       else
-        // a^f(X)=rhs is normalized in f(X)=ln(rhs)/ln(a)
+        // a^x=RHS is normalized in x=ln(RHS)/ln(a)
         rhs = datatree.AddDivide(datatree.AddLog(rhs), datatree.AddLog(arg1));
       break;
     case BinaryOpcode::equal: