mirror of
https://github.com/UpsilonNumworks/Upsilon.git
synced 2026-01-18 16:27:34 +01:00
[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:
committed by
Émilie Feral
parent
e48e826536
commit
52dcd8e749
@@ -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(©, 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
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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"); // Stefan–Boltzmann 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')));
|
||||
|
||||
Reference in New Issue
Block a user