[poincare/unit] Restructure Unit

1. Information about a unit's dimension now uses inheritance.
   _m is an instance of DistanceAlias, which is derived from Alias.
   A UnitNode now keeps a pointer to an Alias and one to a Prefix.
   All aliases are still defined as constexpr.
   This cleans up a lot of the code used namely for computing the
   additional outputs in Calculation.

2. Instead of being defined with a string, each unit is described by its
   ratio with the base SI unit (ex: _L is 0.001 instead of "0.001_m^3").
   This greatly speeds up the calculations using units, as the algorithm
   to find the best unit used to parse the definition.

Change-Id: I4d6ed6ad4cb967026a3f01a335aec270066e2b9f
This commit is contained in:
Gabriel Ozouf
2020-07-31 16:00:07 +02:00
committed by Émilie Feral
parent e48e826536
commit 52dcd8e749
10 changed files with 1341 additions and 1495 deletions

View File

@@ -12,27 +12,6 @@ using namespace Shared;
namespace Calculation {
void storeAndSimplify(Expression e, Expression * dest, bool requireSimplification, bool canChangeUnitPrefix, ExpressionNode::ReductionContext reductionContext) {
assert(!e.isUninitialized());
*dest = e;
if (requireSimplification) {
*dest = dest->simplify(reductionContext);
}
if (canChangeUnitPrefix) {
Expression newUnits;
/* If the expression has already been simplified, we do not want to reduce
* it further, as this would units introduced by a UnitConvert node back to
* SI units. */
if (!requireSimplification) {
// Reduce to be able to removeUnit
*dest = dest->reduce(reductionContext);
}
*dest = dest->removeUnit(&newUnits);
double value = Shared::PoincareHelpers::ApproximateToScalar<double>(*dest, App::app()->localContext());
*dest = Multiplication::Builder(Number::FloatNumber(value), newUnits);
}
}
void UnitListController::setExpression(Poincare::Expression e) {
ExpressionsListController::setExpression(e);
assert(!m_expression.isUninitialized());
@@ -44,7 +23,6 @@ void UnitListController::setExpression(Poincare::Expression e) {
expressions[i] = Expression();
}
size_t numberOfExpressions = 0;
/* 1. First rows: miscellaneous classic units for some dimensions, in both
* metric and imperial units. */
Expression copy = m_expression.clone();
@@ -52,52 +30,15 @@ void UnitListController::setExpression(Poincare::Expression e) {
// Reduce to be able to recognize units
PoincareHelpers::Reduce(&copy, App::app()->localContext(), ExpressionNode::ReductionTarget::User);
copy = copy.removeUnit(&units);
Preferences::UnitFormat chosenFormat = GlobalPreferences::sharedGlobalPreferences()->unitFormat();
Preferences::UnitFormat otherFormat = (chosenFormat == Preferences::UnitFormat::Metric) ? Preferences::UnitFormat::Imperial : Preferences::UnitFormat::Metric;
ExpressionNode::ReductionContext chosenFormatContext(
double value = Shared::PoincareHelpers::ApproximateToScalar<double>(copy, App::app()->localContext());
ExpressionNode::ReductionContext reductionContext(
App::app()->localContext(),
Preferences::sharedPreferences()->complexFormat(),
Preferences::sharedPreferences()->angleUnit(),
chosenFormat,
GlobalPreferences::sharedGlobalPreferences()->unitFormat(),
ExpressionNode::ReductionTarget::User,
ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined);
ExpressionNode::ReductionContext otherFormatContext(
App::app()->localContext(),
Preferences::sharedPreferences()->complexFormat(),
Preferences::sharedPreferences()->angleUnit(),
otherFormat,
ExpressionNode::ReductionTarget::User,
ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined);
if (Unit::IsSISpeed(units)) {
// 1.a. Turn speed into km/h or mi/h
storeAndSimplify(Unit::StandardSpeedConversion(m_expression.clone(), chosenFormatContext), &expressions[numberOfExpressions++], true, false, chosenFormatContext);
storeAndSimplify(Unit::StandardSpeedConversion(m_expression.clone(), otherFormatContext), &expressions[numberOfExpressions++], true, false, otherFormatContext);
} else if (Unit::IsSIVolume(units)) {
// 1.b. Turn volume into L or _gal + _cp + _floz
storeAndSimplify(Unit::StandardVolumeConversion(m_expression.clone(), chosenFormatContext), &expressions[numberOfExpressions++], chosenFormat == Preferences::UnitFormat::Metric, chosenFormat == Preferences::UnitFormat::Metric, chosenFormatContext);
storeAndSimplify(Unit::StandardVolumeConversion(m_expression.clone(), otherFormatContext), &expressions[numberOfExpressions++], otherFormat == Preferences::UnitFormat::Metric, otherFormat == Preferences::UnitFormat::Metric, otherFormatContext);
} else if (Unit::IsSIEnergy(units)) {
// 1.c. Turn energy into Wh
storeAndSimplify(UnitConvert::Builder(m_expression.clone(), Multiplication::Builder(Unit::Watt(), Unit::Hour())), &expressions[numberOfExpressions++], true, true, chosenFormatContext);
storeAndSimplify(UnitConvert::Builder(m_expression.clone(), Unit::ElectronVolt()), &expressions[numberOfExpressions++], true, true, chosenFormatContext);
} else if (Unit::IsSITime(units)) {
// 1.d. Turn time into ? year + ? month + ? day + ? h + ? min + ? s
double value = Shared::PoincareHelpers::ApproximateToScalar<double>(copy, App::app()->localContext());
expressions[numberOfExpressions++] = Unit::BuildTimeSplit(value, App::app()->localContext());
} else if (Unit::IsSIDistance(units)) {
// 1.e. Turn distance into _?m or _mi + _yd + _ft + _in
storeAndSimplify(Unit::StandardDistanceConversion(m_expression.clone(), chosenFormatContext), &expressions[numberOfExpressions++], chosenFormat == Preferences::UnitFormat::Metric, chosenFormat == Preferences::UnitFormat::Metric, chosenFormatContext);
storeAndSimplify(Unit::StandardDistanceConversion(m_expression.clone(), otherFormatContext), &expressions[numberOfExpressions++], otherFormat == Preferences::UnitFormat::Metric, otherFormat == Preferences::UnitFormat::Metric, otherFormatContext);
} else if (Unit::IsSISurface(units)) {
// 1.f. Turn surface into hectares or acres
storeAndSimplify(Unit::StandardSurfaceConversion(m_expression.clone(), chosenFormatContext), &expressions[numberOfExpressions++], true, false, chosenFormatContext);
storeAndSimplify(Unit::StandardSurfaceConversion(m_expression.clone(), otherFormatContext), &expressions[numberOfExpressions++], true, false, otherFormatContext);
} else if (Unit::IsSIMass(units)) {
// 1.g. Turn mass into _?g and _lb + _oz
storeAndSimplify(Unit::StandardMassConversion(m_expression.clone(), chosenFormatContext), &expressions[numberOfExpressions++], chosenFormat == Preferences::UnitFormat::Metric, chosenFormat == Preferences::UnitFormat::Metric, chosenFormatContext);
storeAndSimplify(Unit::StandardMassConversion(m_expression.clone(), otherFormatContext), &expressions[numberOfExpressions++], otherFormat == Preferences::UnitFormat::Metric, otherFormat == Preferences::UnitFormat::Metric, otherFormatContext);
}
int numberOfExpressions = Unit::SetAdditionalExpressions(units, value, expressions, k_maxNumberOfRows, reductionContext);
// 2. SI units only
assert(numberOfExpressions < k_maxNumberOfRows - 1);
@@ -112,14 +53,14 @@ void UnitListController::setExpression(Poincare::Expression e) {
int currentExpressionIndex = 1;
while (currentExpressionIndex < numberOfExpressions) {
bool duplicateFound = false;
for (size_t i = 0; i < currentExpressionIndex + 1; i++) {
for (int i = 0; i < currentExpressionIndex + 1; i++) {
// Compare the currentExpression to all previous expressions and to m_expression
Expression comparedExpression = i == currentExpressionIndex ? reduceExpression : expressions[i];
assert(!comparedExpression.isUninitialized());
if (comparedExpression.isIdenticalTo(expressions[currentExpressionIndex])) {
numberOfExpressions--;
// Shift next expressions
for (size_t j = currentExpressionIndex; j < numberOfExpressions; j++) {
for (int j = currentExpressionIndex; j < numberOfExpressions; j++) {
expressions[j] = expressions[j+1];
}
// Remove last expression

View File

@@ -254,32 +254,13 @@ Calculation::AdditionalInformationType Calculation::additionalInformationType(Co
}
if (o.hasUnit()) {
Expression unit;
PoincareHelpers::Reduce(&o,
App::app()->localContext(),
ExpressionNode::ReductionTarget::User,
ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined,
ExpressionNode::UnitConversion::None);
/* FIXME : When this method is accessed via leaving the additional outputs,
* ie via a press on BACK, the reduction is interrupted, and removeUnit
* goes badly.*/
PoincareHelpers::Reduce(&o, App::app()->localContext(), ExpressionNode::ReductionTarget::User, ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined);
o = o.removeUnit(&unit);
// There might be no unit in the end, if the reduction was interrupted.
if (!unit.isUninitialized()) {
if (Unit::IsSI(unit)) {
if (Unit::IsSISpeed(unit) || Unit::IsSIVolume(unit) || Unit::IsSIEnergy(unit)) {
/* All these units will provide misc. classic representatives in
* addition to the SI unit in additional information. */
return AdditionalInformationType::Unit;
}
if (Unit::IsSITime(unit)) {
/* If the number of seconds is above 60s, we can write it in the form
* of an addition: 23_min + 12_s for instance. */
double value = Shared::PoincareHelpers::ApproximateToScalar<double>(o, App::app()->localContext());
if (value > Unit::SecondsPerMinute) {
return AdditionalInformationType::Unit;
}
}
return AdditionalInformationType::None;
}
return AdditionalInformationType::Unit;
}
double value = PoincareHelpers::ApproximateToScalar<double>(o, App::app()->localContext());
return (Unit::ShouldDisplayAdditionalOutputs(value, unit)) ? AdditionalInformationType::Unit : AdditionalInformationType::None;
}
if (o.isBasedIntegerCappedBy(k_maximalIntegerWithAdditionalInformation)) {
return AdditionalInformationType::Integer;

File diff suppressed because it is too large Load Diff

View File

@@ -322,14 +322,14 @@ Expression Multiplication::shallowReduce(ExpressionNode::ReductionContext reduct
}
static bool CanSimplifyUnitProduct(
const Unit::Dimension::Vector<int> &unitsExponents, size_t &unitsSupportSize,
const Unit::Dimension::Vector<int8_t> *entryUnitExponents, int8_t entryUnitExponent,
int8_t &bestUnitExponent, Unit::Dimension::Vector<int> &bestRemainderExponents, size_t &bestRemainderSupportSize) {
const UnitNode::Vector<int> &unitsExponents, size_t &unitsSupportSize,
const UnitNode::Vector<int> * entryUnitExponents, int entryUnitExponent,
int8_t &bestUnitExponent, UnitNode::Vector<int> &bestRemainderExponents, size_t &bestRemainderSupportSize) {
/* This function tries to simplify a Unit product (given as the
* 'unitsExponents' int array), by applying a given operation. If the
* result of the operation is simpler, 'bestUnit' and
* 'bestRemainder' are updated accordingly. */
Unit::Dimension::Vector<int> simplifiedExponents;
UnitNode::Vector<int> simplifiedExponents;
#if 0
/* In the current algorithm, simplification is attempted using derived units
@@ -369,7 +369,7 @@ static bool CanSimplifyUnitProduct(
n -= step;
#endif
for (size_t i = 0; i < Unit::NumberOfBaseUnits; i++) {
for (size_t i = 0; i < UnitNode::k_numberOfBaseUnits; i++) {
#if 0
// Undo last step as it did not reduce the norm
simplifiedExponents.setCoefficientAtIndex(i, simplifiedExponents.coefficientAtIndex(i) + entryUnitExponent * step * entryUnitExponents->coefficientAtIndex(i));
@@ -444,26 +444,27 @@ Expression Multiplication::shallowBeautify(ExpressionNode::ReductionContext redu
* representation of units with base units and integer exponents.
* It cause no problem because once the best derived units are found,
* units is divided then multiplied by them. */
Unit::Dimension::Vector<int> unitsExponents = Unit::Dimension::Vector<int>::FromBaseUnits(units);
UnitNode::Vector<int> unitsExponents = UnitNode::Vector<int>::FromBaseUnits(units);
size_t unitsSupportSize = unitsExponents.supportSize();
Unit::Dimension::Vector<int> bestRemainderExponents;
UnitNode::Vector<int> bestRemainderExponents;
size_t bestRemainderSupportSize;
while (unitsSupportSize > 1) {
const Unit::Dimension * bestDim = nullptr;
const UnitNode::Representative * bestDim = nullptr;
int8_t bestUnitExponent = 0;
// Look up in the table of derived units.
for (const Unit::Dimension * dim = Unit::DimensionTable + Unit::NumberOfBaseUnits; dim < Unit::DimensionTableUpperBound; dim++) {
const Unit::Dimension::Vector<int8_t> * entryUnitExponents = dim->vector();
for (int i = UnitNode::k_numberOfBaseUnits; i < UnitNode::Representative::k_numberOfDimensions - 1; i++) {
const UnitNode::Representative * dim = UnitNode::Representative::DefaultRepresentatives()[i];
const UnitNode::Vector<int> entryUnitExponents = dim->dimensionVector();
// A simplification is tried by either multiplying or dividing
if (CanSimplifyUnitProduct(
unitsExponents, unitsSupportSize,
entryUnitExponents, 1,
&entryUnitExponents, 1,
bestUnitExponent, bestRemainderExponents, bestRemainderSupportSize
)
||
CanSimplifyUnitProduct(
unitsExponents, unitsSupportSize,
entryUnitExponents, -1,
&entryUnitExponents, -1,
bestUnitExponent, bestRemainderExponents, bestRemainderSupportSize
))
{
@@ -477,7 +478,7 @@ Expression Multiplication::shallowBeautify(ExpressionNode::ReductionContext redu
break;
}
// Build and add the best derived unit
Expression derivedUnit = Unit::Builder(bestDim, bestDim->stdRepresentative(), bestDim->stdRepresentativePrefix());
Expression derivedUnit = Unit::Builder(bestDim->representativesOfSameDimension(), bestDim->basePrefix());
#if 0
if (bestUnitExponent != 1) {
@@ -533,7 +534,7 @@ Expression Multiplication::shallowBeautify(ExpressionNode::ReductionContext redu
} else {
if (unitConversionMode == ExpressionNode::UnitConversion::Default) {
// Find the right unit prefix
Unit::ChooseBestRepresentativeAndPrefixForValue(&units, &value, reductionContext);
Unit::ChooseBestRepresentativeAndPrefixForValue(units, &value, reductionContext);
}
// Build final Expression
result = Multiplication::Builder(Number::FloatNumber(value), units);

View File

@@ -347,13 +347,11 @@ void Parser::parseConstant(Expression & leftHandSide, Token::Type stoppingType)
void Parser::parseUnit(Expression & leftHandSide, Token::Type stoppingType) {
assert(leftHandSide.isUninitialized());
const Unit::Dimension * unitDimension = nullptr;
const Unit::Representative * unitRepresentative = nullptr;
const Unit::Prefix * unitPrefix = nullptr; leftHandSide = Constant::Builder(m_currentToken.codePoint());
if (Unit::CanParse(m_currentToken.text(), m_currentToken.length(),
&unitDimension, &unitRepresentative, &unitPrefix))
{
leftHandSide = Unit::Builder(unitDimension, unitRepresentative, unitPrefix);
const Unit::Prefix * unitPrefix = nullptr;
leftHandSide = Constant::Builder(m_currentToken.codePoint());
if (Unit::CanParse(m_currentToken.text(), m_currentToken.length(), &unitRepresentative, &unitPrefix)) {
leftHandSide = Unit::Builder(unitRepresentative, unitPrefix);
} else {
m_status = Status::Error; // Unit does not exist
return;

File diff suppressed because it is too large Load Diff

View File

@@ -75,18 +75,18 @@ QUIZ_CASE(poincare_expression_rational_constructor) {
}
QUIZ_CASE(poincare_expression_unit_constructor) {
Unit u = Unit::Second();
Unit u = Unit::Builder(Unit::k_timeRepresentatives, Unit::Prefix::EmptyPrefix());
assert_expression_serialize_to(u, "_s");
u = Unit::Hour();
u = Unit::Builder(Unit::k_timeRepresentatives + 2, Unit::Prefix::EmptyPrefix());
assert_expression_serialize_to(u, "_h");
u = Unit::Kilometer();
u = Unit::Builder(Unit::k_distanceRepresentatives, Unit::k_prefixes + 9);
assert_expression_serialize_to(u, "_km");
u = Unit::Liter();
u = Unit::Builder(Unit::k_volumeRepresentatives, Unit::Prefix::EmptyPrefix());
assert_expression_serialize_to(u, "_L");
u = Unit::Watt();
u = Unit::Builder(Unit::k_powerRepresentatives, Unit::Prefix::EmptyPrefix());
assert_expression_serialize_to(u, "_W");
}

View File

@@ -394,51 +394,62 @@ QUIZ_CASE(poincare_properties_remove_unit) {
assert_reduced_expression_unit_is("_L^2×3×_s", "_m^6×_s");
}
void assert_seconds_split_to(double totalSeconds, const char * splittedTime, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) {
Expression time = Unit::BuildTimeSplit(totalSeconds, context);
constexpr static int bufferSize = 100;
char buffer[bufferSize];
time.serialize(buffer, bufferSize, DecimalMode);
quiz_assert_print_if_failure(strcmp(buffer, splittedTime) == 0, splittedTime);
}
Expression extract_unit(const char * expression) {
void assert_additional_results_compute_to(const char * expression, const char * * results, int length) {
Shared::GlobalContext globalContext;
ExpressionNode::ReductionContext reductionContext = ExpressionNode::ReductionContext(&globalContext, Cartesian, Degree, Metric, User, ReplaceAllSymbolsWithUndefined, NoUnitConversion);
constexpr int maxNumberOfResults = 5;
assert(length <= maxNumberOfResults);
Expression additional[maxNumberOfResults];
ExpressionNode::ReductionContext reductionContext = ExpressionNode::ReductionContext(&globalContext, Cartesian, Degree, Metric, User, ReplaceAllSymbolsWithUndefined, DefaultUnitConversion);
Expression e = parse_expression(expression, &globalContext, false).reduce(reductionContext);
Expression unit;
e.removeUnit(&unit);
return unit;
Expression units;
e = e.removeUnit(&units);
double value = e.approximateToScalar<double>(&globalContext, Cartesian, Degree);
if (!Unit::ShouldDisplayAdditionalOutputs(value, units)) {
quiz_assert(length == 0);
return;
}
const int numberOfResults = Unit::SetAdditionalExpressions(units, value, additional, maxNumberOfResults, reductionContext);
quiz_assert(numberOfResults == length);
for (int i = 0; i < length; i++) {
assert_expression_serialize_to(additional[i], results[i], Preferences::PrintFloatMode::Decimal);
}
}
QUIZ_CASE(poincare_expression_unit_helper) {
// 1. Time
Expression s = extract_unit("_s");
quiz_assert(s.type() == ExpressionNode::Type::Unit && static_cast<Unit &>(s).isSecond());
quiz_assert(!static_cast<Unit &>(s).isMeter());
QUIZ_CASE(poincare_expression_additional_results) {
// Time
assert_additional_results_compute_to("3×_s", nullptr, 0);
const char * array1[1] = {"1×_min+1×_s"};
assert_additional_results_compute_to("61×_s", array1, 1);
const char * array2[1] = {"1×_day+10×_h+17×_min+36×_s"};
assert_additional_results_compute_to("123456×_s", array2, 1);
const char * array3[1] = {"7×_day"};
assert_additional_results_compute_to("1×_week", array3, 1);
Shared::GlobalContext globalContext;
assert_seconds_split_to(1234567890, "39×_year+1×_month+13×_day+19×_h+1×_min+30×_s", &globalContext, Cartesian, Degree);
assert_seconds_split_to(-122, "-2×_min-2×_s", &globalContext, Cartesian, Degree);
// Distance
const char * array4[1] = {"19×_mi+853×_yd+1×_ft+7×_in"};
assert_additional_results_compute_to("1234567×_in", array4, 1);
const char * array5[1] = {"1×_yd+7.700787×_in"};
assert_additional_results_compute_to("1.11×_m", array5, 1);
// 2. Speed
Expression meterPerSecond = extract_unit("_m×_s^-1");
quiz_assert(Unit::IsSISpeed(meterPerSecond));
// Masses
const char * array6[1] = {"1×_shtn+240×_lb"};
assert_additional_results_compute_to("1×_lgtn", array6, 1);
const char * array7[1] = {"2×_lb+3.273962×_oz"};
assert_additional_results_compute_to("1×_kg", array7, 1);
// 3. Volume
Expression meter3 = extract_unit("_m^3");
quiz_assert(Unit::IsSIVolume(meter3));
// Energy
const char * array8[2] = {"1×_kW×_h", "2.246943ᴇ13×_TeV"};
assert_additional_results_compute_to("3.6×_MN_m", array8, 2);
// 4. Energy
Expression kilogramMeter2PerSecond2 = extract_unit("_kg×_m^2×_s^-2");
quiz_assert(Unit::IsSIEnergy(kilogramMeter2PerSecond2));
Expression kilogramMeter3PerSecond2 = extract_unit("_kg×_m^3×_s^-2");
quiz_assert(!Unit::IsSIEnergy(kilogramMeter3PerSecond2));
// Volume
const char * array9[2] = {"1000×_L", "264×_gal+2×_cp+6.022702×_floz"};
assert_additional_results_compute_to("1×_m^3", array9, 2);
const char * array10[2] = {"182.5426×_L", "48×_gal+3×_cp+4.5×_floz"};
assert_additional_results_compute_to("12345×_Tbsp", array10, 2);
// 5. International System
quiz_assert(Unit::IsSI(kilogramMeter2PerSecond2));
quiz_assert(Unit::IsSI(meter3));
quiz_assert(Unit::IsSI(meterPerSecond));
Expression joule = extract_unit("_J");
quiz_assert(!Unit::IsSI(joule));
// Speed
const char * array11[2] = {"3.6×_km×_h^\x12-1\x13", "2.236936×_mi×_h^\x12-1\x13"};
assert_additional_results_compute_to("1×_m/_s", array11, 2);
}

View File

@@ -286,20 +286,19 @@ QUIZ_CASE(poincare_parsing_matrices) {
QUIZ_CASE(poincare_parsing_units) {
// Units
for (const Unit::Dimension * dim = Unit::DimensionTable; dim < Unit::DimensionTableUpperBound; dim++) {
for (const Unit::Representative * rep = dim->stdRepresentative(); rep < dim->representativesUpperBound(); rep++) {
for (int i = 0; i < Unit::Representative::k_numberOfDimensions; i++) {
const Unit::Representative * dim = Unit::Representative::DefaultRepresentatives()[i];
for (int j = 0; j < dim->numberOfRepresentatives(); j++) {
const Unit::Representative * rep = dim->representativesOfSameDimension() + j;
static constexpr size_t bufferSize = 10;
char buffer[bufferSize];
Unit::Builder(dim, rep, &Unit::EmptyPrefix).serialize(buffer, bufferSize, Preferences::PrintFloatMode::Decimal, Preferences::VeryShortNumberOfSignificantDigits);
Unit::Builder(rep, Unit::Prefix::EmptyPrefix()).serialize(buffer, bufferSize, Preferences::PrintFloatMode::Decimal, Preferences::VeryShortNumberOfSignificantDigits);
Expression unit = parse_expression(buffer, nullptr, false);
quiz_assert_print_if_failure(unit.type() == ExpressionNode::Type::Unit, "Should be parsed as a Unit");
if (rep->isPrefixable()) {
/* ton is only prefixable by positive prefixes */
bool isTon = strcmp("t", rep->rootSymbol()) == 0;
size_t numberOfPrefixes = ((isTon) ? sizeof(Unit::PositiveLongScalePrefixes) : sizeof(Unit::AllPrefixes))/sizeof(Unit::Prefix *);
for (size_t i = 0; i < numberOfPrefixes; i++) {
const Unit::Prefix * pre = (isTon) ? Unit::PositiveLongScalePrefixes[i] : Unit::AllPrefixes[i];
Unit::Builder(dim, rep, pre).serialize(buffer, bufferSize, Preferences::PrintFloatMode::Decimal, Preferences::VeryShortNumberOfSignificantDigits);
if (rep->isInputPrefixable()) {
for (size_t i = 0; i < Unit::Prefix::k_numberOfPrefixes; i++) {
const Unit::Prefix * pre = Unit::Prefix::Prefixes();
Unit::Builder(rep, pre).serialize(buffer, bufferSize, Preferences::PrintFloatMode::Decimal, Preferences::VeryShortNumberOfSignificantDigits);
Expression unit = parse_expression(buffer, nullptr, false);
quiz_assert_print_if_failure(unit.type() == ExpressionNode::Type::Unit, "Should be parsed as a Unit");
}

View File

@@ -196,6 +196,26 @@ QUIZ_CASE(poincare_simplification_multiplication) {
assert_parsed_expression_simplify_to("0*[[1,0][0,1]]^500", "0×[[1,0][0,1]]^500");
}
void assert_parsed_unit_simplify_to_with_prefixes(const Unit::Representative * representative) {
int numberOfPrefixes;
const Unit::Prefix * prefixes;
static constexpr size_t bufferSize = 12;
char buffer[bufferSize] = "1×";
if (representative->isOutputPrefixable()) {
numberOfPrefixes = Unit::Prefix::k_numberOfPrefixes;
prefixes = Unit::k_prefixes;
} else {
numberOfPrefixes = 1;
prefixes = Unit::Prefix::EmptyPrefix();
}
for (int i = 0; i < numberOfPrefixes; i++) {
if (representative->canPrefix(prefixes + i, true) && representative->canPrefix(prefixes + i, false)) {
Unit::Builder(representative, prefixes + i).serialize(buffer+strlen("1×"), bufferSize-strlen("1×"), Preferences::PrintFloatMode::Decimal, Preferences::VeryShortNumberOfSignificantDigits);
assert_parsed_expression_simplify_to(buffer, buffer);
}
}
}
QUIZ_CASE(poincare_simplification_units) {
/* SI base units */
assert_parsed_expression_simplify_to("_s", "1×_s");
@@ -258,10 +278,9 @@ QUIZ_CASE(poincare_simplification_units) {
assert_parsed_expression_simplify_to("_mol×_s^-1", "1×_kat");
/* Displayed order of magnitude */
assert_parsed_expression_simplify_to("1_t", "1×_t");
assert_parsed_expression_simplify_to("100_kg", "100×_kg");
assert_parsed_expression_simplify_to("1_min", "1×_min");
assert_parsed_expression_simplify_to("0.1_m", "100×_mm");
assert_parsed_expression_simplify_to("0.1_m", "1×_dm");
assert_parsed_expression_simplify_to("180_MΩ", "180×_MΩ");
assert_parsed_expression_simplify_to("180_MH", "180×_MH");
@@ -269,31 +288,60 @@ QUIZ_CASE(poincare_simplification_units) {
* Some symbols are however excluded:
* - At present, some units will not appear as simplification output:
* t, Hz, S, ha, L. These exceptions are tested below. */
for (const Unit::Dimension * dim = Unit::DimensionTable; dim < Unit::DimensionTableUpperBound; dim++) {
for (const Unit::Representative * rep = dim->stdRepresentative(); rep < dim->representativesUpperBound(); rep++) {
if (strcmp(rep->rootSymbol(), "Hz") == 0 || strcmp(rep->rootSymbol(), "S") == 0 || strcmp(rep->rootSymbol(), "ha") == 0 || strcmp(rep->rootSymbol(), "L") == 0 || strcmp(rep->rootSymbol(), "yd") || strcmp(rep->rootSymbol(), "tsp") || strcmp(rep->rootSymbol(), "Tbsp") || strcmp(rep->rootSymbol(), "pt") || strcmp(rep->rootSymbol(), "qt") || strcmp(rep->rootSymbol(), "lgtn")) {
continue;
}
static constexpr size_t bufferSize = 12;
char buffer[bufferSize] = "1×";
Unit::Builder(dim, rep, &Unit::EmptyPrefix).serialize(buffer+strlen("1×"), bufferSize-strlen("1×"), Preferences::PrintFloatMode::Decimal, Preferences::VeryShortNumberOfSignificantDigits);
assert_parsed_expression_simplify_to(buffer, buffer, User, Radian, (rep->canOutputInSystem(Metric) ? Metric : Imperial));
if (rep->isPrefixable()) {
for (size_t i = 0; i < rep->outputPrefixesLength(); i++) {
const Unit::Prefix * pre = rep->outputPrefixes()[i];
Unit::Builder(dim, rep, pre).serialize(buffer+strlen("1×"), bufferSize-strlen("1×"), Preferences::PrintFloatMode::Decimal, Preferences::VeryShortNumberOfSignificantDigits);
assert_parsed_expression_simplify_to(buffer, buffer);
}
}
}
}
assert_parsed_expression_simplify_to("_s", "1×_s");
assert_parsed_expression_simplify_to("_min", "1×_min");
assert_parsed_expression_simplify_to("_h", "1×_h");
assert_parsed_expression_simplify_to("_day", "1×_day");
assert_parsed_expression_simplify_to("_week", "1×_week");
assert_parsed_expression_simplify_to("_month", "1×_month");
assert_parsed_expression_simplify_to("_year", "1×_year");
assert_parsed_unit_simplify_to_with_prefixes(Unit::k_distanceRepresentatives);
assert_parsed_expression_simplify_to("_au", "1×_au");
assert_parsed_expression_simplify_to("_ly", "1×_ly");
assert_parsed_expression_simplify_to("_pc", "1×_pc");
assert_parsed_expression_simplify_to("_in", "1×_in", User, Radian, Imperial);
assert_parsed_expression_simplify_to("_ft", "1×_ft", User, Radian, Imperial);
assert_parsed_expression_simplify_to("_yd", "1×_yd", User, Radian, Imperial);
assert_parsed_expression_simplify_to("_mi", "1×_mi", User, Radian, Imperial);
assert_parsed_unit_simplify_to_with_prefixes(Unit::k_massRepresentatives);
assert_parsed_expression_simplify_to("_oz", "1×_oz", User, Radian, Imperial);
assert_parsed_expression_simplify_to("_lb", "1×_lb", User, Radian, Imperial);
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_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);
assert_parsed_unit_simplify_to_with_prefixes(Unit::k_pressureRepresentatives);
assert_parsed_unit_simplify_to_with_prefixes(Unit::k_pressureRepresentatives + 1);
assert_parsed_expression_simplify_to("_atm", "1×_atm");
assert_parsed_unit_simplify_to_with_prefixes(Unit::k_energyRepresentatives);
assert_parsed_unit_simplify_to_with_prefixes(Unit::k_energyRepresentatives + 1);
assert_parsed_unit_simplify_to_with_prefixes(Unit::k_powerRepresentatives);
assert_parsed_unit_simplify_to_with_prefixes(Unit::k_electricChargeRepresentatives);
assert_parsed_unit_simplify_to_with_prefixes(Unit::k_electricPotentialRepresentatives);
assert_parsed_unit_simplify_to_with_prefixes(Unit::k_electricCapacitanceRepresentatives);
assert_parsed_unit_simplify_to_with_prefixes(Unit::k_electricResistanceRepresentatives);
assert_parsed_unit_simplify_to_with_prefixes(Unit::k_magneticFieldRepresentatives);
assert_parsed_unit_simplify_to_with_prefixes(Unit::k_magneticFluxRepresentatives);
assert_parsed_unit_simplify_to_with_prefixes(Unit::k_inductanceRepresentatives);
assert_parsed_unit_simplify_to_with_prefixes(Unit::k_catalyticActivityRepresentatives);
/* Units that do not appear as output yet */
/* Units that do not appear as output */
assert_parsed_expression_simplify_to("_t", "1000×_kg");
assert_parsed_expression_simplify_to("_Hz", "1×_s^\u0012-1\u0013");
assert_parsed_expression_simplify_to("_S", "1×_Ω^\u0012-1\u0013");
assert_parsed_expression_simplify_to("_L", "0.001×_m^3");
assert_parsed_expression_simplify_to("_L", "1×_dm^3");
assert_parsed_expression_simplify_to("_ha", "10000×_m^2");
/* Imperial units */
assert_parsed_expression_simplify_to("_lgtn", "1016.0469088×_kg");
assert_parsed_expression_simplify_to("_lgtn", "1.12×_shtn", User, Radian, Imperial);
assert_parsed_expression_simplify_to("_in", "2.54×_cm");
assert_parsed_expression_simplify_to("_in", "1×_in", User, Radian, Imperial);
assert_parsed_expression_simplify_to("_ft", "1×_ft", User, Radian, Imperial);
assert_parsed_expression_simplify_to("_yd", "1×_yd", User, Radian, Imperial);
/* Unit sum/subtract */
assert_parsed_expression_simplify_to("_m+_m", "2×_m");
assert_parsed_expression_simplify_to("_m-_m", "0×_m");
@@ -339,11 +387,12 @@ QUIZ_CASE(poincare_simplification_units) {
assert_parsed_expression_simplify_to("_A^2×_s^4×_kg^(-1)×_m^(-3)", "1×_F×_m^\u0012-1\u0013"); // Vacuum magnetic permeability 𝝴0
assert_parsed_expression_simplify_to("_kg×_s^(-3)×_K^(-4)", "1×_K^\u0012-4\u0013×_kg×_s^\u0012-3\u0013"); // StefanBoltzmann constant _W×_m^-2×_K^-4
/* Keep units for 0, infinity float results, Remove unit for undefined
/* Keep SI units for 0, infinity float results, Remove unit for undefined
* expression */
assert_parsed_expression_simplify_to("0×_s", "0×_s");
assert_parsed_expression_simplify_to("0×_tsp", "0×_m^3");
assert_parsed_expression_simplify_to("inf×_s", "inf×_s");
assert_parsed_expression_simplify_to("-inf×_s", "-inf×_s");
assert_parsed_expression_simplify_to("-inf×_oz", "-inf×_kg");
assert_parsed_expression_simplify_to("2_s+3_s-5_s", "0×_s");
assert_parsed_expression_simplify_to("normcdf(0,20,3)×_s", "13.083978345207×_ps");
assert_parsed_expression_simplify_to("log(0)×_s", "-inf×_s");
@@ -1426,7 +1475,7 @@ QUIZ_CASE(poincare_simplification_user_function_with_convert) {
e = Store::Builder(
UnitConvert::Builder(
Rational::Builder(0),
Unit::Second()),
Unit::Builder(&Unit::k_timeRepresentatives[Unit::k_secondRepresentativeIndex], Unit::Prefix::EmptyPrefix())),
Function::Builder(
"f", 1,
Symbol::Builder('x')));