[apps/calculation] Calculations now hold 4 texts: input, exact output

and 2 approximate outputs - one with the maximal number of significant
digits and one with the number of significant digits asked by the user.
This enables to find the approximate output without going through the
approximation routine again.
This commit is contained in:
Émilie Feral
2020-01-22 13:40:57 +01:00
committed by Léa Saviot
parent 5631ef6ad0
commit c2540a1d8a
8 changed files with 95 additions and 58 deletions

View File

@@ -121,7 +121,7 @@ void IllustratedListController::setExpression(Poincare::Expression e) {
int IllustratedListController::textAtIndex(char * buffer, size_t bufferSize, int index) {
ScrollableThreeExpressionsCell * myCell = static_cast<ScrollableThreeExpressionsCell *>(m_listController.selectableTableView()->selectedCell());
Shared::ExpiringPointer<Calculation> c = m_calculationStore.calculationAtIndex(index-1);
const char * text = myCell->selectedSubviewPosition() == ScrollableThreeExpressionsView::SubviewPosition::Right ? c->approximateOutputText() : c->exactOutputText();
const char * text = myCell->selectedSubviewPosition() == ScrollableThreeExpressionsView::SubviewPosition::Right ? c->approximateOutputText(Calculation::NumberOfSignificantDigits::Maximal) : c->exactOutputText();
return strlcpy(buffer, text, bufferSize);
}

View File

@@ -16,7 +16,7 @@ void TrigonometryListController::setExpression(Poincare::Expression e) {
m_calculationStore.push("θ", context);
// Set trigonometry illustration
float angle = Shared::PoincareHelpers::ApproximateToScalar<float>(m_calculationStore.calculationAtIndex(0)->approximateOutput(context), context);
float angle = Shared::PoincareHelpers::ApproximateToScalar<float>(m_calculationStore.calculationAtIndex(0)->approximateOutput(context, Calculation::NumberOfSignificantDigits::Maximal), context);
m_model.setAngle(angle);
}

View File

@@ -16,7 +16,8 @@ static inline KDCoordinate maxCoordinate(KDCoordinate x, KDCoordinate y) { retur
bool Calculation::operator==(const Calculation& c) {
return strcmp(inputText(), c.inputText()) == 0
&& strcmp(approximateOutputText(), c.approximateOutputText()) == 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.
@@ -28,8 +29,8 @@ bool Calculation::operator==(const Calculation& c) {
Calculation * Calculation::next() const {
const char * result = reinterpret_cast<const char *>(this) + sizeof(Calculation);
for (int i = 0; i < 3; i++) {
result = result + strlen(result) + 1; // Pass inputText, exactOutputText, ApproximateOutputText
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));
}
@@ -41,9 +42,13 @@ void Calculation::tidy() {
m_expandedHeight = -1;
}
const char * Calculation::approximateOutputText() const {
const char * Calculation::approximateOutputText(NumberOfSignificantDigits numberOfSignificantDigits) const {
const char * exactOutput = exactOutputText();
return exactOutput + strlen(exactOutput) + 1;
const char * approximateOutputTextWithMaxNumberOfDigits = exactOutput + strlen(exactOutput) + 1;
if (numberOfSignificantDigits == NumberOfSignificantDigits::Maximal) {
return approximateOutputTextWithMaxNumberOfDigits;
}
return approximateOutputTextWithMaxNumberOfDigits + strlen(approximateOutputTextWithMaxNumberOfDigits) + 1;
}
Expression Calculation::input() {
@@ -60,18 +65,39 @@ Expression Calculation::exactOutput() {
return exactOutput;
}
Expression Calculation::approximateOutput(Context * context) {
Expression exp = Expression::Parse(approximateOutputText(), nullptr);
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. That is
* currently the case (see Poincare::Expression::simplifyAndApproximate). So
* 'exp' does not need to be approximated. Moreover since the approximate
* output may contain units and that a Poincare::Unit approximates to undef,
* thus it must not be approximated. If another behavior is desired, the
* previous considerations should be taken into account. */
return exp;
* 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() {
@@ -91,7 +117,7 @@ Layout Calculation::createExactOutputLayout(bool * couldNotCreateExactLayout) {
Layout Calculation::createApproximateOutputLayout(Context * context, bool * couldNotCreateApproximateLayout) {
Poincare::ExceptionCheckpoint ecp;
if (ExceptionRun(ecp)) {
return PoincareHelpers::CreateLayout(approximateOutput(context));
return PoincareHelpers::CreateLayout(approximateOutput(context, NumberOfSignificantDigits::UserDefined));
} else {
*couldNotCreateApproximateLayout = true;
return Layout();
@@ -220,18 +246,18 @@ Calculation::DisplayOutput Calculation::displayOutput(Context * context) {
}, context, true))
{
m_displayOutput = DisplayOutput::ApproximateOnly;
} else if (strcmp(exactOutputText(), approximateOutputText()) == 0) {
/* If the exact and approximate results' texts are equal and their layouts
* too, do not display the exact result. If the two layouts are not equal
* because of the number of significant digits, we display both. */
m_displayOutput = exactAndApproximateDisplayedOutputsAreEqual(context) == Calculation::EqualSign::Equal ? DisplayOutput::ApproximateOnly : DisplayOutput::ExactAndApproximate;
} else if (strcmp(exactOutputText(), approximateOutputText(NumberOfSignificantDigits::UserDefined)) == 0) {
/* If the exact and approximate results' texts are equal (with the
* UserDefined number of significant digits), do not display the exact
* result. Indeed, in this case, the layouts are identical. */
m_displayOutput = DisplayOutput::ApproximateOnly;
} else if (strcmp(exactOutputText(), Undefined::Name()) == 0
|| strcmp(approximateOutputText(), Unreal::Name()) == 0
|| strcmp(approximateOutputText(NumberOfSignificantDigits::Maximal), Unreal::Name()) == 0
|| exactOutput().type() == ExpressionNode::Type::Undefined)
{
// If the approximate result is 'unreal' or the exact result is 'undef'
m_displayOutput = DisplayOutput::ApproximateOnly;
} else if (strcmp(approximateOutputText(), Undefined::Name()) == 0
} else if (strcmp(approximateOutputText(NumberOfSignificantDigits::Maximal), Undefined::Name()) == 0
&& strcmp(inputText(), exactOutputText()) == 0)
{
/* If the approximate result is 'undef' and the input and exactOutput are
@@ -278,7 +304,7 @@ Calculation::EqualSign Calculation::exactAndApproximateDisplayedOutputsAreEqual(
exactOutputExpression = Undefined::Builder();
}
Preferences::ComplexFormat complexFormat = Expression::UpdatedComplexFormatWithTextInput(preferences->complexFormat(), m_inputText);
m_equalSign = exactOutputExpression.isEqualToItsApproximationLayout(approximateOutput(context), buffer, bufferSize, complexFormat, preferences->angleUnit(), preferences->displayMode(), preferences->numberOfSignificantDigits(), context) ? EqualSign::Equal : EqualSign::Approximation;
m_equalSign = exactOutputExpression.isEqualToItsApproximationLayout(approximateOutput(context, NumberOfSignificantDigits::UserDefined), buffer, bufferSize, complexFormat, preferences->angleUnit(), preferences->displayMode(), preferences->numberOfSignificantDigits(), context) ? EqualSign::Equal : EqualSign::Approximation;
return m_equalSign;
} else {
/* Do not override m_equalSign in case there is enough room in the pool

View File

@@ -19,6 +19,7 @@ class CalculationStore;
* */
class Calculation {
friend CalculationStore;
public:
enum class EqualSign : uint8_t {
Unknown,
@@ -63,14 +64,19 @@ public:
void tidy();
// Texts
enum class NumberOfSignificantDigits {
Maximal,
UserDefined
};
const char * inputText() const { return m_inputText; }
const char * exactOutputText() const { return m_inputText + strlen(m_inputText) + 1; }
const char * approximateOutputText() const;
// See comment in approximateOutput implementation explaining the need of two approximateOutputTexts
const char * approximateOutputText(NumberOfSignificantDigits numberOfSignificantDigits) const;
// Expressions
Poincare::Expression input();
Poincare::Expression exactOutput();
Poincare::Expression approximateOutput(Poincare::Context * context);
Poincare::Expression approximateOutput(Poincare::Context * context, NumberOfSignificantDigits numberOfSignificantDigits);
// Layouts
Poincare::Layout createInputLayout();
@@ -89,6 +95,7 @@ public:
// Additional Information
AdditionalInformationType additionalInformationType(Poincare::Context * context);
private:
static constexpr int k_numberOfExpressions = 4;
static constexpr KDCoordinate k_heightComputationFailureHeight = 50;
static constexpr const char * k_maximalIntegerWithAdditionalInformation = "10000000000000000";
/* Buffers holding text expressions have to be longer than the text written

View File

@@ -79,7 +79,7 @@ ExpiringPointer<Calculation> CalculationStore::push(const char * text, Context *
const char * inputSerialization = nextSerializationLocation;
{
Expression input = Expression::Parse(text, context).replaceSymbolWithExpression(Symbol::Ans(), ans);
if (!serializeExpression(input, nextSerializationLocation, &newCalculationsLocation)) {
if (!pushSerializeExpression(input, nextSerializationLocation, &newCalculationsLocation)) {
/* If the input does not fit in the store (event if the current
* calculation is the only calculation), just replace the calculation with
* undef. */
@@ -89,16 +89,27 @@ ExpiringPointer<Calculation> CalculationStore::push(const char * text, Context *
}
// Compute and serialize the outputs
/* The serialized outputs are:
* - the exact ouput
* - the approximate output with the maximal number of significant digits
* - the approximate output with the displayed number of significant digits */
{
Expression outputs[] = {Expression(), Expression()};
// Outputs hold exact output, approximate output and its duplicate
constexpr static int numberOfOutputs = Calculation::k_numberOfExpressions - 1;
Expression outputs[numberOfOutputs] = {Expression(), Expression(), Expression()};
PoincareHelpers::ParseAndSimplifyAndApproximate(inputSerialization, &(outputs[0]), &(outputs[1]), context, false);
for (int i = 0; i < 2; i++) {
if (!serializeExpression(outputs[i], nextSerializationLocation, &newCalculationsLocation)) {
outputs[2] = outputs[1];
int numberOfSignificantDigits = Poincare::PrintFloat::k_numberOfStoredSignificantDigits;
for (int i = 0; i < numberOfOutputs; i++) {
if (i == numberOfOutputs - 1) {
numberOfSignificantDigits = Poincare::Preferences::sharedPreferences()->numberOfSignificantDigits();
}
if (!pushSerializeExpression(outputs[i], nextSerializationLocation, &newCalculationsLocation, numberOfSignificantDigits)) {
/* If the exat/approximate output does not fit in the store (event if the
* current calculation is the only calculation), replace the output with
* undef if it fits, else replace the whole calcualtion with undef. */
Expression undef = Undefined::Builder();
if (!serializeExpression(undef, nextSerializationLocation, &newCalculationsLocation)) {
if (!pushSerializeExpression(undef, nextSerializationLocation, &newCalculationsLocation)) {
return emptyStoreAndPushUndef(context);
}
}
@@ -165,7 +176,7 @@ Expression CalculationStore::ansExpression(Context * context) {
Expression e = mostRecentCalculation->exactOutput();
bool exactOuptutInvolvesStoreEqual = e.type() == ExpressionNode::Type::Store || e.type() == ExpressionNode::Type::Equal;
if (mostRecentCalculation->input().recursivelyMatches(Expression::IsApproximate, context) || exactOuptutInvolvesStoreEqual) {
return mostRecentCalculation->approximateOutput(context);
return mostRecentCalculation->approximateOutput(context, Calculation::NumberOfSignificantDigits::Maximal);
}
return mostRecentCalculation->exactOutput();
}
@@ -182,12 +193,20 @@ Calculation * CalculationStore::bufferCalculationAtIndex(int i) {
return nullptr;
}
bool CalculationStore::serializeExpression(Expression e, char * location, char * * newCalculationsLocation) {
bool CalculationStore::pushSerializeExpression(Expression e, char * location, char * * newCalculationsLocation, int numberOfSignificantDigits) {
assert(m_slidedBuffer);
return pushExpression(
[](char * location, size_t locationSize, void * e) {
return PoincareHelpers::Serialize(*(Expression *)e, location, locationSize) < (int)locationSize-1;
}, &e, location, newCalculationsLocation);
assert(*newCalculationsLocation <= m_buffer + k_bufferSize);
bool expressionIsPushed = false;
while (true) {
size_t locationSize = *newCalculationsLocation - location;
expressionIsPushed = (PoincareHelpers::Serialize(e, location, locationSize, numberOfSignificantDigits) < (int)locationSize-1);
if (expressionIsPushed || *newCalculationsLocation >= m_buffer + k_bufferSize) {
break;
}
*newCalculationsLocation = *newCalculationsLocation + deleteLastCalculation();
assert(*newCalculationsLocation <= m_buffer + k_bufferSize);
}
return expressionIsPushed;
}
char * CalculationStore::slideCalculationsToEndOfBuffer() {
@@ -230,20 +249,6 @@ const char * CalculationStore::lastCalculationPosition(const char * calculations
return reinterpret_cast<const char *>(c);
}
bool CalculationStore::pushExpression(ValueCreator valueCreator, Expression * expression, char * location, char * * newCalculationsLocation) {
assert(*newCalculationsLocation <= m_buffer + k_bufferSize);
bool expressionIsPushed = false;
while (true) {
expressionIsPushed = valueCreator(location, *newCalculationsLocation - location, expression);
if (expressionIsPushed || *newCalculationsLocation >= m_buffer + k_bufferSize) {
break;
}
*newCalculationsLocation = *newCalculationsLocation + deleteLastCalculation();
assert(*newCalculationsLocation <= m_buffer + k_bufferSize);
}
return expressionIsPushed;
}
Shared::ExpiringPointer<Calculation> CalculationStore::emptyStoreAndPushUndef(Context * context) {
/* We end up here as a result of a failed calculation push. The store
* attributes are not necessarily clean, so we need to reset them. */

View File

@@ -3,6 +3,7 @@
#include "calculation.h"
#include <apps/shared/expiring_pointer.h>
#include <poincare/print_float.h>
namespace Calculation {
@@ -35,7 +36,7 @@ public:
void tidy();
private:
static constexpr int k_maxNumberOfCalculations = 25;
static constexpr int k_bufferSize = 10 * 3 * Constant::MaxSerializedExpressionSize;
static constexpr int k_bufferSize = 10 * Calculation::k_numberOfExpressions * Constant::MaxSerializedExpressionSize;
class CalculationIterator {
public:
@@ -55,12 +56,10 @@ private:
Calculation * bufferCalculationAtIndex(int i);
int remainingBufferSize() const { assert(m_bufferEnd >= m_buffer); return k_bufferSize - (m_bufferEnd - m_buffer); }
bool serializeExpression(Poincare::Expression e, char * location, char * * newCalculationsLocation);
bool pushSerializeExpression(Poincare::Expression e, char * location, char * * newCalculationsLocation, int numberOfSignificantDigits = Poincare::PrintFloat::k_numberOfStoredSignificantDigits);
char * slideCalculationsToEndOfBuffer(); // returns the new position of the calculations
size_t deleteLastCalculation(const char * calculationsStart = nullptr);
const char * lastCalculationPosition(const char * calculationsStart) const;
typedef bool (*ValueCreator)(char * location, size_t locationSize, void * e);
bool pushExpression(ValueCreator valueCrator, Poincare::Expression * expression, char * location, char * * newCalculationsLocation);
Shared::ExpiringPointer<Calculation> emptyStoreAndPushUndef(Poincare::Context * context);
char m_buffer[k_bufferSize];

View File

@@ -80,7 +80,7 @@ bool HistoryController::handleEvent(Ion::Events::Event event) {
if (outputSubviewPosition == ScrollableTwoExpressionsView::SubviewPosition::Right
&& !calculation->shouldOnlyDisplayExactOutput())
{
editController->insertTextBody(calculation->approximateOutputText());
editController->insertTextBody(calculation->approximateOutputText(Calculation::NumberOfSignificantDigits::Maximal));
} else {
editController->insertTextBody(calculation->exactOutputText());
}

View File

@@ -50,7 +50,7 @@ QUIZ_CASE(calculation_ans) {
store.push("ans+0.22", &globalContext);
lastCalculation = store.calculationAtIndex(0);
quiz_assert(lastCalculation->displayOutput(&globalContext) == ::Calculation::Calculation::DisplayOutput::ExactAndApproximateToggle);
quiz_assert(strcmp(lastCalculation->approximateOutputText(),"2.6366666666667") == 0);
quiz_assert(strcmp(lastCalculation->approximateOutputText(Calculation::NumberOfSignificantDigits::Maximal),"2.6366666666667") == 0);
store.deleteAll();
}