Base: Units: refactor

Refactor Units making it constexpr, immutable, with repetition reduced.
Separate data from code.

Constexpr constructed units allow constructing predefined Quantities
from predefined unit types.
This commit is contained in:
bofdahof
2025-01-01 18:17:00 +10:00
committed by Ladislav Michl
parent f8d2789a43
commit 847e2f5c85
8 changed files with 729 additions and 1155 deletions

View File

@@ -3,191 +3,154 @@
#include <Base/Exception.h>
// NOLINTBEGIN
TEST(Unit, TestString)
using namespace Base;
TEST(Unit, string_simple_numerator_no_denominator)
{
auto toString = [](const Base::Unit& unit) {
return unit.getString();
};
EXPECT_EQ(toString(Base::Unit(0, 0, 0, 0, 0, 0, 0, 0)), "");
EXPECT_EQ(toString(Base::Unit(1, 0, 0, 0, 0, 0, 0, 0)), "mm");
EXPECT_EQ(toString(Base::Unit(0, 1, 0, 0, 0, 0, 0, 0)), "kg");
EXPECT_EQ(toString(Base::Unit(0, 0, 1, 0, 0, 0, 0, 0)), "s");
EXPECT_EQ(toString(Base::Unit(0, 0, 0, 1, 0, 0, 0, 0)), "A");
EXPECT_EQ(toString(Base::Unit(0, 0, 0, 0, 1, 0, 0, 0)), "K");
EXPECT_EQ(toString(Base::Unit(0, 0, 0, 0, 0, 1, 0, 0)), "mol");
EXPECT_EQ(toString(Base::Unit(0, 0, 0, 0, 0, 0, 1, 0)), "cd");
EXPECT_EQ(toString(Base::Unit(0, 0, 0, 0, 0, 0, 0, 1)), "deg");
EXPECT_EQ(toString(Base::Unit(2, 0, 0, 0, 0, 0, 0, 0)), "mm^2");
EXPECT_EQ(toString(Base::Unit(1, 1, -2, 0, 0, 0, 0, 0)), "mm*kg/s^2");
EXPECT_EQ(Unit::Length.getString(), "mm");
}
TEST(Unit, TestTypeString)
TEST(Unit, string_complex_numerator_no_denominator)
{
auto toString = [](const Base::Unit& unit) {
return unit.getTypeString();
};
EXPECT_EQ(toString(Base::Unit::Acceleration), "Acceleration");
EXPECT_EQ(toString(Base::Unit::AmountOfSubstance), "AmountOfSubstance");
EXPECT_EQ(toString(Base::Unit::Angle), "Angle");
EXPECT_EQ(toString(Base::Unit::AngleOfFriction), "Angle"); // same unit as Angle
EXPECT_EQ(toString(Base::Unit::Area), "Area");
EXPECT_EQ(toString(Base::Unit::CurrentDensity), "CurrentDensity");
EXPECT_EQ(toString(Base::Unit::Density), "Density");
EXPECT_EQ(toString(Base::Unit::DissipationRate), "DissipationRate");
EXPECT_EQ(toString(Base::Unit::DynamicViscosity), "DynamicViscosity");
EXPECT_EQ(toString(Base::Unit::ElectricalCapacitance), "ElectricalCapacitance");
EXPECT_EQ(toString(Base::Unit::ElectricalConductance), "ElectricalConductance");
EXPECT_EQ(toString(Base::Unit::ElectricalConductivity), "ElectricalConductivity");
EXPECT_EQ(toString(Base::Unit::ElectricalInductance), "ElectricalInductance");
EXPECT_EQ(toString(Base::Unit::ElectricalResistance), "ElectricalResistance");
EXPECT_EQ(toString(Base::Unit::ElectricCharge), "ElectricCharge");
EXPECT_EQ(toString(Base::Unit::ElectricCurrent), "ElectricCurrent");
EXPECT_EQ(toString(Base::Unit::ElectricPotential), "ElectricPotential");
EXPECT_EQ(toString(Base::Unit::Frequency), "Frequency");
EXPECT_EQ(toString(Base::Unit::Force), "Force");
EXPECT_EQ(toString(Base::Unit::HeatFlux), "HeatFlux");
EXPECT_EQ(toString(Base::Unit::InverseArea), "InverseArea");
EXPECT_EQ(toString(Base::Unit::InverseLength), "InverseLength");
EXPECT_EQ(toString(Base::Unit::InverseVolume), "InverseVolume");
EXPECT_EQ(toString(Base::Unit::KinematicViscosity), "KinematicViscosity");
EXPECT_EQ(toString(Base::Unit::Length), "Length");
EXPECT_EQ(toString(Base::Unit::LuminousIntensity), "LuminousIntensity");
EXPECT_EQ(toString(Base::Unit::MagneticFieldStrength), "MagneticFieldStrength");
EXPECT_EQ(toString(Base::Unit::MagneticFlux), "MagneticFlux");
EXPECT_EQ(toString(Base::Unit::MagneticFluxDensity), "MagneticFluxDensity");
EXPECT_EQ(toString(Base::Unit::Magnetization),
"MagneticFieldStrength"); // same as MagneticFieldStrength
EXPECT_EQ(toString(Base::Unit::Mass), "Mass");
EXPECT_EQ(toString(Base::Unit::Pressure), "Pressure");
EXPECT_EQ(toString(Base::Unit::Power), "Power");
EXPECT_EQ(toString(Base::Unit::ShearModulus), "Pressure"); // same as Pressure
EXPECT_EQ(toString(Base::Unit::SpecificEnergy), "SpecificEnergy");
EXPECT_EQ(toString(Base::Unit::SpecificHeat), "SpecificHeat");
EXPECT_EQ(toString(Base::Unit::Stiffness), "Stiffness");
EXPECT_EQ(toString(Base::Unit::Stress), "Pressure"); // same as Pressure
EXPECT_EQ(toString(Base::Unit::Temperature), "Temperature");
EXPECT_EQ(toString(Base::Unit::ThermalConductivity), "ThermalConductivity");
EXPECT_EQ(toString(Base::Unit::ThermalExpansionCoefficient), "ThermalExpansionCoefficient");
EXPECT_EQ(toString(Base::Unit::ThermalTransferCoefficient), "ThermalTransferCoefficient");
EXPECT_EQ(toString(Base::Unit::TimeSpan), "TimeSpan");
EXPECT_EQ(toString(Base::Unit::UltimateTensileStrength), "Pressure"); // same as Pressure
EXPECT_EQ(toString(Base::Unit::VacuumPermittivity), "VacuumPermittivity");
EXPECT_EQ(toString(Base::Unit::Velocity), "Velocity");
EXPECT_EQ(toString(Base::Unit::Volume), "Volume");
EXPECT_EQ(toString(Base::Unit::VolumeFlowRate), "VolumeFlowRate");
EXPECT_EQ(toString(Base::Unit::VolumetricThermalExpansionCoefficient),
"ThermalExpansionCoefficient");
EXPECT_EQ(toString(Base::Unit::Work), "Work");
EXPECT_EQ(toString(Base::Unit::YieldStrength), "Pressure"); // same as Pressure
EXPECT_EQ(toString(Base::Unit::YoungsModulus), "Pressure"); // same unit as Pressure
EXPECT_EQ(Unit::Area.getString(), "mm^2");
}
TEST(Unit, strings)
TEST(Unit, string_complex_single_denominator)
{
EXPECT_STREQ(Base::Unit::Acceleration.getString().c_str(), "mm/s^2");
EXPECT_STREQ(Base::Unit::AmountOfSubstance.getString().c_str(), "mol");
EXPECT_STREQ(Base::Unit::Angle.getString().c_str(), "deg");
EXPECT_STREQ(Base::Unit::AngleOfFriction.getString().c_str(), "deg");
EXPECT_STREQ(Base::Unit::Area.getString().c_str(), "mm^2");
EXPECT_STREQ(Base::Unit::CurrentDensity.getString().c_str(), "A/mm^2");
EXPECT_STREQ(Base::Unit::Density.getString().c_str(), "kg/mm^3");
EXPECT_STREQ(Base::Unit::DissipationRate.getString().c_str(), "mm^2/s^3");
EXPECT_EQ(Unit::DissipationRate.getString(), "mm^2/s^3");
}
TEST(Unit, string_no_numerator)
{
EXPECT_EQ(Unit::InverseArea.getString(), "1/mm^2");
}
TEST(Unit, string_complex_multi_denominator)
{
EXPECT_EQ(Unit::MagneticFlux.getString(), "mm^2*kg/(s^2*A)");
}
TEST(Unit, type_string)
{
EXPECT_EQ(Unit::MagneticFlux.getTypeString(), "MagneticFlux");
}
TEST(Unit, TestEqual)
{
Base::Unit unit {1};
EXPECT_EQ(unit.pow(2) == Base::Unit {2}, true);
EXPECT_TRUE(Unit::Length == Unit::Length);
}
TEST(Unit, TestNotEqual)
{
Base::Unit unit {1};
EXPECT_EQ(unit.pow(2) != Base::Unit {1}, true);
EXPECT_TRUE(Unit::Length != Unit::Area);
}
TEST(Unit, multiply_One_is_One)
{
EXPECT_EQ(Unit::One * Unit::One, Unit::One);
}
TEST(Unit, TestMult)
{
EXPECT_EQ(Base::Unit {} * Base::Unit {}, Base::Unit {});
EXPECT_EQ(Base::Unit(0, 1) * Base::Unit(1, 0), Base::Unit(1, 1));
constexpr UnitExponents arr {1, 1, 0, 0, 0, 0, 0, 0};
EXPECT_EQ(Unit::Mass * Unit::Length, Unit {arr});
}
TEST(Unit, TestDiv)
TEST(Unit, div)
{
EXPECT_EQ(Base::Unit {} * Base::Unit {}, Base::Unit {});
EXPECT_EQ(Base::Unit(0, 1) / Base::Unit(1, 0), Base::Unit(-1, 1));
EXPECT_EQ(Unit::Area / Unit::Length, Unit::Length);
}
TEST(Unit, TestPowNoDim)
TEST(Unit, div_by_One_does_nothing)
{
Base::Unit unit {};
EXPECT_EQ(unit.pow(2), Base::Unit {0});
EXPECT_EQ(unit == Base::Unit::One, true);
EXPECT_EQ(Unit::Area / Unit::One, Unit::Area);
}
TEST(Unit, TestPowEQ1)
TEST(Unit, pow_0_is_One)
{
Base::Unit unit {2};
EXPECT_EQ(unit.pow(1), Base::Unit {2});
EXPECT_EQ(Unit::Area.pow(0), Unit::One);
}
TEST(Unit, TestPowEQ0)
TEST(Unit, pow_1_leaves_unit_unchanged)
{
Base::Unit unit {2};
EXPECT_EQ(unit.pow(0), Base::Unit {0});
EXPECT_EQ(Unit::Area.pow(1), Unit::Area);
}
TEST(Unit, TestPowGT1)
TEST(Unit, pow_2_is_squared)
{
Base::Unit unit {2};
EXPECT_EQ(unit.pow(2), Base::Unit {4});
EXPECT_EQ(Unit::Length.pow(2), Unit::Area);
}
TEST(Unit, TestPowLT1)
TEST(Unit, pow_3_is_cubed)
{
Base::Unit unit {3};
EXPECT_EQ(unit.pow(1.0 / 3.0), Base::Unit {1});
EXPECT_EQ(Unit::Length.pow(3), Unit::Volume);
}
TEST(Unit, TestPow3DIV2)
TEST(Unit, pow_less_than_one)
{
Base::Unit unit {3};
EXPECT_THROW(unit.pow(3.0 / 2.0), Base::UnitsMismatchError);
EXPECT_EQ(Unit::Volume.pow(1.0 / 3.0), Unit::Length);
}
TEST(Unit, TestOverflow)
TEST(Unit, one_still_one_after_pow)
{
// this tests _that_ the expected exception is thrown
EXPECT_THROW(
{
try {
Base::Unit unit {3};
unit.pow(10000);
}
catch (const Base::OverflowError& e) {
// and this tests that it has the correct message
EXPECT_STREQ("Unit overflow in pow()", e.what());
throw;
}
},
Base::OverflowError);
EXPECT_EQ(Unit::One.pow(2), Unit::One);
}
TEST(Unit, TestUnderflow)
TEST(Unit, square_root)
{
// this tests _that_ the expected exception is thrown
EXPECT_THROW(
{
try {
Base::Unit unit {3};
unit.pow(-10000);
}
catch (const Base::UnderflowError& e) {
// and this tests that it has the correct message
EXPECT_STREQ("Unit underflow in pow()", e.what());
throw;
}
},
Base::UnderflowError);
EXPECT_EQ(Unit::Area.root(2), Unit::Length);
}
// NOLINTEND
TEST(Unit, cube_root)
{
EXPECT_EQ(Unit::Volume.root(3), Unit::Length);
}
TEST(Unit, zero_root)
{
EXPECT_THROW([[maybe_unused]] auto res = Unit::Area.root(0), UnitsMismatchError);
}
TEST(Unit, one_root)
{
EXPECT_EQ(Unit::Area.root(1), Unit::Area);
}
TEST(Unit, TestPow3div2)
{
EXPECT_THROW([[maybe_unused]] auto res = Unit::Volume.pow(3.0 / 2.0), UnitsMismatchError);
}
TEST(Unit, overflow)
{
constexpr UnitExponents arr {99, 0, 0, 0, 0, 0, 0, 0};
EXPECT_THROW([[maybe_unused]] auto res = Unit {arr}, OverflowError);
}
TEST(Unit, underflow)
{
constexpr UnitExponents arr {-99, 0, 0, 0, 0, 0, 0, 0};
EXPECT_THROW([[maybe_unused]] auto res = Unit {arr}, UnderflowError);
}
TEST(Unit, representation_simple)
{
const std::string expect {"Unit: mm (1,0,0,0,0,0,0,0) [Length]"};
const auto actual = Unit::Length.representation();
EXPECT_EQ(actual, expect);
}
TEST(Unit, representation_complex)
{
const std::string expect {"Unit: mm^2*kg/(s^2*A) (2,1,-2,-1,0,0,0,0) [MagneticFlux]"};
const auto actual = Unit::MagneticFlux.representation();
EXPECT_EQ(actual, expect);
}
TEST(Unit, representation_no_name)
{
constexpr Unit unit {{1, 1}};
const std::string expect {"Unit: mm*kg (1,1,0,0,0,0,0,0)"};
const auto actual = unit.representation();
EXPECT_EQ(actual, expect);
}