From d37540f032c5115a06bcc7a28edd370979e3f5f9 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 19 Aug 2020 15:25:06 +0200 Subject: [PATCH] [poincare/unit] Add Celsius and Fahrenheit Temperatures can be converted to and from degree Celsius and Fahrenheit. When used in non-trivial calculations, they are always reduced to undef, as the rules for manipulating relative scales are not well defined. Change-Id: If59e224a0e7f940b421bc894bbe2279c90f38d04 --- apps/math_toolbox.cpp | 5 +- apps/shared.universal.i18n | 2 + apps/toolbox.de.i18n | 2 + apps/toolbox.en.i18n | 2 + apps/toolbox.es.i18n | 2 + apps/toolbox.fr.i18n | 2 + apps/toolbox.it.i18n | 2 + apps/toolbox.nl.i18n | 2 + apps/toolbox.pt.i18n | 2 + poincare/include/poincare/unit.h | 40 +++++++-- poincare/src/unit.cpp | 109 +++++++++++++++++++++++- poincare/src/unit_convert.cpp | 11 +++ poincare/test/expression_properties.cpp | 10 +++ poincare/test/simplification.cpp | 24 ++++++ 14 files changed, 201 insertions(+), 14 deletions(-) diff --git a/apps/math_toolbox.cpp b/apps/math_toolbox.cpp index ae33aa01c..00a32610a 100644 --- a/apps/math_toolbox.cpp +++ b/apps/math_toolbox.cpp @@ -160,7 +160,10 @@ const ToolboxMessageTree unitCurrentAmpereChildren[] = { }; const ToolboxMessageTree unitTemperatureChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::UnitTemperatureKelvinSymbol, I18n::Message::UnitTemperatureKelvin)}; + ToolboxMessageTree::Leaf(I18n::Message::UnitTemperatureKelvinSymbol, I18n::Message::UnitTemperatureKelvin), + ToolboxMessageTree::Leaf(I18n::Message::UnitTemperatureCelsiusSymbol, I18n::Message::UnitTemperatureCelsius), + ToolboxMessageTree::Leaf(I18n::Message::UnitTemperatureFahrenheitSymbol, I18n::Message::UnitTemperatureFahrenheit), +}; const ToolboxMessageTree unitAmountMoleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::UnitAmountMoleSymbol, I18n::Message::UnitAmountMole), diff --git a/apps/shared.universal.i18n b/apps/shared.universal.i18n index 2c5ad9ab8..e6d00f02e 100644 --- a/apps/shared.universal.i18n +++ b/apps/shared.universal.i18n @@ -35,6 +35,8 @@ UnitCurrentAmpereSymbol = "_A" UnitCurrentAmpereMilliSymbol = "_mA" UnitCurrentAmpereMicroSymbol = "_μA" UnitTemperatureKelvinSymbol = "_K" +UnitTemperatureCelsiusSymbol = "_Cel" +UnitTemperatureFahrenheitSymbol = "_Fah" UnitAmountMoleSymbol = "_mol" UnitAmountMoleMilliSymbol = "_mmol" UnitAmountMoleMicroSymbol = "_μmol" diff --git a/apps/toolbox.de.i18n b/apps/toolbox.de.i18n index 5be05ac9c..9e91443fd 100644 --- a/apps/toolbox.de.i18n +++ b/apps/toolbox.de.i18n @@ -43,6 +43,8 @@ UnitCurrentAmpereMilli = "Milliampere" UnitCurrentAmpereMicro = "Mikroampere" UnitTemperatureMenu = "Temperatur" UnitTemperatureKelvin = "Kelvin" +UnitTemperatureCelsius = "Celsius" +UnitTemperatureFahrenheit = "Fahrenheit" UnitAmountMenu = "Stoffmenge" UnitAmountMole = "Mol" UnitAmountMoleMilli = "Millimol" diff --git a/apps/toolbox.en.i18n b/apps/toolbox.en.i18n index 2b55b9276..153b68a6d 100644 --- a/apps/toolbox.en.i18n +++ b/apps/toolbox.en.i18n @@ -43,6 +43,8 @@ UnitCurrentAmpereMilli = "Milliampere" UnitCurrentAmpereMicro = "Microampere" UnitTemperatureMenu = "Temperature" UnitTemperatureKelvin = "Kelvin" +UnitTemperatureCelsius = "Celsius" +UnitTemperatureFahrenheit = "Fahrenheit" UnitAmountMenu = "Amount of substance" UnitAmountMole = "Mole" UnitAmountMoleMilli = "Millimole" diff --git a/apps/toolbox.es.i18n b/apps/toolbox.es.i18n index f48b85136..0bf1900e0 100644 --- a/apps/toolbox.es.i18n +++ b/apps/toolbox.es.i18n @@ -43,6 +43,8 @@ UnitCurrentAmpereMilli = "Milliampere" UnitCurrentAmpereMicro = "Microampere" UnitTemperatureMenu = "Temperature" UnitTemperatureKelvin = "Kelvin" +UnitTemperatureCelsius = "Celsius" +UnitTemperatureFahrenheit = "Fahrenheit" UnitAmountMenu = "Amount of substance" UnitAmountMole = "Mole" UnitAmountMoleMilli = "Millimole" diff --git a/apps/toolbox.fr.i18n b/apps/toolbox.fr.i18n index df9c16da5..b5920b07d 100644 --- a/apps/toolbox.fr.i18n +++ b/apps/toolbox.fr.i18n @@ -43,6 +43,8 @@ UnitCurrentAmpereMilli = "Milliampère" UnitCurrentAmpereMicro = "Microampère" UnitTemperatureMenu = "Température" UnitTemperatureKelvin = "Kelvin" +UnitTemperatureCelsius = "Celsius" +UnitTemperatureFahrenheit = "Fahrenheit" UnitAmountMenu = "Quantité de matière" UnitAmountMole = "Mole" UnitAmountMoleMilli = "Millimole" diff --git a/apps/toolbox.it.i18n b/apps/toolbox.it.i18n index 2b6faead3..efd60309b 100644 --- a/apps/toolbox.it.i18n +++ b/apps/toolbox.it.i18n @@ -43,6 +43,8 @@ UnitCurrentAmpereMilli = "Milliampere" UnitCurrentAmpereMicro = "Microampere" UnitTemperatureMenu = "Temperatura" UnitTemperatureKelvin = "Kelvin" +UnitTemperatureCelsius = "Celsius" +UnitTemperatureFahrenheit = "Fahrenheit" UnitAmountMenu = "Quantità de materia" UnitAmountMole = "Mole" UnitAmountMoleMilli = "Millimole" diff --git a/apps/toolbox.nl.i18n b/apps/toolbox.nl.i18n index ddfee739f..f1aecba0c 100644 --- a/apps/toolbox.nl.i18n +++ b/apps/toolbox.nl.i18n @@ -43,6 +43,8 @@ UnitCurrentAmpereMilli = "Milliampère" UnitCurrentAmpereMicro = "Microampère" UnitTemperatureMenu = "Temperatuur" UnitTemperatureKelvin = "Kelvin" +UnitTemperatureCelsius = "Celsius" +UnitTemperatureFahrenheit = "Fahrenheit" UnitAmountMenu = "Hoeveelheid stof" UnitAmountMole = "Mol" UnitAmountMoleMilli = "Millimol" diff --git a/apps/toolbox.pt.i18n b/apps/toolbox.pt.i18n index 304330c03..7fb81d46e 100644 --- a/apps/toolbox.pt.i18n +++ b/apps/toolbox.pt.i18n @@ -43,6 +43,8 @@ UnitCurrentAmpereMilli = "Miliampere" UnitCurrentAmpereMicro = "Microampere" UnitTemperatureMenu = "Temperatura" UnitTemperatureKelvin = "Kelvin" +UnitTemperatureCelsius = "Celsius" +UnitTemperatureFahrenheit = "Fahrenheit" UnitAmountMenu = "Quantidade da substância" UnitAmountMole = "Mole" UnitAmountMoleMilli = "Milimole" diff --git a/poincare/include/poincare/unit.h b/poincare/include/poincare/unit.h index 456ff4700..1ea8f7fe4 100644 --- a/poincare/include/poincare/unit.h +++ b/poincare/include/poincare/unit.h @@ -69,6 +69,7 @@ public: *(coefficientsAddresses[i]) = c; } bool operator==(const Vector &rhs) const { return time == rhs.time && distance == rhs.distance && mass == rhs.mass && current == rhs.current && temperature == rhs.temperature && amountOfSubstance == rhs.amountOfSubstance && luminuousIntensity == rhs.luminuousIntensity; } + bool operator!=(const Vector &rhs) const { return !(*this == rhs); } void addAllCoefficients(const Vector other, int factor); Expression toBaseUnits() const; T time; @@ -102,6 +103,7 @@ public: m_inputPrefixable(inputPrefixable), m_outputPrefixable(outputPrefixable) {} + virtual const Vector dimensionVector() const { return Vector{.time = 0, .distance = 0, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; }; virtual int numberOfRepresentatives() const { return 0; }; /* representativesOfSameDimension returns a pointer to the array containing @@ -110,18 +112,20 @@ public: virtual const Prefix * basePrefix() const { return Prefix::EmptyPrefix(); } virtual bool isBaseUnit() const { return false; } virtual const Representative * standardRepresentative(double value, double exponent, ExpressionNode::ReductionContext reductionContext, const Prefix * * prefix) const { return DefaultFindBestRepresentative(value, exponent, representativesOfSameDimension(), numberOfRepresentatives(), prefix); } - virtual bool hasAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const { return true; } + virtual bool hasAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const { return false; } virtual int setAdditionalExpressions(double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) const { return 0; } + const char * rootSymbol() const { return m_rootSymbol; } double ratio() const { return m_ratio; } bool isInputPrefixable() const { return m_inputPrefixable != Prefixable::None; } bool isOutputPrefixable() const { return m_outputPrefixable != Prefixable::None; } - bool canPrefix(const Prefix * prefix, bool input) const; int serialize(char * buffer, int bufferSize, const Prefix * prefix) const; bool canParseWithEquivalents(const char * symbol, size_t length, const Representative * * representative, const Prefix * * prefix) const; bool canParse(const char * symbol, size_t length, const Prefix * * prefix) const; Expression toBaseUnits() const; + bool canPrefix(const Prefix * prefix, bool input) const; const Prefix * findBestPrefix(double value, double exponent) const; + protected: static const Representative * DefaultFindBestRepresentative(double value, double exponent, const Representative * representatives, int length, const Prefix * * prefix); const char * m_rootSymbol; @@ -187,7 +191,6 @@ public: int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; bool isBaseUnit() const override { return this == representativesOfSameDimension(); } - bool hasAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return false; } private: using Representative::Representative; }; @@ -195,13 +198,18 @@ public: class TemperatureRepresentative : public Representative { friend class Unit; public: + static double ConvertTemperatures(double value, const Representative * source, const Representative * target); constexpr static TemperatureRepresentative Default() { return TemperatureRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } const Vector dimensionVector() const override { return Vector{.time = 0, .distance = 0, .mass = 0, .current = 0, .temperature = 1, .amountOfSubstance = 0, .luminuousIntensity = 0}; } - int numberOfRepresentatives() const override { return 1; } + int numberOfRepresentatives() const override { return 3; } const Representative * representativesOfSameDimension() const override; bool isBaseUnit() const override { return this == representativesOfSameDimension(); } - bool hasAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return false; } + const Representative * standardRepresentative(double value, double exponent, ExpressionNode::ReductionContext reductionContext, const Prefix * * prefix) const override { return this; } + bool hasAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return true; } + int setAdditionalExpressions(double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) const override; private: + static constexpr double k_celsiusOrigin = 273.15; + static constexpr double k_fahrenheitOrigin = 459.67; using Representative::Representative; }; @@ -213,7 +221,6 @@ public: int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; bool isBaseUnit() const override { return this == representativesOfSameDimension(); } - bool hasAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return false; } private: using Representative::Representative; }; @@ -226,7 +233,6 @@ public: int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; bool isBaseUnit() const override { return this == representativesOfSameDimension(); } - bool hasAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return false; } private: using Representative::Representative; }; @@ -238,7 +244,6 @@ public: const Vector dimensionVector() const override { return Vector{.time = -1, .distance = 0, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; - bool hasAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return false; } private: using Representative::Representative; }; @@ -272,6 +277,7 @@ public: const Vector dimensionVector() const override { return Vector{.time = -2, .distance = 2, .mass = 1, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } int numberOfRepresentatives() const override { return 2; } const Representative * representativesOfSameDimension() const override; + bool hasAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return true; } int setAdditionalExpressions(double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) const override; private: using Representative::Representative; @@ -394,6 +400,7 @@ public: int numberOfRepresentatives() const override { return 2; } const Representative * representativesOfSameDimension() const override; const Representative * standardRepresentative(double value, double exponent, ExpressionNode::ReductionContext reductionContext, const Prefix * * prefix) const override; + bool hasAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return true; } int setAdditionalExpressions(double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) const override; private: using Representative::Representative; @@ -407,6 +414,7 @@ public: int numberOfRepresentatives() const override { return 8; } const Representative * representativesOfSameDimension() const override; const Representative * standardRepresentative(double value, double exponent, ExpressionNode::ReductionContext reductionContext, const Prefix * * prefix) const override; + bool hasAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return true; } int setAdditionalExpressions(double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) const override; private: using Representative::Representative; @@ -418,6 +426,7 @@ public: constexpr static SpeedRepresentative Default() { return SpeedRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } const VectordimensionVector() const override { return Vector{.time = -1, .distance = 1, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } const Representative * standardRepresentative(double value, double exponent, ExpressionNode::ReductionContext reductionContext, const Prefix * * prefix) const override { return nullptr; } + bool hasAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return true; } int setAdditionalExpressions(double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) const override; private: using Representative::Representative; @@ -539,7 +548,11 @@ public: typedef UnitNode::CurrentRepresentative CurrentRepresentative; static constexpr const CurrentRepresentative k_currentRepresentatives[] = { CurrentRepresentative("A", 1., Prefixable::All, Prefixable::LongScale) }; typedef UnitNode::TemperatureRepresentative TemperatureRepresentative; - static constexpr const TemperatureRepresentative k_temperatureRepresentatives[] = { TemperatureRepresentative("K", 1., Prefixable::All, Prefixable::LongScale) }; + static constexpr const TemperatureRepresentative k_temperatureRepresentatives[] = { + TemperatureRepresentative("K", 1., Prefixable::All, Prefixable::None), + TemperatureRepresentative("Cel", 1., Prefixable::None, Prefixable::None), + TemperatureRepresentative("Fah", 5./9., Prefixable::None, Prefixable::None), + }; typedef UnitNode::AmountOfSubstanceRepresentative AmountOfSubstanceRepresentative; static constexpr const AmountOfSubstanceRepresentative k_amountOfSubstanceRepresentatives[] = { AmountOfSubstanceRepresentative("mol", 1., Prefixable::All, Prefixable::LongScale) }; typedef UnitNode::LuminousIntensityRepresentative LuminousIntensityRepresentative; @@ -631,6 +644,12 @@ public: static_assert(strings_equal(k_massRepresentatives[k_poundRepresentativeIndex].m_rootSymbol, "lb"), "Index for the Pound Representative is incorrect."); static constexpr int k_shortTonRepresentativeIndex = 5; static_assert(strings_equal(k_massRepresentatives[k_shortTonRepresentativeIndex].m_rootSymbol, "shtn"), "Index for the Short Ton Representative is incorrect."); + static constexpr int k_kelvinRepresentativeIndex = 0; + static_assert(strings_equal(k_temperatureRepresentatives[k_kelvinRepresentativeIndex].m_rootSymbol, "K"), "Index for the Kelvin Representative is incorrect."); + static constexpr int k_celsiusRepresentativeIndex = 1; + static_assert(strings_equal(k_temperatureRepresentatives[k_celsiusRepresentativeIndex].m_rootSymbol, "Cel"), "Index for the Celsius Representative is incorrect."); + static constexpr int k_fahrenheitRepresentativeIndex = 2; + static_assert(strings_equal(k_temperatureRepresentatives[k_fahrenheitRepresentativeIndex].m_rootSymbol, "Fah"), "Index for the Fahrenheit Representative is incorrect."); static constexpr int k_electronVoltRepresentativeIndex = 1; static_assert(strings_equal(k_energyRepresentatives[k_electronVoltRepresentativeIndex].m_rootSymbol, "eV"), "Index for the Electron Volt Representative is incorrect."); static constexpr int k_wattRepresentativeIndex = 0; @@ -657,6 +676,7 @@ public: static bool ShouldDisplayAdditionalOutputs(double value, Expression unit, Preferences::UnitFormat unitFormat); static int SetAdditionalExpressions(Expression units, double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext); static Expression BuildSplit(double value, const Unit * units, int length, ExpressionNode::ReductionContext reductionContext); + static Expression ConvertTemperatureUnits(Expression e, Unit unit, ExpressionNode::ReductionContext reductionContext); // Simplification Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); @@ -665,6 +685,8 @@ public: bool isBaseUnit() const { return node()->representative()->isBaseUnit() && node()->prefix() == node()->representative()->basePrefix(); } void chooseBestRepresentativeAndPrefix(double * value, double exponent, ExpressionNode::ReductionContext reductionContext, bool optimizePrefix); + const Representative * representative() const { return node()->representative(); } + private: UnitNode * node() const { return static_cast(Expression::node()); } Expression removeUnit(Expression * unit); diff --git a/poincare/src/unit.cpp b/poincare/src/unit.cpp index a62dea5a8..976f8ff1d 100644 --- a/poincare/src/unit.cpp +++ b/poincare/src/unit.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -54,6 +55,9 @@ constexpr const int Unit::k_ounceRepresentativeIndex, Unit::k_poundRepresentativeIndex, Unit::k_shortTonRepresentativeIndex, + Unit::k_kelvinRepresentativeIndex, + Unit::k_celsiusRepresentativeIndex, + Unit::k_fahrenheitRepresentativeIndex, Unit::k_electronVoltRepresentativeIndex, Unit::k_wattRepresentativeIndex, Unit::k_hectareRepresentativeIndex, @@ -476,6 +480,44 @@ int UnitNode::MassRepresentative::setAdditionalExpressions(double value, Express return 1; } +double UnitNode::TemperatureRepresentative::ConvertTemperatures(double value, const Representative * source, const Representative * target) { + assert(source->dimensionVector() == TemperatureRepresentative::Default().dimensionVector()); + assert(target->dimensionVector() == TemperatureRepresentative::Default().dimensionVector()); + if (source == target) { + return value; + } + constexpr double origin[] = {0, k_celsiusOrigin, k_fahrenheitOrigin}; + assert(sizeof(origin) == source->numberOfRepresentatives() * sizeof(double)); + double sourceOrigin = origin[source - source->representativesOfSameDimension()]; + double targetOrigin = origin[target - target->representativesOfSameDimension()]; + /* (T + origin) * ration converts T to Kelvin. + * T/ratio - origin converts T from Kelvin. */ + return (value + sourceOrigin) * source->ratio() / target->ratio() - targetOrigin; +} + +int UnitNode::TemperatureRepresentative::setAdditionalExpressions(double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) const { + assert(availableLength >= 2); + const Representative * celsius = TemperatureRepresentative::Default().representativesOfSameDimension() + Unit::k_celsiusRepresentativeIndex; + const Representative * fahrenheit = TemperatureRepresentative::Default().representativesOfSameDimension() + Unit::k_fahrenheitRepresentativeIndex; + const Representative * kelvin = TemperatureRepresentative::Default().representativesOfSameDimension() + Unit::k_kelvinRepresentativeIndex; + const Representative * targets[] = { + reductionContext.unitFormat() == Preferences::UnitFormat::Metric ? celsius : fahrenheit, + reductionContext.unitFormat() == Preferences::UnitFormat::Metric ? fahrenheit : celsius, + kelvin}; + int numberOfExpressionsSet = 0; + int numberOfTargets = sizeof(targets) / sizeof(Representative *); + for (int i = 0; i < numberOfTargets; i++) { + if (targets[i] == this) { + continue; + } + dest[numberOfExpressionsSet++] = Multiplication::Builder( + Float::Builder(TemperatureRepresentative::ConvertTemperatures(value, this, targets[i])), + Unit::Builder(targets[i], Prefix::EmptyPrefix())); + } + assert(numberOfExpressionsSet == 2); + return numberOfExpressionsSet; +} + int UnitNode::EnergyRepresentative::setAdditionalExpressions(double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) const { assert(availableLength >= 2); /* 1. Convert into Wh @@ -717,12 +759,12 @@ bool Unit::ShouldDisplayAdditionalOutputs(double value, Expression unit, Prefere UnitNode::Vector vector = UnitNode::Vector::FromBaseUnits(unit); const Representative * representative = Representative::RepresentativeForDimension(vector); return representative != nullptr - && ((unit.type() == ExpressionNode::Type::Unit && !unit.convert().isBaseUnit()) + && ((unit.type() == ExpressionNode::Type::Unit && !static_cast(unit).isBaseUnit()) || representative->hasAdditionalExpressions(value, unitFormat)); } int Unit::SetAdditionalExpressions(Expression units, double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) { - const Representative * representative = UnitNode::Representative::RepresentativeForDimension(UnitNode::Vector::FromBaseUnits(units)); + const Representative * representative = units.type() == ExpressionNode::Type::Unit ? static_cast(units).node()->representative() : UnitNode::Representative::RepresentativeForDimension(UnitNode::Vector::FromBaseUnits(units)); assert(representative); return representative->setAdditionalExpressions(value, dest, availableLength, reductionContext); } @@ -764,6 +806,28 @@ Expression Unit::BuildSplit(double value, const Unit * units, int length, Expres return res.squashUnaryHierarchyInPlace().shallowBeautify(keepUnitsContext); } +Expression Unit::ConvertTemperatureUnits(Expression e, Unit unit, ExpressionNode::ReductionContext reductionContext) { + const Representative * targetRepr = unit.representative(); + const Prefix * targetPrefix = unit.node()->prefix(); + assert(unit.representative()->dimensionVector() == TemperatureRepresentative::Default().dimensionVector()); + + Expression startUnit; + e = e.removeUnit(&startUnit); + if (startUnit.type() != ExpressionNode::Type::Unit) { + return Undefined::Builder(); + } + const Representative * startRepr = static_cast(startUnit).representative(); + if (startRepr->dimensionVector() != TemperatureRepresentative::Default().dimensionVector()) { + return Undefined::Builder(); + } + + const Prefix * startPrefix = static_cast(startUnit).node()->prefix(); + double value = e.approximateToScalar(reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit()); + return Multiplication::Builder( + Float::Builder(TemperatureRepresentative::ConvertTemperatures(value * std::pow(10., startPrefix->exponent()), startRepr, targetRepr) * std::pow(10., - targetPrefix->exponent())), + unit.clone()); +} + Expression Unit::shallowReduce(ExpressionNode::ReductionContext reductionContext) { if (reductionContext.unitConversion() == ExpressionNode::UnitConversion::None || isBaseUnit()) { @@ -772,6 +836,39 @@ Expression Unit::shallowReduce(ExpressionNode::ReductionContext reductionContext * here but not g */ return *this; } + + /* Handle temperatures : Celsius and Fahrenheit should not be used in + * calculations, only in conversions and results. + * These are the seven legal forms for writing non-kelvin temperatures : + * (1) _°C + * (2) _°C->_? + * (3) 123_°C + * (4) -123_°C + * (5) 123_°C->_K + * (6) -123_°C->_K + * (7) Right member of a unit convert - this is handled above, as + * UnitConversion is set to None in this case. */ + if (node()->representative()->dimensionVector() == TemperatureRepresentative::Default().dimensionVector()) { + Expression p = parent(); + if (p.isUninitialized() || p.type() == ExpressionNode::Type::UnitConvert) { + // Form (1) and (2) + return *this; + } + if (p.type() == ExpressionNode::Type::Multiplication && p.numberOfChildren() == 2) { + Expression pp = p.parent(); + if (pp.isUninitialized() || pp.type() == UnitNode::Type::UnitConvert) { + // Form (3) and (5) + return *this; + } + Expression ppp = pp.parent(); + if (pp.type() == UnitNode::Type::Opposite && (ppp.isUninitialized() || ppp.type() == UnitNode::Type::UnitConvert)) { + // Form (4) and (6) + return *this; + } + } + return replaceWithUndefinedInPlace(); + } + UnitNode * unitNode = node(); const Representative * representative = unitNode->representative(); const Prefix * prefix = unitNode->prefix(); @@ -789,7 +886,7 @@ Expression Unit::shallowReduce(ExpressionNode::ReductionContext reductionContext Expression Unit::shallowBeautify(ExpressionNode::ReductionContext reductionContext) { // Force Float(1) in front of an orphan Unit if (parent().isUninitialized() || parent().type() == ExpressionNode::Type::Opposite) { - Multiplication m = Multiplication::Builder(Float::Builder(1.0)); + Multiplication m = Multiplication::Builder(Float::Builder(1.)); replaceWithInPlace(m); m.addChildAtIndexInPlace(*this, 1, 1); return std::move(m); @@ -806,7 +903,11 @@ Expression Unit::removeUnit(Expression * unit) { void Unit::chooseBestRepresentativeAndPrefix(double * value, double exponent, ExpressionNode::ReductionContext reductionContext, bool optimizePrefix) { assert(exponent != 0.f); - if (std::isinf(*value) || *value == 0.0) { + + if ((std::isinf(*value) || (*value == 0.0 && node()->representative()->dimensionVector() != TemperatureRepresentative::Default().dimensionVector()))) { + /* Use the base unit to represent an infinite or null value, as all units + * are equivalent. + * This is not true for temperatures (0 K != 0°C != 0°F). */ node()->setRepresentative(node()->representative()->representativesOfSameDimension()); node()->setPrefix(node()->representative()->basePrefix()); return; diff --git a/poincare/src/unit_convert.cpp b/poincare/src/unit_convert.cpp index e38bef1fe..c2984c386 100644 --- a/poincare/src/unit_convert.cpp +++ b/poincare/src/unit_convert.cpp @@ -83,6 +83,17 @@ Expression UnitConvert::shallowBeautify(ExpressionNode::ReductionContext reducti return replaceWithUndefinedInPlace(); } + /* Handle temperatures, as converting between Kelvin, Celsius and Fahrenheit + * cannot be done with a division. */ + if (unit.type() == ExpressionNode::Type::Unit) { + Unit unitRef = static_cast(unit); + if (unitRef.representative()->dimensionVector() == Unit::TemperatureRepresentative::Default().dimensionVector()) { + Expression result = Unit::ConvertTemperatureUnits(childAtIndex(0), unitRef, reductionContext); + replaceWithInPlace(result); + return result; + } + } + // Divide the left member by the new unit Expression division = Division::Builder(childAtIndex(0), unit.clone()); division = division.deepReduce(reductionContext); diff --git a/poincare/test/expression_properties.cpp b/poincare/test/expression_properties.cpp index 4cf2a3b85..81eb65749 100644 --- a/poincare/test/expression_properties.cpp +++ b/poincare/test/expression_properties.cpp @@ -402,6 +402,16 @@ QUIZ_CASE(poincare_expression_additional_results) { assert_additional_results_compute_to("1×_kg", array7, 1, Imperial); assert_additional_results_compute_to("1×_kg", nullptr, 0, Metric); + // Temperatures + const char * array14[2] = {"-273.15×_Cel", "-459.67×_Fah"}; + assert_additional_results_compute_to("0×_K", array14, 2, Metric); + const char * array15[2] = {"-279.67×_Fah", "-173.15×_Cel"}; + assert_additional_results_compute_to("100×_K", array15, 2, Imperial); + const char * array16[2] = {"12.02×_Fah", "262.05×_K"}; + assert_additional_results_compute_to("-11.1×_Cel", array16, 2); + const char * array17[2] = {"-20×_Cel", "253.15×_K"}; + assert_additional_results_compute_to("-4×_Fah", array17, 2); + // Energy const char * array8[2] = {"1×_kW×_h", "2.246943ᴇ13×_TeV"}; assert_additional_results_compute_to("3.6×_MN_m", array8, 2); diff --git a/poincare/test/simplification.cpp b/poincare/test/simplification.cpp index 8fe5e021b..ee7dc196a 100644 --- a/poincare/test/simplification.cpp +++ b/poincare/test/simplification.cpp @@ -309,6 +309,8 @@ QUIZ_CASE(poincare_simplification_units) { assert_parsed_expression_simplify_to("_shtn", "1×_shtn", User, Radian, Imperial); assert_parsed_unit_simplify_to_with_prefixes(Unit::k_currentRepresentatives); assert_parsed_unit_simplify_to_with_prefixes(Unit::k_temperatureRepresentatives); + assert_parsed_expression_simplify_to("_Cel", "1×_Cel"); + assert_parsed_expression_simplify_to("_Fah", "1×_Fah"); assert_parsed_unit_simplify_to_with_prefixes(Unit::k_amountOfSubstanceRepresentatives); assert_parsed_unit_simplify_to_with_prefixes(Unit::k_luminousIntensityRepresentatives); assert_parsed_unit_simplify_to_with_prefixes(Unit::k_forceRepresentatives); @@ -344,6 +346,21 @@ QUIZ_CASE(poincare_simplification_units) { assert_parsed_expression_simplify_to("1_qt", "1×_qt", User, Radian, Imperial); assert_parsed_expression_simplify_to("1_qt", "946.352946×_cm^3"); + /* Tests for non-absolute units */ + assert_parsed_expression_simplify_to("273.15×_K→_Cel", "0×_Cel"); + assert_parsed_expression_simplify_to("0×_Cel", "0×_Cel"); + assert_parsed_expression_simplify_to("-32×_Fah", "-32×_Fah"); + assert_parsed_expression_simplify_to("273.16×_K", "273.16×_K"); + assert_parsed_expression_simplify_to("100×_Cel→_K", "373.15×_K"); + assert_parsed_expression_simplify_to("-100×_Cel→_K", "173.15×_K"); + assert_parsed_expression_simplify_to("_Cel+_Cel", Undefined::Name()); + assert_parsed_expression_simplify_to("_Cel+_Fah", Undefined::Name()); + assert_parsed_expression_simplify_to("_K+_Fah", Undefined::Name()); + assert_parsed_expression_simplify_to("2*20_Fah", Undefined::Name()); + assert_parsed_expression_simplify_to("_Cel^2", Undefined::Name()); + assert_parsed_expression_simplify_to("1/(-3_Cel)", Undefined::Name()); + assert_parsed_expression_simplify_to("-1×100×_Cel→_K", Undefined::Name()); + /* Unit sum/subtract */ assert_parsed_expression_simplify_to("_m+_m", "2×_m"); assert_parsed_expression_simplify_to("_m-_m", "0×_m"); @@ -1205,6 +1222,13 @@ QUIZ_CASE(poincare_simplification_unit_convert) { assert_parsed_expression_simplify_to("4→_km/_m", Undefined::Name()); assert_parsed_expression_simplify_to("3×_min→_s+1-1", Undefined::Name()); + assert_parsed_expression_simplify_to("0_K→_Cel", "-273.15×_Cel"); + assert_parsed_expression_simplify_to("0_Cel→_K", "273.15×_K"); + assert_parsed_expression_simplify_to("_Cel→_K", "274.15×_K"); + assert_parsed_expression_simplify_to("0_K→_Fah", "-459.67×_Fah"); + assert_parsed_expression_simplify_to("0_Fah→_K", "255.37222222222×_K"); + assert_parsed_expression_simplify_to("_Fah→_K", "255.92777777778×_K"); + assert_reduce("_m→a", Radian, Metric, Real); assert_reduce("_m→b", Radian, Metric, Real); assert_parsed_expression_simplify_to("1_km→a×b", Undefined::Name());