From 4a3f749cc68f1e61cfef67003c3dd03fd8a3f467 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Fri, 23 Oct 2020 12:04:35 +0200 Subject: [PATCH] [poincare] Add NullStatus for expressions Change-Id: Ibaba72e3e3589ba259c7b22d402e2b27937f27c1 --- .../matrix_list_controller.cpp | 2 +- apps/solver/equation_store.cpp | 12 ++++--- poincare/include/poincare/based_integer.h | 2 +- poincare/include/poincare/constant.h | 1 + poincare/include/poincare/decimal.h | 2 +- poincare/include/poincare/expression.h | 3 +- poincare/include/poincare/expression_node.h | 7 +++- poincare/include/poincare/float.h | 2 +- .../include/poincare/hyperbolic_arc_cosine.h | 2 +- .../hyperbolic_trigonometric_function.h | 2 +- poincare/include/poincare/infinity.h | 1 + poincare/include/poincare/rational.h | 2 +- poincare/src/complex_cartesian.cpp | 13 +++---- .../src/hyperbolic_trigonometric_function.cpp | 2 +- poincare/src/matrix.cpp | 8 +++-- poincare/src/multiplication.cpp | 12 +++---- poincare/src/power.cpp | 2 +- poincare/test/expression_properties.cpp | 35 ++++++++++--------- 18 files changed, 62 insertions(+), 48 deletions(-) diff --git a/apps/calculation/additional_outputs/matrix_list_controller.cpp b/apps/calculation/additional_outputs/matrix_list_controller.cpp index 5b4f3051d..803fb69ce 100644 --- a/apps/calculation/additional_outputs/matrix_list_controller.cpp +++ b/apps/calculation/additional_outputs/matrix_list_controller.cpp @@ -51,7 +51,7 @@ void MatrixListController::setExpression(Poincare::Expression e) { m_layouts[index++] = getLayoutFromExpression(determinant, context, preferences); // 2. Matrix inverse if invertible matrix // A squared matrix is invertible if and only if determinant is non null - if (!determinant.isUndefined() && !determinant.isNumberZero()) { + if (!determinant.isUndefined() && determinant.nullStatus(context) != ExpressionNode::NullStatus::Null) { m_indexMessageMap[index] = messageIndex++; m_layouts[index++] = getLayoutFromExpression(MatrixInverse::Builder(m_expression.clone()), context, preferences); } diff --git a/apps/solver/equation_store.cpp b/apps/solver/equation_store.cpp index 86f38fefb..e5865a19a 100644 --- a/apps/solver/equation_store.cpp +++ b/apps/solver/equation_store.cpp @@ -316,12 +316,13 @@ EquationStore::Error EquationStore::resolveLinearSystem(Expression exactSolution for (int j = m-1; j >= 0; j--) { bool rowWithNullCoefficients = true; for (int i = 0; i < n; i++) { - if (!Ab.matrixChild(j, i).isNumberZero()) { + if (Ab.matrixChild(j, i).nullStatus(context) != ExpressionNode::NullStatus::Null) { rowWithNullCoefficients = false; break; } } - if (rowWithNullCoefficients && !Ab.matrixChild(j, n).isNumberZero()) { + if (rowWithNullCoefficients && Ab.matrixChild(j, n).nullStatus(context) != ExpressionNode::NullStatus::Null) { + // TODO: Handle ExpressionNode::NullStatus::Unknown m_numberOfSolutions = 0; } } @@ -348,11 +349,12 @@ EquationStore::Error EquationStore::oneDimensialPolynomialSolve(Expression exact if (delta.isUninitialized()) { delta = Poincare::Undefined::Builder(); } - if (delta.isNumberZero()) { + if (delta.nullStatus(context) == ExpressionNode::NullStatus::Null) { // if delta = 0, x0=x1= -b/(2a) exactSolutions[0] = Division::Builder(Opposite::Builder(coefficients[1]), Multiplication::Builder(Rational::Builder(2), coefficients[2])); m_numberOfSolutions = 2; } else { + // TODO: Handle ExpressionNode::NullStatus::Unknown // x0 = (-b-sqrt(delta))/(2a) exactSolutions[0] = Division::Builder(Subtraction::Builder(Opposite::Builder(coefficients[1].clone()), SquareRoot::Builder(delta.clone())), Multiplication::Builder(Rational::Builder(2), coefficients[2].clone())); // x1 = (-b+sqrt(delta))/(2a) @@ -383,8 +385,8 @@ EquationStore::Error EquationStore::oneDimensialPolynomialSolve(Expression exact Expression * mult5Operands[3] = {new Rational::Builder(3), a->clone(), c->clone()}; Expression * delta0 = new Subtraction::Builder(new Power::Builder(b->clone(), new Rational::Builder(2), false), new Multiplication::Builder(mult5Operands, 3, false), false); Reduce(&delta0, *context); - if (delta->isNumberZero()) { - if (delta0->isNumberZero()) { + if (delta->nullStatus(context) == ExpressionNode::NullStatus::Null) { + if (delta0->nullStatus(context) == ExpressionNode::NullStatus::Null) { // delta0 = 0 && delta = 0 --> x0 = -b/(3a) delete delta0; m_exactSolutions[0] = new Opposite::Builder(new Division::Builder(b, new Multiplication::Builder(new Rational::Builder(3), a, false), false), false); diff --git a/poincare/include/poincare/based_integer.h b/poincare/include/poincare/based_integer.h index ead8a0f14..f553902c1 100644 --- a/poincare/include/poincare/based_integer.h +++ b/poincare/include/poincare/based_integer.h @@ -28,7 +28,7 @@ public: // Expression subclassing Type type() const override { return Type::BasedInteger; } Sign sign(Context * context) const override { return Sign::Positive; } - bool isNumberZero() const override { return integer().isZero(); } + NullStatus nullStatus(Context * context) const override { return integer().isZero() ? NullStatus::Null : NullStatus::NonNull; } // Layout Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; diff --git a/poincare/include/poincare/constant.h b/poincare/include/poincare/constant.h index 59d1a4d48..ed04a922c 100644 --- a/poincare/include/poincare/constant.h +++ b/poincare/include/poincare/constant.h @@ -28,6 +28,7 @@ public: // Expression Properties Type type() const override { return Type::Constant; } Sign sign(Context * context) const override; + NullStatus nullStatus(Context * context) const override { return NullStatus::NonNull; } /* Layout */ Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; diff --git a/poincare/include/poincare/decimal.h b/poincare/include/poincare/decimal.h index 317f2f355..6f9267efd 100644 --- a/poincare/include/poincare/decimal.h +++ b/poincare/include/poincare/decimal.h @@ -42,8 +42,8 @@ public: // Properties Type type() const override { return Type::Decimal; } Sign sign(Context * context) const override { return m_negative ? Sign::Negative : Sign::Positive; } + NullStatus nullStatus(Context * context) const override { return unsignedMantissa().isZero() ? NullStatus::Null : NullStatus::NonNull; } Expression setSign(Sign s, ReductionContext reductionContext) override; - bool isNumberZero() const override { return unsignedMantissa().isZero(); } // Approximation Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { diff --git a/poincare/include/poincare/expression.h b/poincare/include/poincare/expression.h index f20d4ddeb..dff4782ed 100644 --- a/poincare/include/poincare/expression.h +++ b/poincare/include/poincare/expression.h @@ -151,9 +151,10 @@ public: ExpressionNode::Type type() const { return node()->type(); } bool isOfType(ExpressionNode::Type * types, int length) const { return node()->isOfType(types, length); } ExpressionNode::Sign sign(Context * context) const { return node()->sign(context); } + ExpressionNode::NullStatus nullStatus(Context * context) const { return node()->nullStatus(context); } + bool isStrictly(ExpressionNode::Sign s, Context * context) const { return s == node()->sign(context) && node()->nullStatus(context) == ExpressionNode::NullStatus::NonNull; } bool isUndefined() const { return node()->type() == ExpressionNode::Type::Undefined || node()->type() == ExpressionNode::Type::Unreal; } bool isNumber() const { return node()->isNumber(); } - bool isNumberZero() const { return node()->isNumberZero(); } bool isRationalOne() const; bool isRandom() const { return node()->isRandom(); } bool isParameteredExpression() const { return node()->isParameteredExpression(); } diff --git a/poincare/include/poincare/expression_node.h b/poincare/include/poincare/expression_node.h index 3147929e8..c971e28b7 100644 --- a/poincare/include/poincare/expression_node.h +++ b/poincare/include/poincare/expression_node.h @@ -156,6 +156,11 @@ public: Unknown = 0, Positive = 1 }; + enum class NullStatus { + Unknown = -1, + NonNull = 0, + Null = 1, + }; class ReductionContext { public: @@ -186,8 +191,8 @@ public: }; virtual Sign sign(Context * context) const { return Sign::Unknown; } + virtual NullStatus nullStatus(Context * context) const { return NullStatus::Unknown; } virtual bool isNumber() const { return false; } - virtual bool isNumberZero() const { return false; } virtual bool isRandom() const { return false; } virtual bool isParameteredExpression() const { return false; } /* childAtIndexNeedsUserParentheses checks if parentheses are required by mathematical rules: diff --git a/poincare/include/poincare/float.h b/poincare/include/poincare/float.h index 31695ac4a..d1eda0887 100644 --- a/poincare/include/poincare/float.h +++ b/poincare/include/poincare/float.h @@ -38,9 +38,9 @@ public: // Properties Type type() const override { return (sizeof(T) == sizeof(float)) ? Type::Float : Type::Double; } Sign sign(Context * context) const override { return m_value < 0 ? Sign::Negative : Sign::Positive; } + NullStatus nullStatus(Context * context) const override { return m_value == 0.0 ? NullStatus::Null : NullStatus::NonNull; } Expression setSign(Sign s, ReductionContext reductionContext) override; int simplificationOrderSameType(const ExpressionNode * e, bool ascending, bool canBeInterrupted, bool ignoreParentheses) const override; - bool isNumberZero() const override { return m_value == 0.0; } // Layout int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; diff --git a/poincare/include/poincare/hyperbolic_arc_cosine.h b/poincare/include/poincare/hyperbolic_arc_cosine.h index ac0a6aee8..67855d907 100644 --- a/poincare/include/poincare/hyperbolic_arc_cosine.h +++ b/poincare/include/poincare/hyperbolic_arc_cosine.h @@ -21,7 +21,7 @@ public: Type type() const override { return Type::HyperbolicArcCosine; } private: // Simplification - bool isNotableValue(Expression e) const override { return e.isRationalOne(); } + bool isNotableValue(Expression e, Context * context) const override { return e.isRationalOne(); } // Layout Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; diff --git a/poincare/include/poincare/hyperbolic_trigonometric_function.h b/poincare/include/poincare/hyperbolic_trigonometric_function.h index 176c9e834..10828793e 100644 --- a/poincare/include/poincare/hyperbolic_trigonometric_function.h +++ b/poincare/include/poincare/hyperbolic_trigonometric_function.h @@ -17,7 +17,7 @@ private: LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } Expression shallowReduce(ReductionContext reductionContext) override; - virtual bool isNotableValue(Expression e) const { return e.isNumberZero(); } + virtual bool isNotableValue(Expression e, Context * context) const { return e.nullStatus(context) == ExpressionNode::NullStatus::Null; } virtual Expression imageOfNotableValue() const { return Rational::Builder(0); } }; diff --git a/poincare/include/poincare/infinity.h b/poincare/include/poincare/infinity.h index 2d434f266..a1e8ec5bf 100644 --- a/poincare/include/poincare/infinity.h +++ b/poincare/include/poincare/infinity.h @@ -23,6 +23,7 @@ public: // Properties Type type() const override { return Type::Infinity; } Sign sign(Context * context) const override { return m_negative ? Sign::Negative : Sign::Positive; } + NullStatus nullStatus(Context * context) const override { return NullStatus::NonNull; } Expression setSign(Sign s, ReductionContext reductionContext) override; // Approximation diff --git a/poincare/include/poincare/rational.h b/poincare/include/poincare/rational.h index f9c73d300..abde3b3e0 100644 --- a/poincare/include/poincare/rational.h +++ b/poincare/include/poincare/rational.h @@ -17,7 +17,7 @@ public: bool isNegative() const { return m_negative; } void setNegative(bool negative) { m_negative = negative; } bool isInteger() const { return denominator().isOne(); } - bool isNumberZero() const override { return isZero(); } + NullStatus nullStatus(Context * context) const override { return isZero() ? NullStatus::Null : NullStatus::NonNull; } // TreeNode size_t size() const override; diff --git a/poincare/src/complex_cartesian.cpp b/poincare/src/complex_cartesian.cpp index 486e866f1..df7cea0c5 100644 --- a/poincare/src/complex_cartesian.cpp +++ b/poincare/src/complex_cartesian.cpp @@ -60,7 +60,7 @@ Expression ComplexCartesian::shallowReduce() { return e; } } - if (imag().isNumberZero()) { + if (imag().nullStatus(nullptr) == ExpressionNode::NullStatus::Null) { Expression r = real(); replaceWithInPlace(r); return r; @@ -128,9 +128,9 @@ Expression ComplexCartesian::squareNorm(ExpressionNode::ReductionContext reducti Expression ComplexCartesian::norm(ExpressionNode::ReductionContext reductionContext) { Expression a; // Special case for pure real or pure imaginary cartesian - if (imag().isNumberZero()) { + if (imag().nullStatus(nullptr) == ExpressionNode::NullStatus::Null) { a = real(); - } else if (real().isNumberZero()) { + } else if (real().nullStatus(nullptr) == ExpressionNode::NullStatus::Null) { a = imag(); } if (!a.isUninitialized()) { @@ -149,7 +149,8 @@ Expression ComplexCartesian::norm(ExpressionNode::ReductionContext reductionCont Expression ComplexCartesian::argument(ExpressionNode::ReductionContext reductionContext) { Expression a = real(); Expression b = imag(); - if (!b.isNumberZero()) { + if (b.nullStatus(reductionContext.context()) != ExpressionNode::NullStatus::Null) { + // TODO: Handle ExpressionNode::NullStatus::Unknown // if b != 0, argument = sign(b) * π/2 - atan(a/b) // First, compute atan(a/b) or (π/180)*atan(a/b) Expression divab = Division::Builder(a, b.clone()); @@ -242,11 +243,11 @@ ComplexCartesian ComplexCartesian::powerInteger(int n, ExpressionNode::Reduction Expression a = real(); Expression b = imag(); assert(n > 0); - assert(!b.isNumberZero()); + assert(b.nullStatus(reductionContext.context()) != ExpressionNode::NullStatus::Null); // Special case: a == 0 (otherwise, we are going to introduce undefined expressions - a^0 = NAN) // (b*i)^n = b^n*i^n with i^n == i, -i, 1 or -1 - if (a.isNumberZero()) { + if (a.nullStatus(reductionContext.context()) == ExpressionNode::NullStatus::Null) { ComplexCartesian result; Expression bpow = Power::Builder(b, Rational::Builder(n)); if (n/2%2 == 1) { diff --git a/poincare/src/hyperbolic_trigonometric_function.cpp b/poincare/src/hyperbolic_trigonometric_function.cpp index 676d6a724..6ed6d6baa 100644 --- a/poincare/src/hyperbolic_trigonometric_function.cpp +++ b/poincare/src/hyperbolic_trigonometric_function.cpp @@ -26,7 +26,7 @@ Expression HyperbolicTrigonometricFunction::shallowReduce(ExpressionNode::Reduct } // Step 1. Notable values - if (node()->isNotableValue(c)) { + if (node()->isNotableValue(c, reductionContext.context())) { Expression result = node()->imageOfNotableValue(); replaceWithInPlace(result); return result; diff --git a/poincare/src/matrix.cpp b/poincare/src/matrix.cpp index 17e0054f9..1c7fea1d3 100644 --- a/poincare/src/matrix.cpp +++ b/poincare/src/matrix.cpp @@ -136,7 +136,8 @@ int Matrix::rank(Context * context, Preferences::ComplexFormat complexFormat, Pr int i = rank-1; while (i >= 0) { int j = m.numberOfColumns()-1; - while (j >= i && matrixChild(i,j).isNumberZero()) { + while (j >= i && matrixChild(i,j).nullStatus(context) == ExpressionNode::NullStatus::Null) { + // TODO: Handle ExpressionNode::NullStatus::Unknown j--; } if (j == i-1) { @@ -225,7 +226,7 @@ Matrix Matrix::rowCanonize(ExpressionNode::ReductionContext reductionContext, Ex // Using float to find the biggest pivot is sufficient. float pivot = AbsoluteValue::Builder(matrixChild(iPivot_temp, k).clone()).approximateToScalar(reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit()); // Handle very low pivots - if (pivot == 0.0f && !matrixChild(iPivot_temp, k).isNumberZero()) { + if (pivot == 0.0f && matrixChild(iPivot_temp, k).nullStatus(reductionContext.context()) != ExpressionNode::NullStatus::Null) { pivot = FLT_MIN; } @@ -241,7 +242,8 @@ Matrix Matrix::rowCanonize(ExpressionNode::ReductionContext reductionContext, Ex } iPivot_temp++; } - if (matrixChild(iPivot, k).isNumberZero()) { + if (matrixChild(iPivot, k).nullStatus(reductionContext.context()) == ExpressionNode::NullStatus::Null) { + // TODO: Handle ExpressionNode::NullStatus::Unknown // No non-null coefficient in this column, skip k++; if (determinant) { diff --git a/poincare/src/multiplication.cpp b/poincare/src/multiplication.cpp index fc699cff9..f0d07307a 100644 --- a/poincare/src/multiplication.cpp +++ b/poincare/src/multiplication.cpp @@ -1122,27 +1122,27 @@ bool Multiplication::TermsCanSafelyCombineExponents(const Expression & e1, const Expression base = Base(e1); ExpressionNode::Sign baseSign = base.sign(reductionContext.context()); + ExpressionNode::NullStatus baseNullStatus = base.nullStatus(reductionContext.context()); - if (baseSign != ExpressionNode::Sign::Unknown && !base.isNumberZero()) { + if (baseSign != ExpressionNode::Sign::Unknown && baseNullStatus == ExpressionNode::NullStatus::NonNull) { // x cannot be null return true; } Expression exponent1 = CreateExponent(e1); - ExpressionNode::Sign exponentSign1 = exponent1.sign(reductionContext.context()); Expression exponent2 = CreateExponent(e2); - ExpressionNode::Sign exponentSign2 = exponent2.sign(reductionContext.context()); - if (exponentSign1 == ExpressionNode::Sign::Positive && !exponent1.isNumberZero() - && exponentSign2 == ExpressionNode::Sign::Positive && !exponent2.isNumberZero()) { + if (exponent1.isStrictly(ExpressionNode::Sign::Positive, reductionContext.context()) + && exponent2.isStrictly(ExpressionNode::Sign::Positive, reductionContext.context())) { // a and b are strictly positive return true; } Expression sum = Addition::Builder(exponent1, exponent2).shallowReduce(reductionContext); ExpressionNode::Sign sumSign = sum.sign(reductionContext.context()); + ExpressionNode::NullStatus sumNullStatus = sum.nullStatus(reductionContext.context()); - if (sumSign == ExpressionNode::Sign::Negative || sum.isNumberZero()) { + if (sumSign == ExpressionNode::Sign::Negative || sumNullStatus == ExpressionNode::NullStatus::Null) { // a+b is negative or null return true; } diff --git a/poincare/src/power.cpp b/poincare/src/power.cpp index 55c3b8bb0..a259bd46e 100644 --- a/poincare/src/power.cpp +++ b/poincare/src/power.cpp @@ -501,7 +501,7 @@ Expression Power::shallowReduce(ExpressionNode::ReductionContext reductionContex // x^0 if (rationalIndex.isZero()) { // 0^0 = undef or (±inf)^0 = undef - if (base.isNumberZero() || baseType == ExpressionNode::Type::Infinity) { + if (base.nullStatus(reductionContext.context()) == ExpressionNode::NullStatus::Null || baseType == ExpressionNode::Type::Infinity) { return replaceWithUndefinedInPlace(); } // x^0 diff --git a/poincare/test/expression_properties.cpp b/poincare/test/expression_properties.cpp index 0222d22c0..379775b2f 100644 --- a/poincare/test/expression_properties.cpp +++ b/poincare/test/expression_properties.cpp @@ -18,23 +18,24 @@ QUIZ_CASE(poincare_properties_is_number) { } QUIZ_CASE(poincare_properties_is_number_zero) { - quiz_assert(!BasedInteger::Builder("2",Integer::Base::Binary).isNumberZero()); - quiz_assert(!BasedInteger::Builder("2",Integer::Base::Decimal).isNumberZero()); - quiz_assert(!BasedInteger::Builder("2",Integer::Base::Hexadecimal).isNumberZero()); - quiz_assert(BasedInteger::Builder("0",Integer::Base::Binary).isNumberZero()); - quiz_assert(BasedInteger::Builder("0",Integer::Base::Decimal).isNumberZero()); - quiz_assert(BasedInteger::Builder("0",Integer::Base::Hexadecimal).isNumberZero()); - quiz_assert(!Decimal::Builder("2",3).isNumberZero()); - quiz_assert(Decimal::Builder("0",0).isNumberZero()); - quiz_assert(!Float::Builder(1.0f).isNumberZero()); - quiz_assert(Float::Builder(0.0f).isNumberZero()); - quiz_assert(!Infinity::Builder(true).isNumberZero()); - quiz_assert(!Undefined::Builder().isNumberZero()); - quiz_assert(!Rational::Builder(2,3).isNumberZero()); - quiz_assert(Rational::Builder(0,1).isNumberZero()); - quiz_assert(!Symbol::Builder('a').isNumberZero()); - quiz_assert(!Multiplication::Builder(Rational::Builder(1), Rational::Builder(0)).isNumberZero()); - quiz_assert(!Addition::Builder(Rational::Builder(1), Rational::Builder(-1)).isNumberZero()); + Shared::GlobalContext context; + quiz_assert(BasedInteger::Builder("2",Integer::Base::Binary).nullStatus(&context) == ExpressionNode::NullStatus::NonNull ); + quiz_assert(BasedInteger::Builder("2",Integer::Base::Decimal).nullStatus(&context) == ExpressionNode::NullStatus::NonNull ); + quiz_assert(BasedInteger::Builder("2",Integer::Base::Hexadecimal).nullStatus(&context) == ExpressionNode::NullStatus::NonNull ); + quiz_assert(BasedInteger::Builder("0",Integer::Base::Binary).nullStatus(&context) == ExpressionNode::NullStatus::Null ); + quiz_assert(BasedInteger::Builder("0",Integer::Base::Decimal).nullStatus(&context) == ExpressionNode::NullStatus::Null ); + quiz_assert(BasedInteger::Builder("0",Integer::Base::Hexadecimal).nullStatus(&context) == ExpressionNode::NullStatus::Null ); + quiz_assert(Decimal::Builder("2",3).nullStatus(&context) == ExpressionNode::NullStatus::NonNull ); + quiz_assert(Decimal::Builder("0",0).nullStatus(&context) == ExpressionNode::NullStatus::Null ); + quiz_assert(Float::Builder(1.0f).nullStatus(&context) == ExpressionNode::NullStatus::NonNull ); + quiz_assert(Float::Builder(0.0f).nullStatus(&context) == ExpressionNode::NullStatus::Null ); + quiz_assert(Infinity::Builder(true).nullStatus(&context) == ExpressionNode::NullStatus::NonNull ); + quiz_assert(Undefined::Builder().nullStatus(&context) == ExpressionNode::NullStatus::Unknown); + quiz_assert(Rational::Builder(2,3).nullStatus(&context) == ExpressionNode::NullStatus::NonNull ); + quiz_assert(Rational::Builder(0,1).nullStatus(&context) == ExpressionNode::NullStatus::Null ); + quiz_assert(Symbol::Builder('a').nullStatus(&context) == ExpressionNode::NullStatus::Unknown); + quiz_assert(Multiplication::Builder(Rational::Builder(1), Rational::Builder(0)).nullStatus(&context) == ExpressionNode::NullStatus::Unknown); + quiz_assert(Addition::Builder(Rational::Builder(1), Rational::Builder(-1)).nullStatus(&context) == ExpressionNode::NullStatus::Unknown); } QUIZ_CASE(poincare_properties_is_random) {