Files
Upsilon/apps/calculation/calculation.cpp
Yaya-Cout d1b8cefcf9 Merge pull request #254 from RedGl0w/additional_result_trigo
Changes to the trigonometry additional result
2025-11-05 17:17:44 +00:00

287 lines
13 KiB
C++

#include "calculation.h"
#include "../shared/poincare_helpers.h"
#include "../shared/scrollable_multiple_expressions_view.h"
#include "../global_preferences.h"
#include "../exam_mode_configuration.h"
#include "app.h"
#include <poincare/exception_checkpoint.h>
#include <poincare/undefined.h>
#include <poincare/unit.h>
#include <poincare/unreal.h>
#include <poincare/symbol_abstract.h>
#include <string.h>
#include <cmath>
#include <algorithm>
using namespace Poincare;
using namespace Shared;
namespace Calculation {
bool Calculation::operator==(const Calculation& c) {
return strcmp(inputText(), c.inputText()) == 0
&& strcmp(approximateOutputText(NumberOfSignificantDigits::Maximal), c.approximateOutputText(NumberOfSignificantDigits::Maximal)) == 0
&& strcmp(approximateOutputText(NumberOfSignificantDigits::UserDefined), c.approximateOutputText(NumberOfSignificantDigits::UserDefined)) == 0
/* Some calculations can make appear trigonometric functions in their
* exact output. Their argument will be different with the angle unit
* preferences but both input and approximate output will be the same.
* For example, i^(sqrt(3)) = cos(sqrt(3)*pi/2)+i*sin(sqrt(3)*pi/2) if
* angle unit is radian and i^(sqrt(3)) = cos(sqrt(3)*90+i*sin(sqrt(3)*90)
* in degree. */
&& strcmp(exactOutputText(), c.exactOutputText()) == 0;
}
Calculation * Calculation::next() const {
const char * result = reinterpret_cast<const char *>(this) + sizeof(Calculation);
for (int i = 0; i < k_numberOfExpressions; i++) {
result = result + strlen(result) + 1; // Pass inputText, exactOutputText, ApproximateOutputText x2
}
return reinterpret_cast<Calculation *>(const_cast<char *>(result));
}
const char * Calculation::approximateOutputText(NumberOfSignificantDigits numberOfSignificantDigits) const {
const char * exactOutput = exactOutputText();
const char * approximateOutputTextWithMaxNumberOfDigits = exactOutput + strlen(exactOutput) + 1;
if (numberOfSignificantDigits == NumberOfSignificantDigits::Maximal) {
return approximateOutputTextWithMaxNumberOfDigits;
}
return approximateOutputTextWithMaxNumberOfDigits + strlen(approximateOutputTextWithMaxNumberOfDigits) + 1;
}
Expression Calculation::input() {
return Expression::Parse(m_inputText, nullptr);
}
Expression Calculation::exactOutput() {
/* Because the angle unit might have changed, we do not simplify again. We
* thereby avoid turning cos(Pi/4) into sqrt(2)/2 and displaying
* 'sqrt(2)/2 = 0.999906' (which is totally wrong) instead of
* 'cos(pi/4) = 0.999906' (which is true in degree). */
Expression exactOutput = Expression::Parse(exactOutputText(), nullptr);
assert(!exactOutput.isUninitialized());
return exactOutput;
}
Expression Calculation::approximateOutput(Context * context, NumberOfSignificantDigits numberOfSignificantDigits) {
Expression exp = Expression::Parse(approximateOutputText(numberOfSignificantDigits), nullptr);
assert(!exp.isUninitialized());
/* Warning:
* Since quite old versions of Epsilon, the Expression 'exp' was used to be
* approximated again to ensure its content was in the expected form - a
* linear combination of Decimal.
* However, since the approximate output may contain units and that a
* Poincare::Unit approximates to undef, thus it must not be approximated
* anymore.
* We have to keep two serializations of the approximation outputs:
* - one with the maximal significant digits, to be used by 'ans' or when
* handling 'OK' event on the approximation output.
* - one with the displayed number of significant digits that we parse to
* create the displayed layout. If we used the other serialization to
* create the layout, the result of the parsing could be an Integer which
* does not take the number of significant digits into account when creating
* its layout. This would lead to wrong number of significant digits in the
* layout.
* For instance:
* Number of asked significant digits: 7
* Input: "123456780", Approximate output: "1.234567E8"
*
* |--------------------------------------------------------------------------------------|
* | Number of significant digits | Approximate text | Parse expression | Layout |
* |------------------------------+------------------+---------------------+--------------|
* | Maximal | "123456780" | Integer(123456780) | "123456780" |
* |------------------------------+------------------+---------------------+--------------|
* | User defined | "1.234567E8" | Decimal(1.234567E8) | "1.234567E8" |
* |--------------------------------------------------------------------------------------|
*
*/
return exp;
}
Layout Calculation::createInputLayout() {
return input().createLayout(Preferences::PrintFloatMode::Decimal, PrintFloat::k_numberOfStoredSignificantDigits);
}
Layout Calculation::createExactOutputLayout(bool * couldNotCreateExactLayout) {
Poincare::ExceptionCheckpoint ecp;
if (ExceptionRun(ecp)) {
return PoincareHelpers::CreateLayout(exactOutput());
} else {
*couldNotCreateExactLayout = true;
return Layout();
}
}
Layout Calculation::createApproximateOutputLayout(Context * context, bool * couldNotCreateApproximateLayout) {
Poincare::ExceptionCheckpoint ecp;
if (ExceptionRun(ecp)) {
return PoincareHelpers::CreateLayout(approximateOutput(context, NumberOfSignificantDigits::UserDefined));
} else {
*couldNotCreateApproximateLayout = true;
return Layout();
}
}
KDCoordinate Calculation::height(bool expanded) {
KDCoordinate h = expanded ? m_expandedHeight : m_height;
assert(h >= 0);
return h;
}
void Calculation::setHeights(KDCoordinate height, KDCoordinate expandedHeight) {
m_height = height;
m_expandedHeight = expandedHeight;
}
Calculation::DisplayOutput Calculation::displayOutput(Context * context) {
if (m_displayOutput != DisplayOutput::Unknown) {
return m_displayOutput;
}
if (shouldOnlyDisplayExactOutput()) {
m_displayOutput = DisplayOutput::ExactOnly;
} else if (
/* If the exact and approximate outputs are equal (with the
* UserDefined number of significant digits), do not display the exact
* output. Indeed, in this case, the layouts are identical. */
strcmp(exactOutputText(), approximateOutputText(NumberOfSignificantDigits::UserDefined)) == 0
||
// If the approximate output is 'unreal' or the exact result is 'undef'
strcmp(exactOutputText(), Undefined::Name()) == 0 ||
strcmp(approximateOutputText(NumberOfSignificantDigits::Maximal), Unreal::Name()) == 0
||
/* If the approximate output is 'undef' and the input and exactOutput are
* equal */
(strcmp(approximateOutputText(NumberOfSignificantDigits::Maximal), Undefined::Name()) == 0 &&
strcmp(inputText(), exactOutputText()) == 0)
||
// Force all outputs to be ApproximateOnly if required by the exam mode configuration
ExamModeConfiguration::exactExpressionsAreForbidden(GlobalPreferences::sharedGlobalPreferences()->examMode())
||
/* If the input contains the following types, we only display the
* approximate output. */
input().recursivelyMatches(
[](const Expression e, Context * c) {
ExpressionNode::Type approximateOnlyTypes[] = {
ExpressionNode::Type::Random,
ExpressionNode::Type::Unit,
ExpressionNode::Type::Round,
ExpressionNode::Type::FracPart,
ExpressionNode::Type::Integral,
ExpressionNode::Type::Product,
ExpressionNode::Type::Sum,
ExpressionNode::Type::Derivative,
ExpressionNode::Type::ConfidenceInterval,
ExpressionNode::Type::PredictionInterval,
ExpressionNode::Type::Sequence
};
return e.isOfType(approximateOnlyTypes, sizeof(approximateOnlyTypes)/sizeof(ExpressionNode::Type));
}, context)
)
{
m_displayOutput = DisplayOutput::ApproximateOnly;
} else if (input().recursivelyMatches(Expression::IsApproximate, context)
|| exactOutput().recursivelyMatches(Expression::IsApproximate, context))
{
m_displayOutput = DisplayOutput::ExactAndApproximateToggle;
} else {
m_displayOutput = DisplayOutput::ExactAndApproximate;
}
return m_displayOutput;
}
void Calculation::forceDisplayOutput(DisplayOutput d) {
// Heights haven't been computed yet
assert(m_height == -1 && m_expandedHeight == -1);
m_displayOutput = d;
}
bool Calculation::shouldOnlyDisplayExactOutput() {
/* If the input is a "store in a function", do not display the approximate
* result. This prevents x->f(x) from displaying x = undef. */
Expression i = input();
return (i.type() == ExpressionNode::Type::Store && i.childAtIndex(1).type() == ExpressionNode::Type::Function)
|| strcmp(approximateOutputText(NumberOfSignificantDigits::Maximal), Undefined::Name()) == 0;
}
Calculation::EqualSign Calculation::exactAndApproximateDisplayedOutputsAreEqual(Poincare::Context * context) {
if (m_equalSign != EqualSign::Unknown) {
return m_equalSign;
}
/* Displaying the right equal symbol is less important than displaying a
* result, so we do not want exactAndApproximateDisplayedOutputsAreEqual to
* create a pool failure that would prevent from displaying a result that we
* managed to compute. We thus encapsulate the method in an exception
* checkpoint: if there was not enough memory on the pool to compute the equal
* sign, just return EqualSign::Approximation.
* We can safely use an exception checkpoint here because we are sure of not
* modifying any pre-existing node in the pool. We are sure there cannot be a
* Store in the exactOutput. */
Poincare::ExceptionCheckpoint ecp;
if (ExceptionRun(ecp)) {
Preferences * preferences = Preferences::sharedPreferences();
// TODO: complex format should not be needed here (as it is not used to create layouts)
Preferences::ComplexFormat complexFormat = Expression::UpdatedComplexFormatWithTextInput(preferences->complexFormat(), m_inputText);
m_equalSign = Expression::ParsedExpressionsAreEqual(exactOutputText(), approximateOutputText(NumberOfSignificantDigits::UserDefined), context, complexFormat, preferences->angleUnit(), GlobalPreferences::sharedGlobalPreferences()->unitFormat()) ? EqualSign::Equal : EqualSign::Approximation;
return m_equalSign;
} else {
/* Do not override m_equalSign in case there is enough room in the pool
* later to compute it. */
return EqualSign::Approximation;
}
}
Calculation::AdditionalInformationType Calculation::additionalInformationType(Context * context) {
if (ExamModeConfiguration::exactExpressionsAreForbidden(GlobalPreferences::sharedGlobalPreferences()->examMode())) {
return AdditionalInformationType::None;
}
Preferences * preferences = Preferences::sharedPreferences();
Preferences::ComplexFormat complexFormat = Expression::UpdatedComplexFormatWithTextInput(preferences->complexFormat(), m_inputText);
Expression i = input();
Expression o = exactOutput();
/* Special case for Equal and Store:
* Equal/Store nodes have to be at the root of the expression, which prevents
* from creating new expressions with equal/store node as a child. We don't
* return any additional outputs for them to avoid bothering with special
* cases. */
if (i.type() == ExpressionNode::Type::Equal || i.type() == ExpressionNode::Type::Store) {
return AdditionalInformationType::None;
}
/* Trigonometry additional results are displayed if either input or output is a sin or a cos. Indeed, we want to capture both cases:
* - > input: cos(60)
* > output: 1/2
* - > input: 2cos(2) - cos(2)
* > output: cos(2)
*/
if (i.isDefinedCosineOrSineOrTangent(context, complexFormat, preferences->angleUnit())) {
return AdditionalInformationType::TrigonometryInput;
}
if (o.isDefinedCosineOrSineOrTangent(context, complexFormat, preferences->angleUnit())) {
return AdditionalInformationType::TrigonometryOutput;
}
if (o.hasUnit()) {
Expression unit;
PoincareHelpers::ReduceAndRemoveUnit(&o, App::app()->localContext(), ExpressionNode::ReductionTarget::User, &unit, ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined, ExpressionNode::UnitConversion::None);
UnitNode::Vector<int> vector = UnitNode::Vector<int>::FromBaseUnits(unit);
const Unit::Representative * representative = Unit::Representative::RepresentativeForDimension(vector);
return representative != nullptr ? AdditionalInformationType::Unit : AdditionalInformationType::None;
}
if (o.isBasedIntegerCappedBy(k_maximalIntegerWithAdditionalInformation)) {
return AdditionalInformationType::Integer;
}
// Find forms like [12]/[23] or -[12]/[23]
if (o.isDivisionOfIntegers() || (o.type() == ExpressionNode::Type::Opposite && o.childAtIndex(0).isDivisionOfIntegers())) {
return AdditionalInformationType::Rational;
}
if (o.hasDefinedComplexApproximation(context, complexFormat, preferences->angleUnit())) {
return AdditionalInformationType::Complex;
}
if (o.type() == ExpressionNode::Type::Matrix) {
return AdditionalInformationType::Matrix;
}
if (o.polynomialDegree(context, "x") == 2) {
return AdditionalInformationType::SecondDegree;
}
return AdditionalInformationType::None;
}
}