diff --git a/poincare/src/arc_cosine.cpp b/poincare/src/arc_cosine.cpp index 49d10cecf..8bd1baae2 100644 --- a/poincare/src/arc_cosine.cpp +++ b/poincare/src/arc_cosine.cpp @@ -25,14 +25,24 @@ Expression ArcCosineNode::shallowReduce(Context & context, Preferences::AngleUni template Complex ArcCosineNode::computeOnComplex(const std::complex c, Preferences::AngleUnit angleUnit) { - std::complex result = std::acos(c); - /* acos has a branch cut on ]-inf, -1[U]1, +inf[: it is then multivalued on - * this cut. We followed the convention chosen by the lib c++ of llvm on - * ]-inf+0i, -1+0i[ (warning: acos takes the other side of the cut values on - * ]-inf-0i, -1-0i[) and choose the values on ]1+0i, +inf+0i[ to comply with - * acos(-x) = Pi - acos(x) and tan(arccos(x)) = sqrt(1-x^2)/x. */ - if (c.imag() == 0 && c.real() > 1) { - result.imag(-result.imag()); // other side of the cut + std::complex result; + if (c.imag() == 0 && std::fabs(c.real()) <= 1.0) { + /* acos: [-1;1] -> R + * In these cases we rather use std::acos(double) because acos on complexes + * is not as precise as pow on double in std library. For instance, + * - acos(complex(0.03,0.0) = complex(1.54079,-1.11022e-16) + * - acos(0.03) = 1.54079 */ + result = std::acos(c.real()); + } else { + result = std::acos(c); + /* acos has a branch cut on ]-inf, -1[U]1, +inf[: it is then multivalued on + * this cut. We followed the convention chosen by the lib c++ of llvm on + * ]-inf+0i, -1+0i[ (warning: acos takes the other side of the cut values on + * ]-inf-0i, -1-0i[) and choose the values on ]1+0i, +inf+0i[ to comply with + * acos(-x) = Pi - acos(x) and tan(arccos(x)) = sqrt(1-x^2)/x. */ + if (c.imag() == 0 && c.real() > 1) { + result.imag(-result.imag()); // other side of the cut + } } result = Trigonometry::RoundToMeaningfulDigits(result, c); return Complex(Trigonometry::ConvertRadianToAngleUnit(result, angleUnit)); diff --git a/poincare/src/arc_sine.cpp b/poincare/src/arc_sine.cpp index 17f04ffb1..cd28c1fcf 100644 --- a/poincare/src/arc_sine.cpp +++ b/poincare/src/arc_sine.cpp @@ -25,14 +25,24 @@ Expression ArcSineNode::shallowReduce(Context & context, Preferences::AngleUnit template Complex ArcSineNode::computeOnComplex(const std::complex c, Preferences::AngleUnit angleUnit) { - std::complex result = std::asin(c); - /* asin has a branch cut on ]-inf, -1[U]1, +inf[: it is then multivalued on - * this cut. We followed the convention chosen by the lib c++ of llvm on - * ]-inf+0i, -1+0i[ (warning: asin takes the other side of the cut values on - * ]-inf-0i, -1-0i[) and choose the values on ]1+0i, +inf+0i[ to comply with - * asin(-x) = -asin(x) and tan(arcsin(x)) = x/sqrt(1-x^2). */ - if (c.imag() == 0 && c.real() > 1) { - result.imag(-result.imag()); // other side of the cut + std::complex result; + if (c.imag() == 0 && std::fabs(c.real()) <= 1.0) { + /* asin: [-1;1] -> R + * In these cases we rather use std::asin(double) because asin on complexes + * is not as precise as asin on double in std library. For instance, + * - asin(complex(0.03,0.0) = complex(0.0300045,1.11022e-16) + * - asin(0.03) = 0.0300045 */ + result = std::asin(c.real()); + } else { + result = std::asin(c); + /* asin has a branch cut on ]-inf, -1[U]1, +inf[: it is then multivalued on + * this cut. We followed the convention chosen by the lib c++ of llvm on + * ]-inf+0i, -1+0i[ (warning: asin takes the other side of the cut values on + * ]-inf-0i, -1-0i[) and choose the values on ]1+0i, +inf+0i[ to comply with + * asin(-x) = -asin(x) and tan(arcsin(x)) = x/sqrt(1-x^2). */ + if (c.imag() == 0 && c.real() > 1) { + result.imag(-result.imag()); // other side of the cut + } } result = Trigonometry::RoundToMeaningfulDigits(result, c); return Complex(Trigonometry::ConvertRadianToAngleUnit(result, angleUnit)); diff --git a/poincare/src/arc_tangent.cpp b/poincare/src/arc_tangent.cpp index 615f78e7a..e5cc533d7 100644 --- a/poincare/src/arc_tangent.cpp +++ b/poincare/src/arc_tangent.cpp @@ -21,14 +21,24 @@ int ArcTangentNode::serialize(char * buffer, int bufferSize, Preferences::PrintF template Complex ArcTangentNode::computeOnComplex(const std::complex c, Preferences::AngleUnit angleUnit) { - std::complex result = std::atan(c); - /* atan has a branch cut on ]-inf*i, -i[U]i, +inf*i[: it is then multivalued - * on this cut. We followed the convention chosen by the lib c++ of llvm on - * ]-i+0, -i*inf+0[ (warning: atan takes the other side of the cut values on - * ]-i+0, -i*inf+0[) and choose the values on ]-inf*i, -i[ to comply with - * atan(-x) = -atan(x) and sin(arctan(x)) = x/sqrt(1+x^2). */ - if (c.real() == 0 && c.imag() < -1) { - result.real(-result.real()); // other side of the cut + std::complex result; + if (c.imag() == 0 && std::fabs(c.real()) <= 1.0) { + /* atan: R -> R + * In these cases we rather use std::atan(double) because atan on complexes + * is not as precise as atan on double in std library. For instance, + * - atan(complex(0.01,0.0) = complex(9.9996666866652E-3,5.5511151231258E-17) + * - atan(0.03) = 9.9996666866652E-3 */ + result = std::atan(c.real()); + } else { + result = std::atan(c); + /* atan has a branch cut on ]-inf*i, -i[U]i, +inf*i[: it is then multivalued + * on this cut. We followed the convention chosen by the lib c++ of llvm on + * ]-i+0, -i*inf+0[ (warning: atan takes the other side of the cut values on + * ]-i+0, -i*inf+0[) and choose the values on ]-inf*i, -i[ to comply with + * atan(-x) = -atan(x) and sin(arctan(x)) = x/sqrt(1+x^2). */ + if (c.real() == 0 && c.imag() < -1) { + result.real(-result.real()); // other side of the cut + } } result = Trigonometry::RoundToMeaningfulDigits(result, c); return Complex(Trigonometry::ConvertRadianToAngleUnit(result, angleUnit)); diff --git a/poincare/test/trigo.cpp b/poincare/test/trigo.cpp index 62cd65e48..e37543900 100644 --- a/poincare/test/trigo.cpp +++ b/poincare/test/trigo.cpp @@ -93,6 +93,7 @@ QUIZ_CASE(poincare_trigo_evaluate) { */ // On [-1, 1] assert_parsed_expression_evaluates_to("acos(0.5)", "1.0471975511966", Radian); + assert_parsed_expression_evaluates_to("acos(0.03)", "1.5407918249714", Radian); assert_parsed_expression_evaluates_to("acos(0.5)", "60", Degree); // On [1, inf[ assert_parsed_expression_evaluates_to("acos(2)", "1.3169578969248*I", Radian); @@ -124,6 +125,7 @@ QUIZ_CASE(poincare_trigo_evaluate) { */ // On [-1, 1] assert_parsed_expression_evaluates_to("asin(0.5)", "0.5235987755983", Radian); + assert_parsed_expression_evaluates_to("asin(0.03)", "3.0004501823477E-2", Radian); assert_parsed_expression_evaluates_to("asin(0.5)", "30", Degree); // On [1, inf[ assert_parsed_expression_evaluates_to("asin(2)", "1.5707963267949-1.3169578969248*I", Radian); @@ -153,6 +155,7 @@ QUIZ_CASE(poincare_trigo_evaluate) { */ // On R assert_parsed_expression_evaluates_to("atan(2)", "1.1071487177941", Radian); + assert_parsed_expression_evaluates_to("atan(0.01)", "9.9996666866652E-3", Radian); assert_parsed_expression_evaluates_to("atan(2)", "63.434948822922", Degree); assert_parsed_expression_evaluates_to("atan(0.5)", "0.4636476", Radian); // Symmetry: odd