diff --git a/src/App/Expression.cpp b/src/App/Expression.cpp index 6e9db2bc83..9275e678b1 100644 --- a/src/App/Expression.cpp +++ b/src/App/Expression.cpp @@ -488,7 +488,7 @@ static inline Quantity pyToQuantity(const Py::Object &pyobj, } Py::Object pyFromQuantity(const Quantity &quantity) { - if(!quantity.getUnit().isEmpty()) + if (!quantity.isDimensionless()) return Py::asObject(new QuantityPy(new Quantity(quantity))); double v = quantity.getValue(); long l; diff --git a/src/App/FeatureTest.cpp b/src/App/FeatureTest.cpp index e645193844..93bbcb95ab 100644 --- a/src/App/FeatureTest.cpp +++ b/src/App/FeatureTest.cpp @@ -145,7 +145,7 @@ FeatureTest::FeatureTest() ADD_PROPERTY(QuantityLength, (1.0)); QuantityLength.setUnit(Base::Unit::Length); ADD_PROPERTY(QuantityOther, (5.0)); - QuantityOther.setUnit(Base::Unit(-3, 1)); + QuantityOther.setUnit(Base::Unit::Density); // clang-format on } diff --git a/src/App/PropertyGeo.cpp b/src/App/PropertyGeo.cpp index c4a51c4419..ad857a41d0 100644 --- a/src/App/PropertyGeo.cpp +++ b/src/App/PropertyGeo.cpp @@ -194,7 +194,7 @@ void PropertyVector::getPaths(std::vector& paths) const const boost::any PropertyVector::getPathValue(const ObjectIdentifier& path) const { Base::Unit unit = getUnit(); - if (!unit.isEmpty()) { + if (unit != Unit::One) { std::string p = path.getSubPathStr(); if (p == ".x" || p == ".y" || p == ".z") { // Convert double to quantity @@ -207,7 +207,7 @@ const boost::any PropertyVector::getPathValue(const ObjectIdentifier& path) cons bool PropertyVector::getPyPathValue(const ObjectIdentifier& path, Py::Object& res) const { Base::Unit unit = getUnit(); - if (unit.isEmpty()) { + if (unit == Unit::One) { return false; } diff --git a/src/App/PropertyUnits.cpp b/src/App/PropertyUnits.cpp index 300c3ca786..ca38aed84a 100644 --- a/src/App/PropertyUnits.cpp +++ b/src/App/PropertyUnits.cpp @@ -105,13 +105,12 @@ void PropertyQuantity::setPyObject(PyObject* value) else { Base::Quantity quant = createQuantityFromPy(value); - Unit unit = quant.getUnit(); - if (unit.isEmpty()) { + if (quant.isDimensionless()) { PropertyFloat::setValue(quant.getValue()); return; } - if (unit != _Unit) { + if (_Unit != quant.getUnit()) { throw Base::UnitsMismatchError("Not matching Unit!"); } @@ -123,7 +122,7 @@ void PropertyQuantity::setPathValue(const ObjectIdentifier& /*path*/, const boos { auto q = App::anyToQuantity(value); aboutToSetValue(); - if (!q.getUnit().isEmpty()) { + if (!q.isDimensionless()) { _Unit = q.getUnit(); } _dValue = q.getValue(); @@ -187,7 +186,6 @@ void PropertyQuantityConstraint::setPyObject(PyObject* value) { Base::Quantity quant = createQuantityFromPy(value); - Unit unit = quant.getUnit(); double temp = quant.getValue(); if (_ConstStruct) { if (temp > _ConstStruct->UpperBound) { @@ -199,12 +197,12 @@ void PropertyQuantityConstraint::setPyObject(PyObject* value) } quant.setValue(temp); - if (unit.isEmpty()) { + if (quant.isDimensionless()) { PropertyFloat::setValue(quant.getValue()); // clazy:exclude=skipped-base-method return; } - if (unit != _Unit) { + if (_Unit != quant.getUnit()) { throw Base::UnitsMismatchError("Not matching Unit!"); } diff --git a/src/Base/Quantity.cpp b/src/Base/Quantity.cpp index efa8c37a3c..4d10a79dc0 100644 --- a/src/Base/Quantity.cpp +++ b/src/Base/Quantity.cpp @@ -183,7 +183,7 @@ Quantity Quantity::operator/(double factor) const Quantity Quantity::pow(const Quantity& other) const { - if (!other.myUnit.isEmpty()) { + if (!other.isDimensionless()) { throw Base::UnitsMismatchError("Quantity::pow(): exponent must not have a unit"); } @@ -270,10 +270,10 @@ std::string Quantity::getSafeUserString() const return Tools::escapeQuotesFromString(userStr); } -/// true if it has a number without a unit +/// true if unit equals to 1, therefore quantity has no dimension bool Quantity::isDimensionless() const { - return isValid() && myUnit.isEmpty(); + return myUnit == Unit::One; } /// true if it has a specific unit or no dimension. @@ -282,12 +282,6 @@ bool Quantity::isDimensionlessOrUnit(const Unit& unit) const return isDimensionless() || myUnit == unit; } -// true if it has a number and a valid unit -bool Quantity::isQuantity() const -{ - return isValid() && !myUnit.isEmpty(); -} - // true if it has a number with or without a unit bool Quantity::isValid() const { @@ -300,162 +294,150 @@ void Quantity::setInvalid() } // === Predefined types ===================================================== +// clang-format off +const Quantity Quantity::NanoMetre ( 1.0e-6 , Unit::Length ); +const Quantity Quantity::MicroMetre ( 1.0e-3 , Unit::Length ); +const Quantity Quantity::MilliMetre ( 1.0 , Unit::Length ); +const Quantity Quantity::CentiMetre ( 10.0 , Unit::Length ); +const Quantity Quantity::DeciMetre ( 100.0 , Unit::Length ); +const Quantity Quantity::Metre ( 1.0e3 , Unit::Length ); +const Quantity Quantity::KiloMetre ( 1.0e6 , Unit::Length ); -const Quantity Quantity::NanoMetre(1.0e-6, Unit(1)); -const Quantity Quantity::MicroMetre(1.0e-3, Unit(1)); -const Quantity Quantity::MilliMetre(1.0, Unit(1)); -const Quantity Quantity::CentiMetre(10.0, Unit(1)); -const Quantity Quantity::DeciMetre(100.0, Unit(1)); -const Quantity Quantity::Metre(1.0e3, Unit(1)); -const Quantity Quantity::KiloMetre(1.0e6, Unit(1)); +const Quantity Quantity::MilliLiter ( 1000.0 , Unit::Volume ); +const Quantity Quantity::Liter ( 1.0e6 , Unit::Volume ); -const Quantity Quantity::MilliLiter(1000.0, Unit(3)); -const Quantity Quantity::Liter(1.0e6, Unit(3)); +const Quantity Quantity::Hertz ( 1.0 , Unit::Frequency ); +const Quantity Quantity::KiloHertz ( 1.0e3 , Unit::Frequency ); +const Quantity Quantity::MegaHertz ( 1.0e6 , Unit::Frequency ); +const Quantity Quantity::GigaHertz ( 1.0e9 , Unit::Frequency ); +const Quantity Quantity::TeraHertz ( 1.0e12 , Unit::Frequency ); -const Quantity Quantity::Hertz(1.0, Unit(0, 0, -1)); -const Quantity Quantity::KiloHertz(1.0e3, Unit(0, 0, -1)); -const Quantity Quantity::MegaHertz(1.0e6, Unit(0, 0, -1)); -const Quantity Quantity::GigaHertz(1.0e9, Unit(0, 0, -1)); -const Quantity Quantity::TeraHertz(1.0e12, Unit(0, 0, -1)); +const Quantity Quantity::MicroGram ( 1.0e-9 , Unit::Mass ); +const Quantity Quantity::MilliGram ( 1.0e-6 , Unit::Mass ); +const Quantity Quantity::Gram ( 1.0e-3 , Unit::Mass ); +const Quantity Quantity::KiloGram ( 1.0 , Unit::Mass ); +const Quantity Quantity::Ton ( 1.0e3 , Unit::Mass ); -const Quantity Quantity::MicroGram(1.0e-9, Unit(0, 1)); -const Quantity Quantity::MilliGram(1.0e-6, Unit(0, 1)); -const Quantity Quantity::Gram(1.0e-3, Unit(0, 1)); -const Quantity Quantity::KiloGram(1.0, Unit(0, 1)); -const Quantity Quantity::Ton(1.0e3, Unit(0, 1)); +const Quantity Quantity::Second ( 1.0 , Unit::TimeSpan ); +const Quantity Quantity::Minute ( 60.0 , Unit::TimeSpan ); +const Quantity Quantity::Hour ( 3600.0 , Unit::TimeSpan ); -const Quantity Quantity::Second(1.0, Unit(0, 0, 1)); -const Quantity Quantity::Minute(60.0, Unit(0, 0, 1)); -const Quantity Quantity::Hour(3600.0, Unit(0, 0, 1)); +const Quantity Quantity::Ampere ( 1.0 , Unit::ElectricCurrent ); +const Quantity Quantity::MilliAmpere ( 0.001 , Unit::ElectricCurrent ); +const Quantity Quantity::KiloAmpere ( 1000.0 , Unit::ElectricCurrent ); +const Quantity Quantity::MegaAmpere ( 1.0e6 , Unit::ElectricCurrent ); -const Quantity Quantity::Ampere(1.0, Unit(0, 0, 0, 1)); -const Quantity Quantity::MilliAmpere(0.001, Unit(0, 0, 0, 1)); -const Quantity Quantity::KiloAmpere(1000.0, Unit(0, 0, 0, 1)); -const Quantity Quantity::MegaAmpere(1.0e6, Unit(0, 0, 0, 1)); +const Quantity Quantity::Kelvin ( 1.0 , Unit::Temperature ); +const Quantity Quantity::MilliKelvin ( 0.001 , Unit::Temperature ); +const Quantity Quantity::MicroKelvin ( 0.000001 , Unit::Temperature ); -const Quantity Quantity::Kelvin(1.0, Unit(0, 0, 0, 0, 1)); -const Quantity Quantity::MilliKelvin(0.001, Unit(0, 0, 0, 0, 1)); -const Quantity Quantity::MicroKelvin(0.000001, Unit(0, 0, 0, 0, 1)); +const Quantity Quantity::MilliMole ( 0.001 , Unit::AmountOfSubstance ); +const Quantity Quantity::Mole ( 1.0 , Unit::AmountOfSubstance ); -const Quantity Quantity::MilliMole(0.001, Unit(0, 0, 0, 0, 0, 1)); -const Quantity Quantity::Mole(1.0, Unit(0, 0, 0, 0, 0, 1)); +const Quantity Quantity::Candela ( 1.0 , Unit::LuminousIntensity ); -const Quantity Quantity::Candela(1.0, Unit(0, 0, 0, 0, 0, 0, 1)); +const Quantity Quantity::Inch ( 25.4 , Unit::Length ); +const Quantity Quantity::Foot ( 304.8 , Unit::Length ); +const Quantity Quantity::Thou ( 0.0254 , Unit::Length ); +const Quantity Quantity::Yard ( 914.4 , Unit::Length ); +const Quantity Quantity::Mile ( 1609344.0 , Unit::Length ); -const Quantity Quantity::Inch(25.4, Unit(1)); -const Quantity Quantity::Foot(304.8, Unit(1)); -const Quantity Quantity::Thou(0.0254, Unit(1)); -const Quantity Quantity::Yard(914.4, Unit(1)); -const Quantity Quantity::Mile(1609344.0, Unit(1)); +const Quantity Quantity::MilePerHour ( 447.04 , Unit::Velocity ); +const Quantity Quantity::SquareFoot ( 92903.04 , Unit::Area ); +const Quantity Quantity::CubicFoot ( 28316846.592 , Unit::Volume ); -const Quantity Quantity::MilePerHour(447.04, Unit(1, 0, -1)); -const Quantity Quantity::SquareFoot(92903.04, Unit(2)); -const Quantity Quantity::CubicFoot(28316846.592, Unit(3)); +const Quantity Quantity::Pound ( 0.45359237 , Unit::Mass ); +const Quantity Quantity::Ounce ( 0.0283495231 , Unit::Mass ); +const Quantity Quantity::Stone ( 6.35029318 , Unit::Mass ); +const Quantity Quantity::Hundredweights ( 50.80234544 , Unit::Mass ); -const Quantity Quantity::Pound(0.45359237, Unit(0, 1)); -const Quantity Quantity::Ounce(0.0283495231, Unit(0, 1)); -const Quantity Quantity::Stone(6.35029318, Unit(0, 1)); -const Quantity Quantity::Hundredweights(50.80234544, Unit(0, 1)); +const Quantity Quantity::PoundForce ( 4448.22 , Unit::Force ); // lbf are ~= 4.44822 Newton -const Quantity Quantity::PoundForce(4448.22, Unit(1, 1, -2)); // lbf are ~= 4.44822 Newton +const Quantity Quantity::Newton ( 1000.0 , Unit::Force ); // Newton (kg*m/s^2) +const Quantity Quantity::MilliNewton ( 1.0 , Unit::Force ); +const Quantity Quantity::KiloNewton ( 1e+6 , Unit::Force ); +const Quantity Quantity::MegaNewton ( 1e+9 , Unit::Force ); -const Quantity Quantity::Newton(1000.0, Unit(1, 1, -2)); // Newton (kg*m/s^2) -const Quantity Quantity::MilliNewton(1.0, Unit(1, 1, -2)); -const Quantity Quantity::KiloNewton(1e+6, Unit(1, 1, -2)); -const Quantity Quantity::MegaNewton(1e+9, Unit(1, 1, -2)); +const Quantity Quantity::NewtonPerMeter ( 1.00 , Unit::Stiffness ); // Newton per meter (N/m or kg/s^2) +const Quantity Quantity::MilliNewtonPerMeter ( 1e-3 , Unit::Stiffness ); +const Quantity Quantity::KiloNewtonPerMeter ( 1e3 , Unit::Stiffness ); +const Quantity Quantity::MegaNewtonPerMeter ( 1e6 , Unit::Stiffness ); -const Quantity Quantity::NewtonPerMeter(1.00, Unit(0, 1, -2)); // Newton per meter (N/m or kg/s^2) -const Quantity Quantity::MilliNewtonPerMeter(1e-3, Unit(0, 1, -2)); -const Quantity Quantity::KiloNewtonPerMeter(1e3, Unit(0, 1, -2)); -const Quantity Quantity::MegaNewtonPerMeter(1e6, Unit(0, 1, -2)); +const Quantity Quantity::Pascal ( 0.001 , Unit::CompressiveStrength ); // Pascal (kg/m/s^2 or N/m^2) +const Quantity Quantity::KiloPascal ( 1.00 , Unit::CompressiveStrength ); +const Quantity Quantity::MegaPascal ( 1000.0 , Unit::CompressiveStrength ); +const Quantity Quantity::GigaPascal ( 1e+6 , Unit::CompressiveStrength ); -const Quantity Quantity::Pascal(0.001, Unit(-1, 1, -2)); // Pascal (kg/m/s^2 or N/m^2) -const Quantity Quantity::KiloPascal(1.00, Unit(-1, 1, -2)); -const Quantity Quantity::MegaPascal(1000.0, Unit(-1, 1, -2)); -const Quantity Quantity::GigaPascal(1e+6, Unit(-1, 1, -2)); +const Quantity Quantity::MilliBar ( 0.1 , Unit::CompressiveStrength ); +const Quantity Quantity::Bar ( 100.0 , Unit::CompressiveStrength ); // 1 bar = 100 kPa -const Quantity Quantity::MilliBar(0.1, Unit(-1, 1, -2)); -const Quantity Quantity::Bar(100.0, Unit(-1, 1, -2)); // 1 bar = 100 kPa +const Quantity Quantity::Torr ( 101.325 / 760.0 , Unit::CompressiveStrength ); // Torr is a defined fraction of Pascal (kg/m/s^2 or N/m^2) +const Quantity Quantity::mTorr ( 0.101325 / 760.0 , Unit::CompressiveStrength ); // Torr is a defined fraction of Pascal (kg/m/s^2 or N/m^2) +const Quantity Quantity::yTorr ( 0.000101325 / 760.0 , Unit::CompressiveStrength ); // Torr is a defined fraction of Pascal (kg/m/s^2 or N/m^2) -const Quantity - Quantity::Torr(101.325 / 760.0, - Unit(-1, 1, -2)); // Torr is a defined fraction of Pascal (kg/m/s^2 or N/m^2) -const Quantity - Quantity::mTorr(0.101325 / 760.0, - Unit(-1, 1, -2)); // Torr is a defined fraction of Pascal (kg/m/s^2 or N/m^2) -const Quantity - Quantity::yTorr(0.000101325 / 760.0, - Unit(-1, 1, -2)); // Torr is a defined fraction of Pascal (kg/m/s^2 or N/m^2) +const Quantity Quantity::PSI ( 6.894744825494 , Unit::CompressiveStrength ); // pounds/in^2 +const Quantity Quantity::KSI ( 6894.744825494 , Unit::CompressiveStrength ); // 1000 x pounds/in^2 +const Quantity Quantity::MPSI ( 6894744.825494 , Unit::CompressiveStrength ); // 1000 ksi -const Quantity Quantity::PSI(6.894744825494, Unit(-1, 1, -2)); // pounds/in^2 -const Quantity Quantity::KSI(6894.744825494, Unit(-1, 1, -2)); // 1000 x pounds/in^2 -const Quantity Quantity::MPSI(6894744.825494, Unit(-1, 1, -2)); // 1000 ksi +const Quantity Quantity::Watt ( 1e+6 , Unit::Power ); // Watt (kg*m^2/s^3) +const Quantity Quantity::MilliWatt ( 1e+3 , Unit::Power ); +const Quantity Quantity::KiloWatt ( 1e+9 , Unit::Power ); +const Quantity Quantity::VoltAmpere ( 1e+6 , Unit::Power ); // VoltAmpere (kg*m^2/s^3) -const Quantity Quantity::Watt(1e+6, Unit(2, 1, -3)); // Watt (kg*m^2/s^3) -const Quantity Quantity::MilliWatt(1e+3, Unit(2, 1, -3)); -const Quantity Quantity::KiloWatt(1e+9, Unit(2, 1, -3)); -const Quantity Quantity::VoltAmpere(1e+6, Unit(2, 1, -3)); // VoltAmpere (kg*m^2/s^3) +const Quantity Quantity::Volt ( 1e+6 , Unit::ElectricPotential ); // Volt (kg*m^2/A/s^3) +const Quantity Quantity::MilliVolt ( 1e+3 , Unit::ElectricPotential ); +const Quantity Quantity::KiloVolt ( 1e+9 , Unit::ElectricPotential ); -const Quantity Quantity::Volt(1e+6, Unit(2, 1, -3, -1)); // Volt (kg*m^2/A/s^3) -const Quantity Quantity::MilliVolt(1e+3, Unit(2, 1, -3, -1)); -const Quantity Quantity::KiloVolt(1e+9, Unit(2, 1, -3, -1)); +const Quantity Quantity::MegaSiemens ( 1.0 , Unit::ElectricalConductance ); +const Quantity Quantity::KiloSiemens ( 1e-3 , Unit::ElectricalConductance ); +const Quantity Quantity::Siemens ( 1e-6 , Unit::ElectricalConductance ); // Siemens (A^2*s^3/kg/m^2) +const Quantity Quantity::MilliSiemens ( 1e-9 , Unit::ElectricalConductance ); +const Quantity Quantity::MicroSiemens ( 1e-12 , Unit::ElectricalConductance ); -const Quantity Quantity::MegaSiemens(1.0, Unit(-2, -1, 3, 2)); -const Quantity Quantity::KiloSiemens(1e-3, Unit(-2, -1, 3, 2)); -const Quantity Quantity::Siemens(1e-6, Unit(-2, -1, 3, 2)); // Siemens (A^2*s^3/kg/m^2) -const Quantity Quantity::MilliSiemens(1e-9, Unit(-2, -1, 3, 2)); -const Quantity Quantity::MicroSiemens(1e-12, Unit(-2, -1, 3, 2)); +const Quantity Quantity::Ohm ( 1e+6 , Unit::ElectricalResistance ); // Ohm (kg*m^2/A^2/s^3) +const Quantity Quantity::KiloOhm ( 1e+9 , Unit::ElectricalResistance ); +const Quantity Quantity::MegaOhm ( 1e+12 , Unit::ElectricalResistance ); -const Quantity Quantity::Ohm(1e+6, Unit(2, 1, -3, -2)); // Ohm (kg*m^2/A^2/s^3) -const Quantity Quantity::KiloOhm(1e+9, Unit(2, 1, -3, -2)); -const Quantity Quantity::MegaOhm(1e+12, Unit(2, 1, -3, -2)); +const Quantity Quantity::Coulomb ( 1.0 , Unit::ElectricCharge ); // Coulomb (A*s) -const Quantity Quantity::Coulomb(1.0, Unit(0, 0, 1, 1)); // Coulomb (A*s) +const Quantity Quantity::Tesla ( 1.0 , Unit::MagneticFluxDensity ); // Tesla (kg/s^2/A) +const Quantity Quantity::Gauss ( 1e-4 , Unit::MagneticFluxDensity ); // 1 G = 1e-4 T -const Quantity Quantity::Tesla(1.0, Unit(0, 1, -2, -1)); // Tesla (kg/s^2/A) -const Quantity Quantity::Gauss(1e-4, Unit(0, 1, -2, -1)); // 1 G = 1e-4 T +const Quantity Quantity::Weber ( 1e6 , Unit::MagneticFlux ); // Weber (kg*m^2/s^2/A) -const Quantity Quantity::Weber(1e6, Unit(2, 1, -2, -1)); // Weber (kg*m^2/s^2/A) +const Quantity Quantity::PicoFarad ( 1e-18 , Unit::ElectricalCapacitance ); +const Quantity Quantity::NanoFarad ( 1e-15 , Unit::ElectricalCapacitance ); +const Quantity Quantity::MicroFarad ( 1e-12 , Unit::ElectricalCapacitance ); +const Quantity Quantity::MilliFarad ( 1e-9 , Unit::ElectricalCapacitance ); +const Quantity Quantity::Farad ( 1e-6 , Unit::ElectricalCapacitance ); // Farad (s^4*A^2/m^2/kg) -// disable Oersted because people need to input e.g. a field strength of -// 1 ampere per meter -> 1 A/m and not get the recalculation to Oersted -// const Quantity Quantity::Oersted(0.07957747, Unit(-1, 0, 0, 1));// Oersted (A/m) +const Quantity Quantity::NanoHenry ( 1e-3 , Unit::ElectricalInductance ); +const Quantity Quantity::MicroHenry ( 1.0 , Unit::ElectricalInductance ); +const Quantity Quantity::MilliHenry ( 1e+3 , Unit::ElectricalInductance ); +const Quantity Quantity::Henry ( 1e+6 , Unit::ElectricalInductance ); // Henry (kg*m^2/s^2/A^2) -const Quantity Quantity::PicoFarad(1e-18, Unit(-2, -1, 4, 2)); -const Quantity Quantity::NanoFarad(1e-15, Unit(-2, -1, 4, 2)); -const Quantity Quantity::MicroFarad(1e-12, Unit(-2, -1, 4, 2)); -const Quantity Quantity::MilliFarad(1e-9, Unit(-2, -1, 4, 2)); -const Quantity Quantity::Farad(1e-6, Unit(-2, -1, 4, 2)); // Farad (s^4*A^2/m^2/kg) +const Quantity Quantity::Joule ( 1e+6 , Unit::Moment ); // Joule (kg*m^2/s^2) +const Quantity Quantity::MilliJoule ( 1e+3 , Unit::Moment ); +const Quantity Quantity::KiloJoule ( 1e+9 , Unit::Moment ); +const Quantity Quantity::NewtonMeter ( 1e+6 , Unit::Moment ); // Joule (kg*m^2/s^2) +const Quantity Quantity::VoltAmpereSecond ( 1e+6 , Unit::Moment ); // Joule (kg*m^2/s^2) +const Quantity Quantity::WattSecond ( 1e+6 , Unit::Moment ); // Joule (kg*m^2/s^2) +const Quantity Quantity::KiloWattHour ( 3.6e+12 , Unit::Moment ); // 1 kWh = 3.6e6 J +const Quantity Quantity::ElectronVolt ( 1.602176634e-13 , Unit::Moment ); // 1 eV = 1.602176634e-19 J +const Quantity Quantity::KiloElectronVolt ( 1.602176634e-10 , Unit::Moment ); +const Quantity Quantity::MegaElectronVolt ( 1.602176634e-7 , Unit::Moment ); +const Quantity Quantity::Calorie ( 4.1868e+6 , Unit::Moment ); // 1 cal = 4.1868 J +const Quantity Quantity::KiloCalorie ( 4.1868e+9 , Unit::Moment ); -const Quantity Quantity::NanoHenry(1e-3, Unit(2, 1, -2, -2)); -const Quantity Quantity::MicroHenry(1.0, Unit(2, 1, -2, -2)); -const Quantity Quantity::MilliHenry(1e+3, Unit(2, 1, -2, -2)); -const Quantity Quantity::Henry(1e+6, Unit(2, 1, -2, -2)); // Henry (kg*m^2/s^2/A^2) - -const Quantity Quantity::Joule(1e+6, Unit(2, 1, -2)); // Joule (kg*m^2/s^2) -const Quantity Quantity::MilliJoule(1e+3, Unit(2, 1, -2)); -const Quantity Quantity::KiloJoule(1e+9, Unit(2, 1, -2)); -const Quantity Quantity::NewtonMeter(1e+6, Unit(2, 1, -2)); // Joule (kg*m^2/s^2) -const Quantity Quantity::VoltAmpereSecond(1e+6, Unit(2, 1, -2)); // Joule (kg*m^2/s^2) -const Quantity Quantity::WattSecond(1e+6, Unit(2, 1, -2)); // Joule (kg*m^2/s^2) -const Quantity Quantity::KiloWattHour(3.6e+12, Unit(2, 1, -2)); // 1 kWh = 3.6e6 J -const Quantity Quantity::ElectronVolt(1.602176634e-13, Unit(2, 1, -2)); // 1 eV = 1.602176634e-19 J -const Quantity Quantity::KiloElectronVolt(1.602176634e-10, Unit(2, 1, -2)); -const Quantity Quantity::MegaElectronVolt(1.602176634e-7, Unit(2, 1, -2)); -const Quantity Quantity::Calorie(4.1868e+6, Unit(2, 1, -2)); // 1 cal = 4.1868 J -const Quantity Quantity::KiloCalorie(4.1868e+9, Unit(2, 1, -2)); - -const Quantity Quantity::KMH(277.778, Unit(1, 0, -1)); // km/h -const Quantity Quantity::MPH(447.04, Unit(1, 0, -1)); // Mile/h - -const Quantity Quantity::AngMinute(1.0 / 60.0, Unit(0, 0, 0, 0, 0, 0, 0, 1)); // angular minute -const Quantity Quantity::AngSecond(1.0 / 3600.0, Unit(0, 0, 0, 0, 0, 0, 0, 1)); // angular second -const Quantity - Quantity::Degree(1.0, - Unit(0, 0, 0, 0, 0, 0, 0, 1)); // degree (internal standard angle) -const Quantity Quantity::Radian(180 / std::numbers::pi, Unit(0, 0, 0, 0, 0, 0, 0, 1)); // radian -const Quantity Quantity::Gon(360.0 / 400.0, Unit(0, 0, 0, 0, 0, 0, 0, 1)); // gon +const Quantity Quantity::KMH ( 277.778 , Unit::Velocity ); // km/h +const Quantity Quantity::MPH ( 447.04 , Unit::Velocity ); // Mile/h +const Quantity Quantity::AngMinute ( 1.0 / 60.0 , Unit::Angle ); // angular minute +const Quantity Quantity::AngSecond ( 1.0 / 3600.0 , Unit::Angle ); // angular second +const Quantity Quantity::Degree ( 1.0 , Unit::Angle ); // degree (internal standard angle) +const Quantity Quantity::Radian ( 180 / std::numbers::pi, Unit::Angle ); // radian +const Quantity Quantity::Gon ( 360.0 / 400.0 , Unit::Angle ); // gon +// clang-format on // === Parser & Scanner stuff =============================================== diff --git a/src/Base/Quantity.h b/src/Base/Quantity.h index ed0f947ac9..657f3af1b4 100644 --- a/src/Base/Quantity.h +++ b/src/Base/Quantity.h @@ -191,12 +191,10 @@ public: double getValueAs(const Quantity&) const; - /// true if it has a number without a unit + /// true if it has no unit bool isDimensionless() const; /// true if it has a specific unit or no dimension. bool isDimensionlessOrUnit(const Unit& unit) const; - /// true if it has a number and a valid unit - bool isQuantity() const; /// true if it has a number with or without a unit bool isValid() const; /// sets the quantity invalid diff --git a/src/Base/QuantityPyImp.cpp b/src/Base/QuantityPyImp.cpp index d832945b5e..365ef592f6 100644 --- a/src/Base/QuantityPyImp.cpp +++ b/src/Base/QuantityPyImp.cpp @@ -43,19 +43,15 @@ using Base::Quantity; // returns a string which represents the object e.g. when printed in python std::string QuantityPy::representation() const { - std::stringstream ret; - - double val = getQuantityPtr()->getValue(); - Unit unit = getQuantityPtr()->getUnit(); - + std::stringstream ss; // Use Python's implementation to repr() a float - Py::Float flt(val); - ret << static_cast(flt.repr()); - if (!unit.isEmpty()) { - ret << " " << unit.getString(); + Py::Float flt(getQuantityPtr()->getValue()); + ss << static_cast(flt.repr()); + if (!getQuantityPtr()->isDimensionless()) { + ss << " " << getQuantityPtr()->getUnit().getString(); } - return ret.str(); + return ss.str(); } PyObject* QuantityPy::toStr(PyObject* args) const @@ -66,17 +62,16 @@ PyObject* QuantityPy::toStr(PyObject* args) const } double val = getQuantityPtr()->getValue(); - Unit unit = getQuantityPtr()->getUnit(); - std::stringstream ret; - ret.precision(prec); - ret.setf(std::ios::fixed, std::ios::floatfield); - ret << val; - if (!unit.isEmpty()) { - ret << " " << unit.getString(); + std::stringstream ss; + ss.precision(prec); + ss.setf(std::ios::fixed, std::ios::floatfield); + ss << val; + if (!getQuantityPtr()->isDimensionless()) { + ss << " " << getQuantityPtr()->getUnit().getString(); } - return Py_BuildValue("s", ret.str().c_str()); + return Py_BuildValue("s", ss.str().c_str()); } PyObject* QuantityPy::PyMake(PyTypeObject* /*unused*/, PyObject* /*unused*/, PyObject* /*unused*/) @@ -96,45 +91,37 @@ int QuantityPy::PyInit(PyObject* args, PyObject* /*kwd*/) *self = *(static_cast(object)->getQuantityPtr()); return 0; } - PyErr_Clear(); // set by PyArg_ParseTuple() + double f = std::numeric_limits::max(); if (PyArg_ParseTuple(args, "dO!", &f, &(UnitPy::Type), &object)) { *self = Quantity(f, *(static_cast(object)->getUnitPtr())); return 0; } - PyErr_Clear(); // set by PyArg_ParseTuple() + if (PyArg_ParseTuple(args, "dO!", &f, &(QuantityPy::Type), &object)) { PyErr_SetString(PyExc_TypeError, "Second argument must be a Unit not a Quantity"); return -1; } - - int i1 = 0; - int i2 = 0; - int i3 = 0; - int i4 = 0; - int i5 = 0; - int i6 = 0; - int i7 = 0; - int i8 = 0; PyErr_Clear(); // set by PyArg_ParseTuple() + + int i1 {0}; + int i2 {0}; + int i3 {0}; + int i4 {0}; + int i5 {0}; + int i6 {0}; + int i7 {0}; + int i8 {0}; if (PyArg_ParseTuple(args, "|diiiiiiii", &f, &i1, &i2, &i3, &i4, &i5, &i6, &i7, &i8)) { if (f < std::numeric_limits::max()) { - *self = Quantity(f, - Unit {static_cast(i1), - static_cast(i2), - static_cast(i3), - static_cast(i4), - static_cast(i5), - static_cast(i6), - static_cast(i7), - static_cast(i8)}); + *self = Quantity {f, Unit(i1, i2, i3, i4, i5, i6, i7, i8)}; } return 0; } - PyErr_Clear(); // set by PyArg_ParseTuple() + char* string {}; if (PyArg_ParseTuple(args, "et", "utf-8", &string)) { std::string str(string); @@ -149,13 +136,13 @@ int QuantityPy::PyInit(PyObject* args, PyObject* /*kwd*/) return 0; } - PyErr_Clear(); // set by PyArg_ParseTuple() + if (PyArg_ParseTuple(args, "det", &f, "utf-8", &string)) { - std::string unit(string); + std::string str(string); PyMem_Free(string); try { - *self = Quantity(f, unit); + *self = Quantity(f, str); } catch (const ParserError& e) { PyErr_SetString(PyExc_ValueError, e.what()); @@ -215,7 +202,7 @@ PyObject* QuantityPy::getValueAs(PyObject* args) const }; auto tryUnitPartsAndValue = [&]() -> std::optional { - double f = std::numeric_limits::max(); + double f; int i1 {0}; int i2 {0}; int i3 {0}; @@ -224,20 +211,11 @@ PyObject* QuantityPy::getValueAs(PyObject* args) const int i6 {0}; int i7 {0}; int i8 {0}; - PyErr_Clear(); if (!PyArg_ParseTuple(args, "d|iiiiiiii", &f, &i1, &i2, &i3, &i4, &i5, &i6, &i7, &i8)) { return std::nullopt; } - if (f >= std::numeric_limits::max()) { - return std::nullopt; - } - - auto re = [](auto val) { - return static_cast(val); - }; - - return Quantity {f, Unit {re(i1), re(i2), re(i3), re(i4), re(i5), re(i6), re(i7), re(i8)}}; + return Quantity {f, Unit(i1, i2, i3, i4, i5, i6, i7, i8)}; }; auto tryString = [&]() -> std::optional { @@ -279,11 +257,6 @@ PyObject* QuantityPy::getValueAs(PyObject* args) const } const auto qpUnit = qPtr->getUnit(); - if (qpUnit.isEmpty()) { - err("QuantityPtr returned empty unit"); - return false; - } - if (const auto qUnit = quant.getUnit(); qUnit != qpUnit) { err("Unit mismatch (`" + qUnit.getString() + "` != `" + qpUnit.getString() + "`)"); return false; @@ -301,7 +274,7 @@ PyObject* QuantityPy::getValueAs(PyObject* args) const } const auto quant = optQuant.value(); - if (quant.isQuantity()) { + if (!quant.isDimensionless()) { if (!checkQuant(quant)) { return nullptr; } diff --git a/src/Base/Unit.cpp b/src/Base/Unit.cpp index baa782972d..45a4af42e7 100644 --- a/src/Base/Unit.cpp +++ b/src/Base/Unit.cpp @@ -1,23 +1,22 @@ -/*************************************************************************** - * Copyright (c) 2011 Jürgen Riegel * - * * - * This file is part of the FreeCAD CAx development system. * - * * - * This library is free software; you can redistribute it and/or * - * modify it under the terms of the GNU Library General Public * - * License as published by the Free Software Foundation; either * - * version 2 of the License, or (at your option) any later version. * - * * - * This library is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU Library General Public License for more details. * - * * - * You should have received a copy of the GNU Library General Public * - * License along with this library; see the file COPYING.LIB. If not, * - * write to the Free Software Foundation, Inc., 59 Temple Place, * - * Suite 330, Boston, MA 02111-1307, USA * - * * +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * ***************************************************************************/ #include "PreCompiled.h" @@ -27,696 +26,349 @@ #include #include #include -#include +#include +#include #endif -#include "Unit.h" -#include "Exception.h" -#include "Quantity.h" +#include +#include +#include "Unit.h" using namespace Base; -// clang-format off -constexpr int UnitSignatureLengthBits = 4; -constexpr int UnitSignatureMassBits = 4; -constexpr int UnitSignatureTimeBits = 4; -constexpr int UnitSignatureElectricCurrentBits = 4; -constexpr int UnitSignatureThermodynamicTemperatureBits = 4; -constexpr int UnitSignatureAmountOfSubstanceBits = 4; -constexpr int UnitSignatureLuminousIntensityBits = 4; -constexpr int UnitSignatureAngleBits = 4; - -struct UnitSignature { - int32_t Length: UnitSignatureLengthBits; - int32_t Mass: UnitSignatureMassBits; - int32_t Time: UnitSignatureTimeBits; - int32_t ElectricCurrent: UnitSignatureElectricCurrentBits; - int32_t ThermodynamicTemperature: UnitSignatureThermodynamicTemperatureBits; - int32_t AmountOfSubstance: UnitSignatureAmountOfSubstanceBits; - int32_t LuminousIntensity: UnitSignatureLuminousIntensityBits; - int32_t Angle: UnitSignatureAngleBits; +struct UnitSpec +{ + std::string_view name; + UnitExponents exps; }; -static inline uint32_t sigVal(const std::string &op, - int length, int mass, int time, int electricCurrent, - int thermodynamicTemperature, int amountOfSubstance, int luminousIntensity, int angle) +constexpr auto unitSpecs = std::to_array({ + // clang-format off + // Length + // . Mass + // . . Time + // . . . ElectricCurrent + // . . . . ThermodynamicTemperature + // . . . . . AmountOfSubstance + // . . . . . . LuminousIntensity + // . . . . . . . Angle + { "1" , { 0, 0, 0, 0, 0, 0, 0, 0 } }, + { "Length" , { 1 } }, + { "Mass" , { 0, 1 } }, + { "TimeSpan" , { 0, 0, 1 } }, + { "ElectricCurrent" , { 0, 0, 0, 1 } }, + { "Temperature" , { 0, 0, 0, 0, 1 } }, + { "AmountOfSubstance" , { 0, 0, 0, 0, 0, 1 } }, + { "LuminousIntensity" , { 0, 0, 0, 0, 0, 0, 1 } }, + { "Angle" , { 0, 0, 0, 0, 0, 0, 0, 1 } }, + { "Acceleration" , { 1, 0, -2 } }, + { "AngleOfFriction" , { 0, 0, 0, 0, 0, 0, 0, 1 } }, + { "Area" , { 2 } }, + { "CurrentDensity" , { -2, 0, 0, 1 } }, + { "Density" , { -3, 1 } }, + { "DissipationRate" , { 2, 0, -3 } }, + { "DynamicViscosity" , { -1, 1, -1 } }, + { "ElectricalCapacitance" , { -2, -1, 4, 2 } }, + { "ElectricalConductance" , { -2, -1, 3, 2 } }, + { "ElectricalConductivity" , { -3, -1, 3, 2 } }, + { "ElectricalInductance" , { 2, 1, -2, -2 } }, + { "ElectricalResistance" , { 2, 1, -3, -2 } }, + { "ElectricCharge" , { 0, 0, 1, 1 } }, + { "ElectricPotential" , { 2, 1, -3, -1 } }, + { "ElectromagneticPotential" , { 1, 1, -2, -1 } }, + { "Force" , { 1, 1, -2 } }, + { "Frequency" , { 0, 0, -1 } }, + { "HeatFlux" , { 0, 1, -3 } }, + { "InverseArea" , { -2 } }, + { "InverseLength" , { -1 } }, + { "InverseVolume" , { -3 } }, + { "KinematicViscosity" , { 2, 0, -1 } }, + { "MagneticFieldStrength" , { -1, 0, 0, 1 } }, + { "MagneticFlux" , { 2, 1, -2, -1 } }, + { "MagneticFluxDensity" , { 0, 1, -2, -1 } }, + { "Magnetization" , { -1, 0, 0, 1 } }, + { "Moment" , { 2, 1, -2 } }, + { "Pressure" , { -1, 1, -2 } }, + { "Power" , { 2, 1, -3 } }, + { "ShearModulus" , { -1, 1, -2 } }, + { "SpecificEnergy" , { 2, 0, -2 } }, + { "SpecificHeat" , { 2, 0, -2, 0, -1 } }, + { "Stiffness" , { 0, 1, -2 } }, + { "StiffnessDensity" , { -2, 1, -2 } }, + { "Stress" , { -1, 1, -2 } }, + { "SurfaceChargeDensity" , { -2, 0, 1, 1 } }, + { "ThermalConductivity" , { 1, 1, -3, 0, -1 } }, + { "ThermalExpansionCoefficient" , { 0, 0, 0, 0, -1 } }, + { "ThermalTransferCoefficient" , { 0, 1, -3, 0, -1 } }, + { "UltimateTensileStrength" , { -1, 1, -2 } }, + { "VacuumPermittivity" , { -3, -1, 4, 2 } }, + { "Velocity" , { 1, 0, -1 } }, + { "Volume" , { 3 } }, + { "VolumeChargeDensity" , { -3, 0, 1, 1 } }, + { "VolumeFlowRate" , { 3, 0, -1 } }, + { "VolumetricThermalExpansionCoefficient" , { 0, 0, 0, 0, -1 } }, + { "Work" , { 2, 1, -2 } }, + { "YieldStrength" , { -1, 1, -2 } }, + { "YoungsModulus" , { -1, 1, -2 } }, +}); // clang-format on + +Unit::Unit(const int length, // NOLINT + const int mass, + const int time, + const int electricCurrent, + const int thermodynamicTemperature, + const int amountOfSubstance, + const int luminousIntensity, + const int angle) + : _name {""} { - if ( ( length >= (1 << (UnitSignatureLengthBits - 1)) ) || - ( mass >= (1 << (UnitSignatureMassBits - 1)) ) || - ( time >= (1 << (UnitSignatureTimeBits - 1)) ) || - ( electricCurrent >= (1 << (UnitSignatureElectricCurrentBits - 1)) ) || - ( thermodynamicTemperature >= (1 << (UnitSignatureThermodynamicTemperatureBits - 1)) ) || - ( amountOfSubstance >= (1 << (UnitSignatureAmountOfSubstanceBits - 1)) ) || - ( luminousIntensity >= (1 << (UnitSignatureLuminousIntensityBits - 1)) ) || - ( angle >= (1 << (UnitSignatureAngleBits - 1)) ) ) { - throw Base::OverflowError(("Unit overflow in " + op).c_str()); - } - if ( ( length < -(1 << (UnitSignatureLengthBits - 1)) ) || - ( mass < -(1 << (UnitSignatureMassBits - 1)) ) || - ( time < -(1 << (UnitSignatureTimeBits - 1)) ) || - ( electricCurrent < -(1 << (UnitSignatureElectricCurrentBits - 1)) ) || - ( thermodynamicTemperature < -(1 << (UnitSignatureThermodynamicTemperatureBits - 1)) ) || - ( amountOfSubstance < -(1 << (UnitSignatureAmountOfSubstanceBits - 1)) ) || - ( luminousIntensity < -(1 << (UnitSignatureLuminousIntensityBits - 1)) ) || - ( angle < -(1 << (UnitSignatureAngleBits - 1)) ) ) { - throw Base::UnderflowError(("Unit underflow in " + op).c_str()); - } - - UnitSignature Sig; - Sig.Length = length; - Sig.Mass = mass; - Sig.Time = time; - Sig.ElectricCurrent = electricCurrent; - Sig.ThermodynamicTemperature = thermodynamicTemperature; - Sig.AmountOfSubstance = amountOfSubstance; - Sig.LuminousIntensity = luminousIntensity; - Sig.Angle = angle; - - uint32_t ret; - memcpy(&ret, &Sig, sizeof(ret)); - return ret; -} - - -Unit::Unit(int8_t Length, //NOLINT - int8_t Mass, - int8_t Time, - int8_t ElectricCurrent, - int8_t ThermodynamicTemperature, - int8_t AmountOfSubstance, - int8_t LuminousIntensity, - int8_t Angle) -{ - Val = sigVal("unit", - Length, - Mass, - Time, - ElectricCurrent, - ThermodynamicTemperature, - AmountOfSubstance, - LuminousIntensity, - Angle); -} - - -Unit::Unit() //NOLINT -{ - Val = 0; -} - -Unit::Unit(const std::string& expr) // NOLINT -{ - try { - *this = Quantity::parse(expr).getUnit(); - } - catch (const Base::ParserError&) { - Val = 0; - } -} - -Unit Unit::pow(double exp) const -{ - UnitSignature sig; - memcpy(&sig, &Val, sizeof(Val)); - auto isInt = [](double value) { - return std::fabs(std::round(value) - value) < std::numeric_limits::epsilon(); + auto cast = [](auto val) { + return static_cast(std::clamp(val, + std::numeric_limits::min(), + std::numeric_limits::max())); }; - if (!isInt(sig.Length * exp) || - !isInt(sig.Mass * exp) || - !isInt(sig.Time * exp) || - !isInt(sig.ElectricCurrent * exp) || - !isInt(sig.ThermodynamicTemperature * exp) || - !isInt(sig.AmountOfSubstance * exp) || - !isInt(sig.LuminousIntensity * exp) || - !isInt(sig.Angle * exp)) - throw Base::UnitsMismatchError("pow() of unit not possible"); - Unit result; - result.Val = sigVal("pow()", - sig.Length * exp, - sig.Mass * exp, - sig.Time * exp, - sig.ElectricCurrent * exp, - sig.ThermodynamicTemperature * exp, - sig.AmountOfSubstance * exp, - sig.LuminousIntensity * exp, - sig.Angle * exp); + _exps[0] = cast(length); + _exps[1] = cast(mass); + _exps[2] = cast(time); + _exps[3] = cast(electricCurrent); + _exps[4] = cast(thermodynamicTemperature); + _exps[5] = cast(amountOfSubstance); + _exps[6] = cast(luminousIntensity); + _exps[7] = cast(angle); - return result; + checkRange(); } -Unit Unit::sqrt() const +bool Unit::operator==(const Unit& that) const { - UnitSignature sig; - memcpy(&sig, &Val, sizeof(Val)); - // All components of unit must be either zero or dividable by 2 - if (!((sig.Length % 2) == 0) && - ((sig.Mass % 2) == 0) && - ((sig.Time % 2) == 0) && - ((sig.ElectricCurrent % 2) == 0) && - ((sig.ThermodynamicTemperature % 2) == 0) && - ((sig.AmountOfSubstance % 2) == 0) && - ((sig.LuminousIntensity % 2) == 0) && - ((sig.Angle % 2) == 0)) - throw Base::UnitsMismatchError("sqrt() needs even dimensions"); - - Unit result; - result.Val = sigVal("sqrt()", - sig.Length >> 1, - sig.Mass >> 1, - sig.Time >> 1, - sig.ElectricCurrent >> 1, - sig.ThermodynamicTemperature >> 1, - sig.AmountOfSubstance >> 1, - sig.LuminousIntensity >> 1, - sig.Angle >> 1); - - return result; + return _exps == that._exps; } -Unit Unit::cbrt() const +bool Unit::operator!=(const Unit& that) const { - UnitSignature sig; - memcpy(&sig, &Val, sizeof(Val)); - // All components of unit must be either zero or dividable by 3 - if (!((sig.Length % 3) == 0) && - ((sig.Mass % 3) == 0) && - ((sig.Time % 3) == 0) && - ((sig.ElectricCurrent % 3) == 0) && - ((sig.ThermodynamicTemperature % 3) == 0) && - ((sig.AmountOfSubstance % 3) == 0) && - ((sig.LuminousIntensity % 3) == 0) && - ((sig.Angle % 3) == 0)) - throw Base::UnitsMismatchError("cbrt() needs dimensions to be multiples of 3"); + return _exps != that._exps; +} - Unit result; - result.Val = sigVal("cbrt()", - sig.Length / 3, - sig.Mass / 3, - sig.Time / 3, - sig.ElectricCurrent / 3, - sig.ThermodynamicTemperature / 3, - sig.AmountOfSubstance / 3, - sig.LuminousIntensity / 3, - sig.Angle / 3); +Unit& Unit::operator*=(const Unit& that) +{ + *this = *this * that; + return *this; +} - return result; +Unit& Unit::operator/=(const Unit& that) +{ + *this = *this / that; + return *this; +} + +Unit Unit::operator*(const Unit& right) const +{ + auto mult = [&](auto leftExponent, auto rightExponent) { + return leftExponent + rightExponent; + }; + + UnitExponents res {}; + std::transform(_exps.begin(), _exps.end(), right._exps.begin(), res.begin(), mult); + + return Unit {res}; +} + +Unit Unit::operator/(const Unit& right) const +{ + auto div = [&](auto leftExponent, auto rightExponent) { + return leftExponent - rightExponent; + }; + + UnitExponents res {}; + std::transform(_exps.begin(), _exps.end(), right._exps.begin(), res.begin(), div); + + return Unit {res}; +} + +Unit Unit::root(const uint8_t num) const +{ + auto apply = [&](auto val) { + if (val % num != 0) { + throw UnitsMismatchError("unit values must be divisible by root"); + } + return static_cast(val / num); + }; + + if (num < 1) { + throw UnitsMismatchError("root must be > 0"); + } + + UnitExponents res {}; + std::transform(_exps.begin(), _exps.end(), res.begin(), apply); + + return Unit {res}; +} + +Unit Unit::pow(const double exp) const +{ + auto apply = [&](const auto val) { + const auto num {val * exp}; + if (std::fabs(std::round(num) - num) >= std::numeric_limits::epsilon()) { + throw UnitsMismatchError("pow() of unit not possible"); + } + + return static_cast(val * exp); + }; + + UnitExponents res {}; + std::transform(_exps.begin(), _exps.end(), res.begin(), apply); + + return Unit {res}; +} + +UnitExponents Unit::exponents() const +{ + return _exps; } int Unit::length() const { - UnitSignature sig; - memcpy(&sig, &Val, sizeof(Val)); - return sig.Length; -} - -int Unit::mass() const -{ - UnitSignature sig; - memcpy(&sig, &Val, sizeof(Val)); - return sig.Mass; -} - - -int Unit::time() const -{ - UnitSignature sig; - memcpy(&sig, &Val, sizeof(Val)); - return sig.Time; -} - -int Unit::electricCurrent() const -{ - UnitSignature sig; - memcpy(&sig, &Val, sizeof(Val)); - return sig.ElectricCurrent; -} - -int Unit::thermodynamicTemperature() const -{ - UnitSignature sig; - memcpy(&sig, &Val, sizeof(Val)); - return sig.ThermodynamicTemperature; -} - -int Unit::amountOfSubstance() const -{ - UnitSignature sig; - memcpy(&sig, &Val, sizeof(Val)); - return sig.AmountOfSubstance; -} - -int Unit::luminousIntensity() const -{ - UnitSignature sig; - memcpy(&sig, &Val, sizeof(Val)); - return sig.LuminousIntensity; -} - -int Unit::angle() const -{ - UnitSignature sig; - memcpy(&sig, &Val, sizeof(Val)); - return sig.Angle; -} - -bool Unit::isEmpty() const -{ - return Val == 0; -} - -int Unit::operator [](int index) const -{ - UnitSignature sig; - memcpy(&sig, &Val, sizeof(Val)); - - switch (index) { - case 0: - return sig.Length; - case 1: - return sig.Mass; - case 2: - return sig.Time; - case 3: - return sig.ElectricCurrent; - case 4: - return sig.ThermodynamicTemperature; - case 5: - return sig.AmountOfSubstance; - case 6: - return sig.LuminousIntensity; - case 7: - return sig.Angle; - default: - throw Base::IndexError("Unknown Unit element"); - } -} - -bool Unit::operator ==(const Unit& that) const -{ - return Val == that.Val; -} - -Unit Unit::operator *(const Unit &right) const -{ - Unit result; - UnitSignature sig, rsig; - - memcpy(&sig, &Val, sizeof(Val)); - memcpy(&rsig, &right.Val, sizeof(right.Val)); - result.Val = sigVal("* operator", - sig.Length + rsig.Length, - sig.Mass + rsig.Mass, - sig.Time + rsig.Time, - sig.ElectricCurrent + rsig.ElectricCurrent, - sig.ThermodynamicTemperature + rsig.ThermodynamicTemperature, - sig.AmountOfSubstance + rsig.AmountOfSubstance, - sig.LuminousIntensity + rsig.LuminousIntensity, - sig.Angle + rsig.Angle); - - return result; -} - -Unit Unit::operator /(const Unit &right) const -{ - Unit result; - UnitSignature sig, rsig; - - memcpy(&sig, &Val, sizeof(Val)); - memcpy(&rsig, &right.Val, sizeof(right.Val)); - result.Val = sigVal("/ operator", - sig.Length - rsig.Length, - sig.Mass - rsig.Mass, - sig.Time - rsig.Time, - sig.ElectricCurrent - rsig.ElectricCurrent, - sig.ThermodynamicTemperature - rsig.ThermodynamicTemperature, - sig.AmountOfSubstance - rsig.AmountOfSubstance, - sig.LuminousIntensity - rsig.LuminousIntensity, - sig.Angle - rsig.Angle); - - return result; + return _exps[0]; } std::string Unit::getString() const { - if (isEmpty()) { - return {}; + auto buildSubStr = [&](auto index) { + const std::string unitStrString {unitSymbols.at(index)}; + const auto absol {abs(_exps.at(index))}; + + return absol <= 1 ? unitStrString + : fmt::format("{}^{}", unitStrString, std::to_string(absol)); + }; + + auto buildStr = [&](auto indexes) { + std::vector subStrings {}; + std::transform(indexes.begin(), indexes.end(), std::back_inserter(subStrings), buildSubStr); + + return fmt::format("{}", fmt::join(subStrings, "*")); + }; + + //------------------------------------------------------------------------------ + + auto [posValIndexes, negValIndexes] = nonZeroValsIndexes(); + auto numeratorStr = buildStr(posValIndexes); + if (negValIndexes.empty()) { + return numeratorStr; } - std::stringstream ret; - UnitSignature sig; - memcpy(&sig, &Val, sizeof(Val)); + auto denominatorStr = buildStr(negValIndexes); - if (sig.Length > 0 || - sig.Mass > 0 || - sig.Time > 0 || - sig.ElectricCurrent > 0 || - sig.ThermodynamicTemperature > 0 || - sig.AmountOfSubstance > 0 || - sig.LuminousIntensity > 0 || - sig.Angle > 0 ) { + return fmt::format("{}/{}", + numeratorStr.empty() ? "1" : numeratorStr, + negValIndexes.size() > 1 ? fmt::format("({})", denominatorStr) + : denominatorStr); +} - bool mult = false; - if (sig.Length > 0) { - mult = true; - ret << "mm"; - if (sig.Length > 1) { - ret << "^" << sig.Length; - } - } - - if (sig.Mass > 0) { - if (mult) { - ret << '*'; - } - mult = true; - ret << "kg"; - if (sig.Mass > 1) { - ret << "^" << sig.Mass; - } - } - - if (sig.Time > 0) { - if (mult) { - ret << '*'; - } - mult = true; - ret << "s"; - if (sig.Time > 1) { - ret << "^" << sig.Time; - } - } - - if (sig.ElectricCurrent > 0) { - if (mult) { - ret << '*'; - } - mult = true; - ret << "A"; - if (sig.ElectricCurrent > 1) { - ret << "^" << sig.ElectricCurrent; - } - } - - if (sig.ThermodynamicTemperature > 0) { - if (mult) { - ret << '*'; - } - mult = true; - ret << "K"; - if (sig.ThermodynamicTemperature > 1) { - ret << "^" << sig.ThermodynamicTemperature; - } - } - - if (sig.AmountOfSubstance > 0) { - if (mult) { - ret << '*'; - } - mult = true; - ret << "mol"; - if (sig.AmountOfSubstance > 1) { - ret << "^" << sig.AmountOfSubstance; - } - } - - if (sig.LuminousIntensity > 0) { - if (mult) { - ret << '*'; - } - mult = true; - ret << "cd"; - if (sig.LuminousIntensity > 1) { - ret << "^" << sig.LuminousIntensity; - } - } - - if (sig.Angle > 0) { - if (mult) { - ret << '*'; - } - mult = true; - ret << "deg"; - if (sig.Angle > 1) { - ret << "^" << sig.Angle; - } - } - } - else { - ret << "1"; - } - - if (sig.Length < 0 || - sig.Mass < 0 || - sig.Time < 0 || - sig.ElectricCurrent < 0 || - sig.ThermodynamicTemperature < 0 || - sig.AmountOfSubstance < 0 || - sig.LuminousIntensity < 0 || - sig.Angle < 0 ) { - ret << "/"; - - int nnom = 0; - nnom += sig.Length < 0 ? 1 : 0; - nnom += sig.Mass < 0 ? 1 : 0; - nnom += sig.Time < 0 ? 1 : 0; - nnom += sig.ElectricCurrent < 0 ? 1 : 0; - nnom += sig.ThermodynamicTemperature < 0 ? 1 : 0; - nnom += sig.AmountOfSubstance < 0 ? 1 : 0; - nnom += sig.LuminousIntensity < 0 ? 1 : 0; - nnom += sig.Angle < 0 ? 1 : 0; - - if (nnom > 1) { - ret << '('; - } - - bool mult = false; - if (sig.Length < 0) { - ret << "mm"; - mult = true; - if (sig.Length < -1) { - ret << "^" << abs(sig.Length); - } - } - - if (sig.Mass < 0) { - if (mult) { - ret << '*'; - } - mult = true; - ret << "kg"; - if (sig.Mass < -1) { - ret << "^" << abs(sig.Mass); - } - } - - if (sig.Time < 0) { - if (mult) { - ret << '*'; - } - mult = true; - ret << "s"; - if (sig.Time < -1) { - ret << "^" << abs(sig.Time); - } - } - - if (sig.ElectricCurrent < 0) { - if (mult) { - ret << '*'; - } - mult = true; - ret << "A"; - if (sig.ElectricCurrent < -1) { - ret << "^" << abs(sig.ElectricCurrent); - } - } - - if (sig.ThermodynamicTemperature < 0) { - if (mult) { - ret << '*'; - } - mult = true; - ret << "K"; - if (sig.ThermodynamicTemperature < -1) { - ret << "^" << abs(sig.ThermodynamicTemperature); - } - } - - if (sig.AmountOfSubstance < 0) { - if (mult) { - ret << '*'; - } - mult = true; - ret << "mol"; - if (sig.AmountOfSubstance < -1) { - ret << "^" << abs(sig.AmountOfSubstance); - } - } - - if (sig.LuminousIntensity < 0) { - if (mult) { - ret << '*'; - } - mult = true; - ret << "cd"; - if (sig.LuminousIntensity < -1) { - ret << "^" << abs(sig.LuminousIntensity); - } - } - - if (sig.Angle < 0) { - if (mult) { - ret << '*'; - } - mult = true; - ret << "deg"; - if (sig.Angle < -1) { - ret << "^" << abs(sig.Angle); - } - } - - if (nnom > 1) { - ret << ')'; - } - } - - return ret.str(); +std::string Unit::representation() const +{ + auto name = getTypeString(); + auto inParen = fmt::format("Unit: {} ({})", getString(), fmt::join(_exps, ",")); + return name.empty() ? inParen : fmt::format("{} [{}]", inParen, name); } std::string Unit::getTypeString() const { - static std::array, 57> unitSpecs {{ - { Unit::Acceleration, "Acceleration" }, - { Unit::AmountOfSubstance, "AmountOfSubstance" }, - { Unit::Angle, "Angle" }, - { Unit::AngleOfFriction, "AngleOfFriction" }, - { Unit::Area, "Area" }, - { Unit::CurrentDensity, "CurrentDensity" }, - { Unit::Density, "Density" }, - { Unit::DissipationRate, "DissipationRate" }, - { Unit::DynamicViscosity, "DynamicViscosity" }, - { Unit::ElectricalCapacitance, "ElectricalCapacitance" }, - { Unit::ElectricalConductance, "ElectricalConductance" }, - { Unit::ElectricalConductivity, "ElectricalConductivity" }, - { Unit::ElectricalInductance, "ElectricalInductance" }, - { Unit::ElectricalResistance, "ElectricalResistance" }, - { Unit::ElectricCharge, "ElectricCharge" }, - { Unit::SurfaceChargeDensity, "SurfaceChargeDensity" }, - { Unit::VolumeChargeDensity, "VolumeChargeDensity" }, - { Unit::ElectricCurrent, "ElectricCurrent" }, - { Unit::ElectricPotential, "ElectricPotential" }, - { Unit::ElectromagneticPotential, "ElectromagneticPotential" }, - { Unit::Frequency, "Frequency" }, - { Unit::Force, "Force" }, - { Unit::HeatFlux, "HeatFlux" }, - { Unit::InverseArea, "InverseArea" }, - { Unit::InverseLength, "InverseLength" }, - { Unit::InverseVolume, "InverseVolume" }, - { Unit::KinematicViscosity, "KinematicViscosity" }, - { Unit::Length, "Length" }, - { Unit::LuminousIntensity, "LuminousIntensity" }, - { Unit::MagneticFieldStrength, "MagneticFieldStrength" }, - { Unit::MagneticFlux, "MagneticFlux" }, - { Unit::MagneticFluxDensity, "MagneticFluxDensity" }, - { Unit::Magnetization, "Magnetization" }, - { Unit::Mass, "Mass" }, - { Unit::Pressure, "Pressure" }, - { Unit::Power, "Power" }, - { Unit::ShearModulus, "ShearModulus" }, - { Unit::SpecificEnergy, "SpecificEnergy" }, - { Unit::SpecificHeat, "SpecificHeat" }, - { Unit::Stiffness, "Stiffness" }, - { Unit::StiffnessDensity, "StiffnessDensity" }, - { Unit::Stress, "Stress" }, - { Unit::Temperature, "Temperature" }, - { Unit::ThermalConductivity, "ThermalConductivity" }, - { Unit::ThermalExpansionCoefficient, "ThermalExpansionCoefficient" }, - { Unit::ThermalTransferCoefficient, "ThermalTransferCoefficient" }, - { Unit::TimeSpan, "TimeSpan" }, - { Unit::UltimateTensileStrength, "UltimateTensileStrength" }, - { Unit::VacuumPermittivity, "VacuumPermittivity" }, - { Unit::Velocity, "Velocity" }, - { Unit::Volume, "Volume" }, - { Unit::VolumeFlowRate, "VolumeFlowRate" }, - { Unit::VolumetricThermalExpansionCoefficient, "VolumetricThermalExpansionCoefficient" }, - { Unit::Work, "Work" }, - { Unit::YieldStrength, "YieldStrength" }, - { Unit::YoungsModulus, "YoungsModulus" }, - { Unit::Moment, "Moment" }, - }}; + if (_name.empty()) { + const auto spec = std::ranges::find(unitSpecs, _exps, &UnitSpec::exps); + return std::string(spec == unitSpecs.end() ? "" : spec->name); + } - const auto spec = - std::find_if(unitSpecs.begin(), unitSpecs.end(), [&](const auto& pair) { - return pair.first == *this; - }); - - if (spec == std::end(unitSpecs)) - return ""; - - return spec->second; + return std::string {_name.data(), _name.size()}; } -// SI base units -const Unit Unit::AmountOfSubstance (0, 0, 0, 0, 0, 1); -const Unit Unit::ElectricCurrent (0, 0, 0, 1); -const Unit Unit::Length (1); -const Unit Unit::LuminousIntensity (0, 0, 0, 0, 0, 0, 1); -const Unit Unit::Mass (0, 1); -const Unit Unit::Temperature (0, 0, 0, 0, 1); -const Unit Unit::TimeSpan (0, 0, 1); +std::pair, std::vector> Unit::nonZeroValsIndexes() const +{ + std::vector pos {}; + std::vector neg {}; -// all other units -const Unit Unit::Acceleration (1, 0, -2); -const Unit Unit::Angle (0, 0, 0, 0, 0, 0, 0, 1); -const Unit Unit::AngleOfFriction (0, 0, 0, 0, 0, 0, 0, 1); -const Unit Unit::Area (2); -const Unit Unit::CompressiveStrength (-1, 1, -2); -const Unit Unit::CurrentDensity (-2, 0, 0, 1); -const Unit Unit::Density (-3, 1); -const Unit Unit::DissipationRate (2, 0, -3); // https://cfd-online.com/Wiki/Turbulence_dissipation_rate -const Unit Unit::DynamicViscosity (-1, 1, -1); -const Unit Unit::ElectricalCapacitance (-2, -1, 4, 2); -const Unit Unit::ElectricalConductance (-2, -1, 3, 2); -const Unit Unit::ElectricalConductivity (-3, -1, 3, 2); -const Unit Unit::ElectricalInductance (2, 1, -2, -2); -const Unit Unit::ElectricalResistance (2, 1, -3, -2); -const Unit Unit::ElectricCharge (0, 0, 1, 1); -const Unit Unit::SurfaceChargeDensity (-2, 0, 1, 1); -const Unit Unit::VolumeChargeDensity (-3, 0, 1, 1); -const Unit Unit::ElectricPotential (2, 1, -3, -1); -const Unit Unit::ElectromagneticPotential (1, 1, -2, -1); -const Unit Unit::Force (1, 1, -2); -const Unit Unit::Frequency (0, 0, -1); -const Unit Unit::HeatFlux (0, 1, -3, 0, 0); -const Unit Unit::InverseArea (-2, 0, 0); -const Unit Unit::InverseLength (-1, 0, 0); -const Unit Unit::InverseVolume (-3, 0, 0); -const Unit Unit::KinematicViscosity (2, 0, -1); -const Unit Unit::MagneticFieldStrength (-1,0,0,1); -const Unit Unit::MagneticFlux (2,1,-2,-1); -const Unit Unit::MagneticFluxDensity (0,1,-2,-1); -const Unit Unit::Magnetization (-1,0,0,1); -const Unit Unit::Moment (2, 1, -2); -const Unit Unit::Pressure (-1,1,-2); -const Unit Unit::Power (2, 1, -3); -const Unit Unit::ShearModulus (-1,1,-2); -const Unit Unit::SpecificEnergy (2, 0, -2); -const Unit Unit::SpecificHeat (2, 0, -2, 0, -1); -const Unit Unit::Stiffness (0, 1, -2); -const Unit Unit::StiffnessDensity (-2, 1, -2); -const Unit Unit::Stress (-1,1,-2); -const Unit Unit::ThermalConductivity (1, 1, -3, 0, -1); -const Unit Unit::ThermalExpansionCoefficient(0, 0, 0, 0, -1); -const Unit Unit::ThermalTransferCoefficient (0, 1, -3, 0, -1); -const Unit Unit::UltimateTensileStrength (-1,1,-2); -const Unit Unit::VacuumPermittivity (-3, -1, 4, 2); -const Unit Unit::Velocity (1, 0, -1); -const Unit Unit::Volume (3); -const Unit Unit::VolumeFlowRate (3, 0, -1); -const Unit Unit::VolumetricThermalExpansionCoefficient(0, 0, 0, 0, -1); -const Unit Unit::Work (2, 1, -2); -const Unit Unit::YieldStrength (-1,1,-2); -const Unit Unit::YoungsModulus (-1,1,-2); -// clang-format on + auto posNeg = [&, index {0}](auto val) mutable { + if (val != 0) { + (val > 0 ? pos : neg).push_back(index); + } + ++index; + }; + + std::ranges::for_each(_exps, posNeg); + + return {pos, neg}; +} + + +constexpr Unit make(const std::string_view name) +{ + if (const auto spec = std::ranges::find(unitSpecs, name, &UnitSpec::name); + spec != unitSpecs.end()) { + return Unit {spec->exps, spec->name}; + } + throw NameError("Invalid unit name"); +} + +// clang-format off +constexpr Unit Unit::One = make("1" ); + +constexpr Unit Unit::Length = make("Length" ); +constexpr Unit Unit::Mass = make("Mass" ); +constexpr Unit Unit::TimeSpan = make("TimeSpan" ); +constexpr Unit Unit::ElectricCurrent = make("ElectricCurrent" ); +constexpr Unit Unit::Temperature = make("Temperature" ); +constexpr Unit Unit::AmountOfSubstance = make("AmountOfSubstance" ); +constexpr Unit Unit::LuminousIntensity = make("LuminousIntensity" ); +constexpr Unit Unit::Angle = make("Angle" ); + +constexpr Unit Unit::Acceleration = make("Acceleration" ); +constexpr Unit Unit::AngleOfFriction = make("Angle" ); +constexpr Unit Unit::Area = make("Area" ); +constexpr Unit Unit::CompressiveStrength = make("Pressure" ); +constexpr Unit Unit::CurrentDensity = make("CurrentDensity" ); +constexpr Unit Unit::Density = make("Density" ); +constexpr Unit Unit::DissipationRate = make("DissipationRate" ); +constexpr Unit Unit::DynamicViscosity = make("DynamicViscosity" ); +constexpr Unit Unit::ElectricalCapacitance = make("ElectricalCapacitance" ); +constexpr Unit Unit::ElectricalConductance = make("ElectricalConductance" ); +constexpr Unit Unit::ElectricalConductivity = make("ElectricalConductivity" ); +constexpr Unit Unit::ElectricalInductance = make("ElectricalInductance" ); +constexpr Unit Unit::ElectricalResistance = make("ElectricalResistance" ); +constexpr Unit Unit::ElectricCharge = make("ElectricCharge" ); +constexpr Unit Unit::ElectricPotential = make("ElectricPotential" ); +constexpr Unit Unit::ElectromagneticPotential = make("ElectromagneticPotential" ); +constexpr Unit Unit::Force = make("Force" ); +constexpr Unit Unit::Frequency = make("Frequency" ); +constexpr Unit Unit::HeatFlux = make("HeatFlux" ); +constexpr Unit Unit::InverseArea = make("InverseArea" ); +constexpr Unit Unit::InverseLength = make("InverseLength" ); +constexpr Unit Unit::InverseVolume = make("InverseVolume" ); +constexpr Unit Unit::KinematicViscosity = make("KinematicViscosity" ); +constexpr Unit Unit::MagneticFieldStrength = make("Magnetization" ); +constexpr Unit Unit::MagneticFlux = make("MagneticFlux" ); +constexpr Unit Unit::MagneticFluxDensity = make("MagneticFluxDensity" ); +constexpr Unit Unit::Magnetization = make("MagneticFieldStrength" ); +constexpr Unit Unit::Moment = make("Moment" ); +constexpr Unit Unit::Pressure = make("Pressure" ); +constexpr Unit Unit::Power = make("Power" ); +constexpr Unit Unit::ShearModulus = make("Pressure" ); +constexpr Unit Unit::SpecificEnergy = make("SpecificEnergy" ); +constexpr Unit Unit::SpecificHeat = make("SpecificHeat" ); +constexpr Unit Unit::Stiffness = make("Stiffness" ); +constexpr Unit Unit::StiffnessDensity = make("StiffnessDensity" ); +constexpr Unit Unit::Stress = make("Pressure" ); +constexpr Unit Unit::SurfaceChargeDensity = make("SurfaceChargeDensity" ); +constexpr Unit Unit::ThermalConductivity = make("ThermalConductivity" ); +constexpr Unit Unit::ThermalExpansionCoefficient = make("ThermalExpansionCoefficient" ); +constexpr Unit Unit::ThermalTransferCoefficient = make("ThermalTransferCoefficient" ); +constexpr Unit Unit::UltimateTensileStrength = make("Pressure" ); +constexpr Unit Unit::VacuumPermittivity = make("VacuumPermittivity" ); +constexpr Unit Unit::Velocity = make("Velocity" ); +constexpr Unit Unit::Volume = make("Volume" ); +constexpr Unit Unit::VolumeChargeDensity = make("VolumeChargeDensity" ); +constexpr Unit Unit::VolumeFlowRate = make("VolumeFlowRate" ); +constexpr Unit Unit::VolumetricThermalExpansionCoefficient = make("ThermalExpansionCoefficient" ); +constexpr Unit Unit::Work = make("Work" ); +constexpr Unit Unit::YieldStrength = make("Pressure" ); +constexpr Unit Unit::YoungsModulus = make("Pressure" ); diff --git a/src/Base/Unit.h b/src/Base/Unit.h index 77b2273b3d..36d93d6e45 100644 --- a/src/Base/Unit.h +++ b/src/Base/Unit.h @@ -1,181 +1,178 @@ -/*************************************************************************** - * Copyright (c) 2011 Jürgen Riegel * - * * - * This file is part of the FreeCAD CAx development system. * - * * - * This library is free software; you can redistribute it and/or * - * modify it under the terms of the GNU Library General Public * - * License as published by the Free Software Foundation; either * - * version 2 of the License, or (at your option) any later version. * - * * - * This library is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU Library General Public License for more details. * - * * - * You should have received a copy of the GNU Library General Public * - * License along with this library; see the file COPYING.LIB. If not, * - * write to the Free Software Foundation, Inc., 59 Temple Place, * - * Suite 330, Boston, MA 02111-1307, USA * - * * +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * ***************************************************************************/ - #ifndef BASE_Unit_H #define BASE_Unit_H +#include #include +#include #include +#include +#include + #include +#include "Exception.h" + namespace Base { -/** - * The Unit class. - */ -class BaseExport Unit +constexpr auto unitSymbols = + std::to_array({"mm", "kg", "s", "A", "K", "mol", "cd", "deg"}); + +constexpr auto unitNumExponents {unitSymbols.size()}; +using UnitExponents = std::array; + +constexpr auto unitExponentLimit {8}; + +class BaseExport Unit final { public: - /// default constructor - explicit Unit(int8_t Length, - int8_t Mass = 0, - int8_t Time = 0, - int8_t ElectricCurrent = 0, - int8_t ThermodynamicTemperature = 0, - int8_t AmountOfSubstance = 0, - int8_t LuminousIntensity = 0, - int8_t Angle = 0); - Unit(); - Unit(const Unit&) = default; - Unit(Unit&&) = default; - explicit Unit(const std::string& expr); - /// Destruction - ~Unit() = default; + Unit() = default; - /** Operators. */ - //@{ - inline Unit& operator*=(const Unit& that); - inline Unit& operator/=(const Unit& that); - int operator[](int index) const; + explicit constexpr Unit(const UnitExponents exps, const std::string_view name = "") + : _exps {exps} + , _name {name} + { + checkRange(); + } + + /// helper constructor to ease Unit construction from Python + explicit Unit(const int length, + const int mass = 0, + const int time = 0, + const int electricCurrent = 0, + const int thermodynamicTemperature = 0, + const int amountOfSubstance = 0, + const int luminousIntensity = 0, + const int angle = 0); + + bool operator==(const Unit&) const; + bool operator!=(const Unit& that) const; + Unit& operator*=(const Unit& that); + Unit& operator/=(const Unit& that); Unit operator*(const Unit&) const; Unit operator/(const Unit&) const; - bool operator==(const Unit&) const; - bool operator!=(const Unit& that) const + + [[nodiscard]] Unit pow(const double exp) const; + [[nodiscard]] Unit root(const uint8_t num) const; + + [[nodiscard]] UnitExponents exponents() const; + [[nodiscard]] int length() const; + + [[nodiscard]] std::string getString() const; // E.g. kg, mm^2, mm*kg/s^2 + [[nodiscard]] std::string getTypeString() const; // E.g. "Area", "Length", "Pressure" + [[nodiscard]] std::string representation() const; // E.g. "Unit: mm (1,0,0,0,0,0,0,0) [Length]" + + Unit sqrt() const { - return !(*this == that); + return root(2); + } + Unit cbrt() const + { + return root(3); } - Unit& operator=(const Unit&) = default; - Unit& operator=(Unit&&) = default; - Unit pow(double exp) const; - Unit sqrt() const; - Unit cbrt() const; - //@} - int length() const; - int mass() const; - int time() const; - int electricCurrent() const; - int thermodynamicTemperature() const; - int amountOfSubstance() const; - int luminousIntensity() const; - int angle() const; - bool isEmpty() const; - std::string getString() const; - /// get the type as an string such as "Area", "Length" or "Pressure". - std::string getTypeString() const; +private: + UnitExponents _exps {}; + std::string_view _name; - /** Predefined Unit types. */ - //@{ - /// Length unit - static const Unit Length; - /// Mass unit - static const Unit Mass; + constexpr void checkRange() + { + for (const auto exp : _exps) { + if (exp >= unitExponentLimit) { + throw OverflowError("Unit exponent overflow"); + } + if (exp < -unitExponentLimit) { + throw UnderflowError("Unit exponent underflow"); + } + } + } - /// Angle + /** Returns posIndexes, negIndexes*/ + std::pair, std::vector> nonZeroValsIndexes() const; + +public: + static const Unit Acceleration; + static const Unit AmountOfSubstance; static const Unit Angle; static const Unit AngleOfFriction; - - static const Unit Density; - static const Unit Area; - static const Unit Volume; - static const Unit TimeSpan; - static const Unit Frequency; - static const Unit Velocity; - static const Unit Acceleration; - static const Unit Temperature; - + static const Unit CompressiveStrength; static const Unit CurrentDensity; + static const Unit Density; + static const Unit DissipationRate; + static const Unit DynamicViscosity; + static const Unit ElectricalCapacitance; + static const Unit ElectricalConductance; + static const Unit ElectricalConductivity; + static const Unit ElectricalInductance; + static const Unit ElectricalResistance; + static const Unit ElectricCharge; static const Unit ElectricCurrent; static const Unit ElectricPotential; - static const Unit ElectricCharge; - static const Unit SurfaceChargeDensity; - static const Unit VolumeChargeDensity; + static const Unit ElectromagneticPotential; + static const Unit Force; + static const Unit Frequency; + static const Unit HeatFlux; + static const Unit InverseArea; + static const Unit InverseLength; + static const Unit InverseVolume; + static const Unit KinematicViscosity; + static const Unit Length; + static const Unit LuminousIntensity; static const Unit MagneticFieldStrength; static const Unit MagneticFlux; static const Unit MagneticFluxDensity; static const Unit Magnetization; - static const Unit ElectricalCapacitance; - static const Unit ElectricalInductance; - static const Unit ElectricalConductance; - static const Unit ElectricalResistance; - static const Unit ElectricalConductivity; - static const Unit ElectromagneticPotential; - static const Unit AmountOfSubstance; - static const Unit LuminousIntensity; - - // Pressure - static const Unit CompressiveStrength; + static const Unit Mass; + static const Unit Moment; + static const Unit One; static const Unit Pressure; + static const Unit Power; static const Unit ShearModulus; - static const Unit Stress; - static const Unit UltimateTensileStrength; - static const Unit YieldStrength; - static const Unit YoungsModulus; - + static const Unit SpecificEnergy; + static const Unit SpecificHeat; static const Unit Stiffness; static const Unit StiffnessDensity; - - static const Unit Force; - static const Unit Work; - static const Unit Power; - static const Unit Moment; - - static const Unit SpecificEnergy; + static const Unit Stress; + static const Unit SurfaceChargeDensity; + static const Unit Temperature; + static const Unit TimeSpan; static const Unit ThermalConductivity; static const Unit ThermalExpansionCoefficient; - static const Unit VolumetricThermalExpansionCoefficient; - static const Unit SpecificHeat; static const Unit ThermalTransferCoefficient; - static const Unit HeatFlux; - static const Unit DynamicViscosity; - static const Unit KinematicViscosity; + static const Unit UltimateTensileStrength; static const Unit VacuumPermittivity; + static const Unit Velocity; + static const Unit Volume; + static const Unit VolumeChargeDensity; static const Unit VolumeFlowRate; - static const Unit DissipationRate; - - static const Unit InverseLength; - static const Unit InverseArea; - static const Unit InverseVolume; - - //@} -private: - uint32_t Val; + static const Unit VolumetricThermalExpansionCoefficient; + static const Unit Work; + static const Unit YieldStrength; + static const Unit YoungsModulus; }; -inline Unit& Unit::operator*=(const Unit& that) -{ - *this = *this * that; - return *this; -} - -inline Unit& Unit::operator/=(const Unit& that) -{ - *this = *this / that; - return *this; -} - } // namespace Base #endif // BASE_Unit_H diff --git a/src/Base/UnitPyImp.cpp b/src/Base/UnitPyImp.cpp index dd174fbf8e..35edc8450e 100644 --- a/src/Base/UnitPyImp.cpp +++ b/src/Base/UnitPyImp.cpp @@ -21,6 +21,10 @@ ***************************************************************************/ #include "PreCompiled.h" +#ifndef _PreComp_ +#include +#include +#endif #include "Unit.h" @@ -35,25 +39,7 @@ using namespace Base; // returns a string which represents the object e.g. when printed in python std::string UnitPy::representation() const { - std::stringstream ret; - Unit* self = getUnitPtr(); - - ret << "Unit: "; - ret << self->getString() << " ("; - ret << (*self).length() << ","; - ret << (*self).mass() << ","; - ret << (*self).time() << ","; - ret << (*self).electricCurrent() << ","; - ret << (*self).thermodynamicTemperature() << ","; - ret << (*self).amountOfSubstance() << ","; - ret << (*self).luminousIntensity() << ","; - ret << (*self).angle() << ")"; - - std::string type = self->getTypeString(); - if (!type.empty()) { - ret << " [" << type << "]"; - } - return ret.str(); + return getUnitPtr()->representation(); } PyObject* UnitPy::PyMake(PyTypeObject* /*unused*/, PyObject* /*unused*/, PyObject* /*unused*/) @@ -98,14 +84,14 @@ int UnitPy::PyInit(PyObject* args, PyObject* /*kwd*/) } PyErr_Clear(); // set by PyArg_ParseTuple() - int i1 = 0; - int i2 = 0; - int i3 = 0; - int i4 = 0; - int i5 = 0; - int i6 = 0; - int i7 = 0; - int i8 = 0; + int i1 {0}; + int i2 {0}; + int i3 {0}; + int i4 {0}; + int i5 {0}; + int i6 {0}; + int i7 {0}; + int i8 {0}; if (PyArg_ParseTuple(args, "|iiiiiiii", &i1, &i2, &i3, &i4, &i5, &i6, &i7, &i8)) { try { *self = Unit(i1, i2, i3, i4, i5, i6, i7, i8); @@ -115,6 +101,10 @@ int UnitPy::PyInit(PyObject* args, PyObject* /*kwd*/) PyErr_SetString(PyExc_OverflowError, e.what()); return -1; } + catch (const UnderflowError& e) { + PyErr_SetString(PyExc_OverflowError, e.what()); + return -1; + } } PyErr_SetString(PyExc_TypeError, "Either string, (float,8 ints), Unit() or Quantity()"); @@ -213,13 +203,11 @@ Py::String UnitPy::getType() const Py::Tuple UnitPy::getSignature() const { - Py::Tuple tuple(8); - Unit* self = getUnitPtr(); - - for (auto i = 0; i < tuple.size(); i++) { - tuple.setItem(i, Py::Long((*self)[i])); - } - + Py::Tuple tuple {unitNumExponents}; + auto exps = getUnitPtr()->exponents(); + std::ranges::for_each(exps, [&, pos {0}](auto exp) mutable { + tuple.setItem(pos++, Py::Long {exp}); + }); return tuple; } diff --git a/src/Base/UnitsSchema.cpp b/src/Base/UnitsSchema.cpp index 1fbc4e0169..921d245dc5 100644 --- a/src/Base/UnitsSchema.cpp +++ b/src/Base/UnitsSchema.cpp @@ -53,22 +53,20 @@ std::string UnitsSchema::translate(const Quantity& quant) const std::string UnitsSchema::translate(const Quantity& quant, double& factor, std::string& unitString) const { + // Use defaults without schema-level translation. + factor = 1.0; + unitString = quant.getUnit().getString(); + if (spec.translationSpecs.empty()) { - return toLocale(quant, 1.0, unitString); + return toLocale(quant, factor, unitString); } const auto unitName = quant.getUnit().getTypeString(); - - if (spec.translationSpecs.count(unitName) == 0) { - // no schema-level translation. Use defaults. - factor = 1.0; - unitString = quant.getUnit().getString(); - + if (!spec.translationSpecs.contains(unitName)) { return toLocale(quant, factor, unitString); } const auto value = quant.getValue(); - auto isSuitable = [&](const UnitTranslationSpec& row) { return row.threshold > value || row.threshold == 0; // zero indicates default }; @@ -81,7 +79,7 @@ UnitsSchema::translate(const Quantity& quant, double& factor, std::string& unitS } if (unitSpec->factor == 0) { - return UnitsSchemasData::runSpecial(unitSpec->unitString, value); + return UnitsSchemasData::runSpecial(unitSpec->unitString, value, factor, unitString); } factor = unitSpec->factor; @@ -96,20 +94,17 @@ UnitsSchema::toLocale(const Quantity& quant, const double factor, const std::str QLocale Lc; const QuantityFormat& format = quant.getFormat(); if (format.option != QuantityFormat::None) { - int opt = format.option; - Lc.setNumberOptions(static_cast(opt)); + Lc.setNumberOptions(static_cast(format.option)); } - std::string valueString = - Lc.toString((quant.getValue() / factor), format.toFormat(), format.precision).toStdString(); + auto valueString = + Lc.toString(quant.getValue() / factor, format.toFormat(), format.precision).toStdString(); - return fmt::format("{}{}{}", - valueString, - unitString.empty() || unitString == "°" || unitString == "″" - || unitString == "′" || unitString == "\"" || unitString == "'" - ? "" - : " ", - unitString); + auto notUnit = [](auto s) { + return s.empty() || s == "°" || s == "″" || s == "′" || s == "\"" || s == "'"; + }; + + return fmt::format("{}{}{}", valueString, notUnit(unitString) ? "" : " ", unitString); } bool UnitsSchema::isMultiUnitLength() const diff --git a/src/Base/UnitsSchemasData.h b/src/Base/UnitsSchemasData.h index 8f937c1a50..45f25bdc98 100644 --- a/src/Base/UnitsSchemasData.h +++ b/src/Base/UnitsSchemasData.h @@ -733,17 +733,27 @@ inline std::string toDms(const double value) * Special functions caller */ -inline const std::map> specials // clang-format off +// clang-format off +inline const std::map> specials { { - { "toDMS" , [](const double val) { return toDms(val); }}, - { "toFractional" , [](const double val) { return toFractional(val); }} + { "toDMS" , [](const double val, double& factor, std::string& unitString) { + factor = 1.0; + unitString = "deg"; + return toDms(val); + }}, + { "toFractional" , [](const double val, double& factor, std::string& unitString) { + factor = 25.4; + unitString = "in"; + return toFractional(val); + }} } }; // clang-format on -inline std::string runSpecial(const std::string& name, const double value) +inline std::string +runSpecial(const std::string& name, const double value, double& factor, std::string& unitString) { - return specials.contains(name) ? specials.at(name)(value) : ""; + return specials.contains(name) ? specials.at(name)(value, factor, unitString) : ""; } diff --git a/src/Gui/Dialogs/DlgExpressionInput.cpp b/src/Gui/Dialogs/DlgExpressionInput.cpp index 337cc24f02..28680bcfc3 100644 --- a/src/Gui/Dialogs/DlgExpressionInput.cpp +++ b/src/Gui/Dialogs/DlgExpressionInput.cpp @@ -298,14 +298,14 @@ void DlgExpressionInput::checkExpression(const QString& text) } auto msg = value.getUserString(); - if (!impliedUnit.isEmpty()) { - if (!value.getUnit().isEmpty() && value.getUnit() != impliedUnit) + if (impliedUnit != Base::Unit::One) { + if (!value.isDimensionless() && value.getUnit() != impliedUnit) throw Base::UnitsMismatchError("Unit mismatch between result and required unit"); value.setUnit(impliedUnit); } - else if (!value.getUnit().isEmpty()) { + else if (!value.isDimensionless()) { msg += " (Warning: unit discarded)"; QPalette p(ui->msg->palette()); diff --git a/src/Gui/Dialogs/DlgUnitsCalculatorImp.cpp b/src/Gui/Dialogs/DlgUnitsCalculatorImp.cpp index 65947b6179..8e9ed1edc4 100644 --- a/src/Gui/Dialogs/DlgUnitsCalculatorImp.cpp +++ b/src/Gui/Dialogs/DlgUnitsCalculatorImp.cpp @@ -30,9 +30,13 @@ #include "Dialogs/DlgUnitsCalculatorImp.h" #include "ui_DlgUnitsCalculator.h" +#include #include using namespace Gui::Dialog; +using Base::Quantity; +using Base::Unit; +using Base::UnitsApi; /* TRANSLATOR Gui::Dialog::DlgUnitsCalculator */ @@ -55,7 +59,7 @@ DlgUnitsCalculator::DlgUnitsCalculator(QWidget* parent, Qt::WindowFlags fl) auto addItem = [&, index {0}](const auto& item) mutable { ui->comboBoxScheme->addItem(QString::fromStdString(item), index++); }; - auto descriptions = Base::UnitsApi::getDescriptions(); + auto descriptions = UnitsApi::getDescriptions(); std::for_each(descriptions.begin(), descriptions.end(), addItem); // clang-format off @@ -65,7 +69,7 @@ DlgUnitsCalculator::DlgUnitsCalculator(QWidget* parent, Qt::WindowFlags fl) this, &DlgUnitsCalculator::onComboBoxSchemeActivated); connect(ui->spinBoxDecimals, qOverload(&QSpinBox::valueChanged), this, &DlgUnitsCalculator::onSpinBoxDecimalsValueChanged); - connect(ui->ValueInput, qOverload(&InputField::valueChanged), + connect(ui->ValueInput, qOverload(&InputField::valueChanged), this, &DlgUnitsCalculator::valueChanged); connect(ui->ValueInput, &InputField::returnPressed, this, &DlgUnitsCalculator::returnPressed); @@ -86,31 +90,27 @@ DlgUnitsCalculator::DlgUnitsCalculator(QWidget* parent, Qt::WindowFlags fl) ui->ValueInput->setText(QStringLiteral("1 cm")); ui->UnitInput->setText(QStringLiteral("in")); - units << Base::Unit::Acceleration << Base::Unit::AmountOfSubstance << Base::Unit::Angle - << Base::Unit::Area << Base::Unit::Density << Base::Unit::CurrentDensity - << Base::Unit::DissipationRate << Base::Unit::DynamicViscosity - << Base::Unit::ElectricalCapacitance << Base::Unit::ElectricalInductance - << Base::Unit::ElectricalConductance << Base::Unit::ElectricalResistance - << Base::Unit::ElectricalConductivity << Base::Unit::ElectricCharge - << Base::Unit::ElectricCurrent << Base::Unit::ElectricPotential << Base::Unit::Force - << Base::Unit::Frequency << Base::Unit::HeatFlux << Base::Unit::InverseArea - << Base::Unit::InverseLength << Base::Unit::InverseVolume - << Base::Unit::KinematicViscosity << Base::Unit::Length << Base::Unit::LuminousIntensity - << Base::Unit::Mass << Base::Unit::MagneticFieldStrength << Base::Unit::MagneticFlux - << Base::Unit::MagneticFluxDensity << Base::Unit::Magnetization << Base::Unit::Power - << Base::Unit::Pressure << Base::Unit::SpecificEnergy << Base::Unit::SpecificHeat - << Base::Unit::Stiffness << Base::Unit::Temperature << Base::Unit::ThermalConductivity - << Base::Unit::ThermalExpansionCoefficient << Base::Unit::ThermalTransferCoefficient - << Base::Unit::TimeSpan << Base::Unit::VacuumPermittivity << Base::Unit::Velocity - << Base::Unit::Volume << Base::Unit::VolumeFlowRate - << Base::Unit::VolumetricThermalExpansionCoefficient << Base::Unit::Work; - for (const Base::Unit& it : units) { + units << Unit::Acceleration << Unit::AmountOfSubstance << Unit::Angle << Unit::Area + << Unit::Density << Unit::CurrentDensity << Unit::DissipationRate + << Unit::DynamicViscosity << Unit::ElectricalCapacitance << Unit::ElectricalInductance + << Unit::ElectricalConductance << Unit::ElectricalResistance + << Unit::ElectricalConductivity << Unit::ElectricCharge << Unit::ElectricCurrent + << Unit::ElectricPotential << Unit::Force << Unit::Frequency << Unit::HeatFlux + << Unit::InverseArea << Unit::InverseLength << Unit::InverseVolume + << Unit::KinematicViscosity << Unit::Length << Unit::LuminousIntensity << Unit::Mass + << Unit::MagneticFieldStrength << Unit::MagneticFlux << Unit::MagneticFluxDensity + << Unit::Magnetization << Unit::Power << Unit::Pressure << Unit::SpecificEnergy + << Unit::SpecificHeat << Unit::Stiffness << Unit::Temperature << Unit::ThermalConductivity + << Unit::ThermalExpansionCoefficient << Unit::ThermalTransferCoefficient << Unit::TimeSpan + << Unit::VacuumPermittivity << Unit::Velocity << Unit::Volume << Unit::VolumeFlowRate + << Unit::VolumetricThermalExpansionCoefficient << Unit::Work; + for (const Unit& it : units) { ui->unitsBox->addItem(QString::fromStdString(it.getTypeString())); } ui->quantitySpinBox->setValue(1.0); ui->quantitySpinBox->setUnit(units.front()); - ui->spinBoxDecimals->setValue(Base::UnitsApi::getDecimals()); + ui->spinBoxDecimals->setValue(UnitsApi::getDecimals()); } /** Destroys the object and frees any allocated resources */ @@ -132,27 +132,32 @@ void DlgUnitsCalculator::textChanged(QString unit) valueChanged(actValue); } -void DlgUnitsCalculator::valueChanged(const Base::Quantity& quant) +void DlgUnitsCalculator::valueChanged(const Quantity& quant) { + std::string unitTypeStr; + try { + unitTypeStr = + Quantity::parse(ui->UnitInput->text().toStdString()).getUnit().getTypeString(); + } + catch (const Base::ParserError&) { + } // first check the unit, if it is invalid, getTypeString() outputs an empty string - // explicitly check for "ee" like in "eeV" because this would trigger an exception in Base::Unit + // explicitly check for "ee" like in "eeV" because this would trigger an exception in Unit // since it expects then a scientific notation number like "1e3" - if ((ui->UnitInput->text().mid(0, 2) == QStringLiteral("ee")) - || Base::Unit(ui->UnitInput->text().toStdString()).getTypeString().empty()) { + if ((ui->UnitInput->text().mid(0, 2) == QStringLiteral("ee")) || unitTypeStr.empty()) { ui->ValueOutput->setText( QStringLiteral("%1 %2").arg(tr("unknown unit:"), ui->UnitInput->text())); ui->pushButton_Copy->setEnabled(false); } else { // the unit is valid // we can only convert units of the same type, thus check - if (Base::Unit(ui->UnitInput->text().toStdString()).getTypeString() - != quant.getUnit().getTypeString()) { + if (unitTypeStr != quant.getUnit().getTypeString()) { ui->ValueOutput->setText(tr("unit mismatch")); ui->pushButton_Copy->setEnabled(false); } else { // the unit is valid and has the same type double convertValue = - Base::Quantity::parse("1" + ui->UnitInput->text().toStdString()).getValue(); + Quantity::parse("1" + ui->UnitInput->text().toStdString()).getValue(); // we got now e.g. for "1 in" the value '25.4' because 1 in = 25.4 mm // the result is now just quant / convertValue because the input is always in a base // unit (an input of "1 cm" will immediately be converted to "10 mm" by Gui::InputField @@ -164,7 +169,7 @@ void DlgUnitsCalculator::valueChanged(const Base::Quantity& quant) // "10 um" in "in", thus only if value > 0.005 because FC's default are 2 decimals QString val = QLocale().toString(value, 'g'); if (!val.contains(QChar::fromLatin1('e')) && (value > 0.005)) { - val = QLocale().toString(value, 'f', Base::UnitsApi::getDecimals()); + val = QLocale().toString(value, 'f', UnitsApi::getDecimals()); } // create the output string QString out = QStringLiteral("%1 %2").arg(val, ui->UnitInput->text()); @@ -201,13 +206,13 @@ void DlgUnitsCalculator::onUnitsBoxActivated(int index) { // SI units use [m], not [mm] for lengths // - Base::Quantity q = ui->quantitySpinBox->value(); + Quantity q = ui->quantitySpinBox->value(); int32_t old = q.getUnit().length(); double value = q.getValue(); - Base::Unit unit = units[index]; + Unit unit = units[index]; int32_t len = unit.length(); - ui->quantitySpinBox->setValue(Base::Quantity(value * std::pow(10.0, 3 * (len - old)), unit)); + ui->quantitySpinBox->setValue(Quantity(value * std::pow(10.0, 3 * (len - old)), unit)); } void DlgUnitsCalculator::onComboBoxSchemeActivated(int index) diff --git a/src/Gui/InputField.cpp b/src/Gui/InputField.cpp index e203e0bc69..ed418866a3 100644 --- a/src/Gui/InputField.cpp +++ b/src/Gui/InputField.cpp @@ -278,11 +278,11 @@ void InputField::newInput(const QString & text) return; } - if (res.getUnit().isEmpty()) + if (res.isDimensionless()) res.setUnit(this->actUnit); // check if unit fits! - if(!actUnit.isEmpty() && !res.getUnit().isEmpty() && actUnit != res.getUnit()){ + if (actUnit != Unit::One && !res.isDimensionless() && actUnit != res.getUnit()){ if (iconLabel->isHidden()) { iconLabel->setVisible(true); } @@ -644,7 +644,7 @@ void InputField::focusInEvent(QFocusEvent *event) void InputField::focusOutEvent(QFocusEvent *event) { try { - if (Quantity::parse(this->text().toStdString()).getUnit().isEmpty()) { + if (Quantity::parse(this->text().toStdString()).isDimensionless()) { // if user didn't enter a unit, we virtually compensate // the multiplication factor induced by user unit system double factor; diff --git a/src/Gui/QuantitySpinBox.cpp b/src/Gui/QuantitySpinBox.cpp index 59f88073bc..4631b45c75 100644 --- a/src/Gui/QuantitySpinBox.cpp +++ b/src/Gui/QuantitySpinBox.cpp @@ -224,7 +224,7 @@ public: ok = parseString(copy, res, value, path); // If result does not have unit: add default unit - if (res.getUnit().isEmpty()){ + if (res.isDimensionless()) { res.setUnit(unit); } diff --git a/src/Gui/propertyeditor/PropertyItem.cpp b/src/Gui/propertyeditor/PropertyItem.cpp index 8964d29ac8..4bf7a42bb1 100644 --- a/src/Gui/propertyeditor/PropertyItem.cpp +++ b/src/Gui/propertyeditor/PropertyItem.cpp @@ -1786,15 +1786,11 @@ void PropertyVectorDistanceItem::setValue(const QVariant& variant) } const Base::Vector3d& value = variant.value(); - Base::Quantity x = Base::Quantity(value.x, Base::Unit::Length); - Base::Quantity y = Base::Quantity(value.y, Base::Unit::Length); - Base::Quantity z = Base::Quantity(value.z, Base::Unit::Length); - Base::QuantityFormat format(Base::QuantityFormat::Default, highPrec); std::string val = fmt::format("({}, {}, {})", - Base::UnitsApi::toNumber(x, format), - Base::UnitsApi::toNumber(y, format), - Base::UnitsApi::toNumber(z, format)); + Base::UnitsApi::toNumber(value.x, format), + Base::UnitsApi::toNumber(value.y, format), + Base::UnitsApi::toNumber(value.z, format)); setPropertyValue(val); } diff --git a/src/Mod/Fem/App/FemPostPipeline.cpp b/src/Mod/Fem/App/FemPostPipeline.cpp index 2689b66278..70697e5bfe 100644 --- a/src/Mod/Fem/App/FemPostPipeline.cpp +++ b/src/Mod/Fem/App/FemPostPipeline.cpp @@ -611,12 +611,11 @@ Base::Unit FemPostPipeline::getFrameUnit() vtkAbstractArray* TimeInfo = multiblock->GetFieldData()->GetAbstractArray("TimeInfo"); if (!TimeInfo->IsA("vtkStringArray") || TimeInfo->GetNumberOfTuples() < 2) { - // units cannot be undefined, so use time return Base::Unit::TimeSpan; } - - return Base::Unit(vtkStringArray::SafeDownCast(TimeInfo)->GetValue(1)); + auto qty = Base::Quantity(0, vtkStringArray::SafeDownCast(TimeInfo)->GetValue(1)); + return qty.getUnit(); } std::vector FemPostPipeline::getFrameValues() diff --git a/src/Mod/Sketcher/Gui/EditDatumDialog.cpp b/src/Mod/Sketcher/Gui/EditDatumDialog.cpp index e04bd45b48..fd8337da31 100644 --- a/src/Mod/Sketcher/Gui/EditDatumDialog.cpp +++ b/src/Mod/Sketcher/Gui/EditDatumDialog.cpp @@ -201,8 +201,8 @@ int EditDatumDialog::exec(bool atCursor) void EditDatumDialog::accepted() { Base::Quantity newQuant = ui_ins_datum->labelEdit->value(); - if (newQuant.isQuantity() || (Constr->Type == Sketcher::SnellsLaw && newQuant.isDimensionless()) - || (Constr->Type == Sketcher::Weight && newQuant.isDimensionless())) { + if (Constr->Type == Sketcher::SnellsLaw || Constr->Type == Sketcher::Weight + || !newQuant.isDimensionless()) { // save the value for the history ui_ins_datum->labelEdit->pushToHistory(); diff --git a/src/Mod/Spreadsheet/App/Cell.cpp b/src/Mod/Spreadsheet/App/Cell.cpp index 324ff7bb61..9668a52d14 100644 --- a/src/Mod/Spreadsheet/App/Cell.cpp +++ b/src/Mod/Spreadsheet/App/Cell.cpp @@ -607,7 +607,7 @@ void Cell::setComputedUnit(const Base::Unit& unit) PropertySheet::AtomicPropertyChange signaller(*owner); computedUnit = unit; - setUsed(COMPUTED_UNIT_SET, !computedUnit.isEmpty()); + setUsed(COMPUTED_UNIT_SET, computedUnit != Unit::One); setDirty(); signaller.tryInvoke(); @@ -1110,7 +1110,7 @@ std::string Cell::getFormattedQuantity() const Base::Unit& computedUnit = floatProp->getUnit(); qFormatted = QLocale().toString(rawVal, 'f', Base::UnitsApi::getDecimals()); if (hasDisplayUnit) { - if (computedUnit.isEmpty() || computedUnit == du.unit) { + if (computedUnit == Unit::One || computedUnit == du.unit) { QString number = QLocale().toString(rawVal / duScale, 'f', Base::UnitsApi::getDecimals()); qFormatted = number + QString::fromStdString(" " + displayUnit.stringRep); diff --git a/src/Mod/Spreadsheet/App/Sheet.cpp b/src/Mod/Spreadsheet/App/Sheet.cpp index e2253da0cd..eb4fea54c5 100644 --- a/src/Mod/Spreadsheet/App/Sheet.cpp +++ b/src/Mod/Spreadsheet/App/Sheet.cpp @@ -802,7 +802,7 @@ void Sheet::updateProperty(CellAddress key) Base::PyGILStateLocker lock; setObjectProperty(key, constant->getPyValue()); } - else if (!number->getUnit().isEmpty()) { + else if (number->getUnit() != Unit::One) { setQuantityProperty(key, number->getValue(), number->getUnit()); } else if (number->isInteger(&l)) { diff --git a/src/Mod/Spreadsheet/Gui/SheetModel.cpp b/src/Mod/Spreadsheet/Gui/SheetModel.cpp index 9f097aac73..befa29c199 100644 --- a/src/Mod/Spreadsheet/Gui/SheetModel.cpp +++ b/src/Mod/Spreadsheet/Gui/SheetModel.cpp @@ -384,7 +384,7 @@ QVariant SheetModel::data(const QModelIndex& index, int role) const // Display locale specific decimal separator (#0003875,#0003876) if (cell->getDisplayUnit(displayUnit)) { - if (computedUnit.isEmpty() || computedUnit == displayUnit.unit) { + if (computedUnit == Base::Unit::One || computedUnit == displayUnit.unit) { QString number = QLocale().toString(floatProp->getValue() / displayUnit.scaler, 'f', diff --git a/tests/src/App/ExpressionParser.cpp b/tests/src/App/ExpressionParser.cpp index d7a88267fb..fe27e45002 100644 --- a/tests/src/App/ExpressionParser.cpp +++ b/tests/src/App/ExpressionParser.cpp @@ -37,16 +37,12 @@ protected: App::DocumentObject* this_obj() { return _this_obj; } Base::Quantity parse_expression_text_as_quantity(const char* expression_text) { - auto expression = App::ExpressionParser::parse(this_obj(), expression_text); - auto expression_value = expression->getValueAsAny(); - auto quantity_result = App::any_cast(expression_value); - return quantity_result; + const auto expression = App::ExpressionParser::parse(this_obj(), expression_text); + return App::any_cast(expression->getValueAsAny()); } Base::Quantity parse_quantity_text_as_quantity(const char* quantity_text) { - auto quantity_str = std::string(quantity_text); - auto quantity_result = Base::Quantity::parse(quantity_str); - return quantity_result; + return Base::Quantity::parse(quantity_text); } private: diff --git a/tests/src/Base/Quantity.cpp b/tests/src/Base/Quantity.cpp index 7e555d7e71..d333236271 100644 --- a/tests/src/Base/Quantity.cpp +++ b/tests/src/Base/Quantity.cpp @@ -27,13 +27,6 @@ TEST(BaseQuantity, TestParse) EXPECT_THROW(auto rew [[maybe_unused]] = Quantity::parse("1,234,500.12 kg"), ParserError); } -TEST(BaseQuantity, TestDim) -{ - const Quantity q1 {0, Unit::Area}; - - EXPECT_EQ(q1.isQuantity(), true); -} - TEST(BaseQuantity, TestNoDim) { const Quantity q1 {}; diff --git a/tests/src/Base/SchemaTests.cpp b/tests/src/Base/SchemaTests.cpp index 412bc8ac81..7ffc09be5d 100644 --- a/tests/src/Base/SchemaTests.cpp +++ b/tests/src/Base/SchemaTests.cpp @@ -29,6 +29,7 @@ #include "Base/UnitsSchemas.h" #include +#include #include using Base::Quantity; @@ -76,6 +77,119 @@ protected: std::unique_ptr schemas; // NOLINT }; +TEST_F(SchemaTest, meter_decimal_1_mm_precision_6) +{ + const std::string result = setWithPrecision("MeterDecimal", 1.0, Unit::Length, 6); + const auto expect {"0.001000 m"}; + + EXPECT_EQ(result, expect); +} + +TEST_F(SchemaTest, meter_decimal_15_mm2_precision_6) +{ + const std::string result = setWithPrecision("MeterDecimal", 15.0, Unit::Area, 6); + const auto expect {"0.000015 m^2"}; + + EXPECT_EQ(result, expect); +} + +TEST_F(SchemaTest, meter_decimal_123456000_mm3_precision_6) +{ + const std::string result = setWithPrecision("MeterDecimal", 123456000.0, Unit::Volume, 6); + const auto expect {"0.123456 m^3"}; + + EXPECT_EQ(result, expect); +} + +TEST_F(SchemaTest, meter_decimal_123456000_W_precision_6) +{ + const std::string result = setWithPrecision("MeterDecimal", 123456000.0, Unit::Power, 6); + const auto expect {"123.456000 W"}; + + EXPECT_EQ(result, expect); +} + +TEST_F(SchemaTest, meter_decimal_123456000_V_precision_6) +{ + const std::string result = + setWithPrecision("MeterDecimal", 123456000.0, Unit::ElectricPotential, 6); + const auto expect {"123.456000 V"}; + + EXPECT_EQ(result, expect); +} + +TEST_F(SchemaTest, meter_decimal_123456000_W_m2_precision_6) +{ + const std::string result = setWithPrecision("MeterDecimal", 123.456, Unit::HeatFlux, 6); + const auto expect {"123.456000 W/m^2"}; + + EXPECT_EQ(result, expect); +} + +TEST_F(SchemaTest, meter_decimal_123456000_m_s_precision_6) +{ + const std::string result = setWithPrecision("MeterDecimal", 123.456, Unit::Velocity, 6); + const auto expect {"0.123456 m/s"}; + + EXPECT_EQ(result, expect); +} + +TEST_F(SchemaTest, mks_1_mm_precision_6) +{ + const std::string result = setWithPrecision("MKS", 1.0, Unit::Length, 6); + const auto expect {"1.000000 mm"}; + + EXPECT_EQ(result, expect); +} + +TEST_F(SchemaTest, mks_15_mm2_precision_6) +{ + const std::string result = setWithPrecision("MKS", 15.0, Unit::Area, 6); + const auto expect {"15.000000 mm^2"}; + + EXPECT_EQ(result, expect); +} + +TEST_F(SchemaTest, mks_123456000_mm3_precision_6) +{ + const std::string result = setWithPrecision("MKS", 123456000.0, Unit::Volume, 6); + const auto expect {"123.456000 l"}; + + EXPECT_EQ(result, expect); +} + +TEST_F(SchemaTest, mks_123456000_W_precision_6) +{ + const std::string result = setWithPrecision("MKS", 123456000.0, Unit::Power, 6); + const auto expect {"123.456000 W"}; + + EXPECT_EQ(result, expect); +} + +TEST_F(SchemaTest, mks_123456000_V_precision_6) +{ + const std::string result = setWithPrecision("MKS", 123456000.0, Unit::ElectricPotential, 6); + const auto expect {"123.456000 V"}; + + EXPECT_EQ(result, expect); +} + +TEST_F(SchemaTest, mks_123456000_W_m2_precision_6) +{ + const std::string result = setWithPrecision("MKS", 123.456, Unit::HeatFlux, 6); + const auto expect {"123.456000 W/m^2"}; + + EXPECT_EQ(result, expect); +} + +TEST_F(SchemaTest, mks_123456000_m_s_precision_6) +{ + const std::string result = setWithPrecision("MKS", 123.456, Unit::Velocity, 6); + const auto expect {"0.123456 m/s"}; + + EXPECT_EQ(result, expect); +} + TEST_F(SchemaTest, imperial_decimal_1_mm_default_precision) { const std::string result = set("ImperialDecimal", Unit::Length, 1.0); @@ -410,3 +524,161 @@ TEST_F(SchemaTest, unknown_schema_name_throws) { EXPECT_THROW(UnitsApi::setSchema("Unknown"), RuntimeError); } + +TEST_F(SchemaTest, round_trip_test) +{ + const auto units = std::to_array({ + Unit::Length, + Unit::Mass, + Unit::Area, + Unit::Density, + Unit::Volume, + Unit::TimeSpan, + Unit::Frequency, + Unit::Velocity, + Unit::Acceleration, + Unit::Temperature, + Unit::CurrentDensity, + Unit::ElectricCurrent, + Unit::ElectricPotential, + Unit::ElectricCharge, + Unit::SurfaceChargeDensity, + Unit::MagneticFieldStrength, + Unit::MagneticFlux, + Unit::MagneticFluxDensity, + Unit::Magnetization, + Unit::ElectricalCapacitance, + Unit::ElectricalInductance, + Unit::ElectricalConductance, + Unit::ElectricalResistance, + Unit::ElectricalConductivity, + Unit::ElectromagneticPotential, + Unit::AmountOfSubstance, + Unit::LuminousIntensity, + Unit::CompressiveStrength, + Unit::Pressure, + Unit::ShearModulus, + Unit::Stress, + Unit::UltimateTensileStrength, + Unit::YieldStrength, + Unit::YoungsModulus, + Unit::Stiffness, + Unit::StiffnessDensity, + Unit::Force, + Unit::Work, + Unit::Power, + Unit::Moment, + Unit::SpecificEnergy, + Unit::ThermalConductivity, + Unit::ThermalExpansionCoefficient, + Unit::VolumetricThermalExpansionCoefficient, + Unit::SpecificHeat, + Unit::ThermalTransferCoefficient, + Unit::HeatFlux, + Unit::DynamicViscosity, + Unit::KinematicViscosity, + Unit::VacuumPermittivity, + Unit::VolumeFlowRate, + Unit::DissipationRate, + Unit::InverseLength, + Unit::InverseArea, + Unit::InverseVolume, + }); + + std::array values = {0.01, 0.1, 1.0, 10.0, 100.0}; + + double factor {}; + std::string unitString; + + UnitsApi::setDecimals(16); + + UnitsApi::setSchema("Internal"); + for (auto unit : units) { + for (double value : values) { + Quantity q1 {value, unit}; + std::string result = UnitsApi::schemaTranslate(q1, factor, unitString); + Quantity q2 = Quantity::parse(result); + EXPECT_DOUBLE_EQ(q2.getValue(), value); + } + } + + UnitsApi::setSchema("MKS"); + for (auto unit : units) { + for (double value : values) { + Quantity q1 {value, unit}; + std::string result = UnitsApi::schemaTranslate(q1, factor, unitString); + Quantity q2 = Quantity::parse(result); + EXPECT_DOUBLE_EQ(q2.getValue(), value); + } + } + + UnitsApi::setSchema("Imperial"); + for (auto unit : units) { + for (double value : values) { + Quantity q1 {value, unit}; + std::string result = UnitsApi::schemaTranslate(q1, factor, unitString); + Quantity q2 = Quantity::parse(result); + EXPECT_NEAR(q2.getValue(), value, 0.001); + } + } + + UnitsApi::setSchema("ImperialDecimal"); + for (auto unit : units) { + for (double value : values) { + Quantity q1 {value, unit}; + std::string result = UnitsApi::schemaTranslate(q1, factor, unitString); + Quantity q2 = Quantity::parse(result); + EXPECT_NEAR(q2.getValue(), value, 0.001); + } + } + + UnitsApi::setSchema("Centimeter"); + for (auto unit : units) { + for (double value : values) { + Quantity q1 {value, unit}; + std::string result = UnitsApi::schemaTranslate(q1, factor, unitString); + Quantity q2 = Quantity::parse(result); + EXPECT_DOUBLE_EQ(q2.getValue(), value); + } + } + + UnitsApi::setSchema("MmMin"); + for (auto unit : units) { + for (double value : values) { + Quantity q1 {value, unit}; + std::string result = UnitsApi::schemaTranslate(q1, factor, unitString); + Quantity q2 = Quantity::parse(result); + EXPECT_DOUBLE_EQ(q2.getValue(), value); + } + } + + UnitsApi::setSchema("ImperialCivil"); + for (auto unit : units) { + for (double value : values) { + Quantity q1 {value, unit}; + std::string result = UnitsApi::schemaTranslate(q1, factor, unitString); + Quantity q2 = Quantity::parse(result); + EXPECT_NEAR(q2.getValue(), value, 0.001); + } + } + + UnitsApi::setSchema("FEM"); + for (auto unit : units) { + for (double value : values) { + Quantity q1 {value, unit}; + std::string result = UnitsApi::schemaTranslate(q1, factor, unitString); + Quantity q2 = Quantity::parse(result); + EXPECT_DOUBLE_EQ(q2.getValue(), value); + } + } + + UnitsApi::setSchema("MeterDecimal"); + for (auto unit : units) { + for (double value : values) { + Quantity q1 {value, unit}; + std::string result = UnitsApi::schemaTranslate(q1, factor, unitString); + Quantity q2 = Quantity::parse(result); + EXPECT_DOUBLE_EQ(q2.getValue(), value); + } + } +} diff --git a/tests/src/Base/Unit.cpp b/tests/src/Base/Unit.cpp index fa73d91106..c4ef021f2a 100644 --- a/tests/src/Base/Unit.cpp +++ b/tests/src/Base/Unit.cpp @@ -3,191 +3,154 @@ #include // 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.isEmpty(), 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); +}