From ff1e315dfeca26a80c5d40f55469af3b4f8c58ca Mon Sep 17 00:00:00 2001 From: Philip Top Date: Thu, 6 Aug 2026 05:57:36 -0700 Subject: [PATCH 01/11] address some issues raised concerning half power exponents for meters which is a valid unit. allow more complete processing of valid units with half power and decimals in the exponent --- test/test_unit_ops.cpp | 27 ++++ test/test_unit_strings.cpp | 28 ++++ units/units.cpp | 235 ++++++++++++++++++++++---------- units/units_conversion_maps.hpp | 8 +- units/units_decl.hpp | 11 +- 5 files changed, 231 insertions(+), 78 deletions(-) diff --git a/test/test_unit_ops.cpp b/test/test_unit_ops.cpp index 2bb1d769..1cf13b18 100644 --- a/test/test_unit_ops.cpp +++ b/test/test_unit_ops.cpp @@ -483,6 +483,8 @@ TEST(preciseUnitOps, rootMeter) auto m1 = precise::m.pow(1); EXPECT_EQ(precise::m, root(m1, 1)); EXPECT_EQ(precise::m.inv(), root(m1, -1)); + EXPECT_EQ(precise::m, precise::special::rootMeter.pow(2)); + EXPECT_EQ(precise::m.inv(), precise::special::rootMeter.inv().pow(2)); auto m2 = pow(precise::m, 2); // use the alternate free function form EXPECT_EQ(precise::m, root(m2, 2)); EXPECT_EQ(precise::m, sqrt(m2)); @@ -831,6 +833,11 @@ TEST(specialOps, rootHertz) EXPECT_FALSE(is_error(precise::special::ASD)); auto rh = precise::special::rootHertz; + auto asd = precise::m / precise::s.pow(2) / rh; + + EXPECT_EQ(asd, precise::special::ASD); + EXPECT_EQ(unit_from_string("[m/s2/Hz^(1/2)]"), precise::special::ASD); + EXPECT_EQ(unit_from_string("m/s^2/Hz^0.5"), precise::special::ASD); EXPECT_EQ(rh.pow(1), rh); EXPECT_EQ(rh.pow(0), precise::one); @@ -851,6 +858,26 @@ TEST(specialOps, rootHertz) // EXPECT_EQ(rh.pow(-6), s.pow(3)); } +TEST(specialOps, rootMeterAndRootHertzDistinct) +{ + auto rm = precise::special::rootMeter; + auto rh = precise::special::rootHertz; + + EXPECT_NE(rm, rh); + EXPECT_EQ(rm, unit_from_string("m^0.5")); + EXPECT_EQ(rm, unit_from_string("sqrt(m)")); + EXPECT_EQ(rh, unit_from_string("Hz^0.5")); + EXPECT_EQ(rh.inv(), unit_from_string("sqrt(s)")); + + EXPECT_EQ(rm.pow(2), precise::m); + EXPECT_EQ(rh.pow(2), precise::Hz); + EXPECT_EQ(rm.pow(-2), precise::m.inv()); + EXPECT_EQ(rh.pow(-2), precise::s); + + EXPECT_NE(rm.pow(2), precise::Hz); + EXPECT_NE(rh.pow(2), precise::m); +} + TEST(specialOps, degC) { auto res = precise::degC.pow(2); diff --git a/test/test_unit_strings.cpp b/test/test_unit_strings.cpp index bbf59936..ace85b8c 100644 --- a/test/test_unit_strings.cpp +++ b/test/test_unit_strings.cpp @@ -557,6 +557,34 @@ TEST(stringToUnits, morePower) EXPECT_EQ(precise::us::mile.pow(2), unit_from_string("mi(USA)^(2)")); } +TEST(stringToUnits, decimalPowerExponents) +{ + EXPECT_EQ(precise::special::rootMeter, unit_from_string("m^0.5")); + EXPECT_EQ(precise::special::rootMeter, unit_from_string("m**0.5")); + EXPECT_EQ(precise::special::rootMeter, unit_from_string("m^(0.5)")); + EXPECT_EQ(precise::special::rootMeter.inv(), unit_from_string("m^-0.5")); + EXPECT_EQ(precise::special::rootMeter.inv(), unit_from_string("m^(-0.5)")); + EXPECT_EQ(precise::m.pow(2), unit_from_string("m^2.0")); + EXPECT_EQ(precise::m.pow(2), unit_from_string("m^2.00")); + EXPECT_EQ(precise::Hz, unit_from_string("s^-1.0")); + EXPECT_EQ(precise::m.pow(2), unit_from_string("m^2e0")); + EXPECT_EQ(precise::special::rootHertz, unit_from_string("Hz^(0.5)")); + + const std::vector invalidPowers{ + "m^0.25", + "m^(0.25)", + "m^1.5", + "m^2.25", + "m^2.5", + "m^-2.5", + "m^2e", + "m^2e2"}; + + for (const auto& powerString : invalidPowers) { + EXPECT_TRUE(is_error(unit_from_string(powerString))) << powerString; + } +} + TEST(stringToUnits, specialUnits) { EXPECT_EQ( diff --git a/units/units.cpp b/units/units.cpp index 980d1438..abc2fc86 100644 --- a/units/units.cpp +++ b/units/units.cpp @@ -3960,40 +3960,63 @@ static bool checkExponentOperations(const std::string& unit_string) while (cx != std::string::npos) { const bool ndigit = isDigitCharacter(unit_string[cx - 1]); ++cx; - const char c = unit_string[cx]; - if (!isDigitCharacter(c)) { - if (c == '-') { - if (!isDigitCharacter(unit_string[cx + 1])) { - return false; - } + const bool parenthesized = (unit_string[cx] == '('); + if (parenthesized) { + ++cx; + } + if (unit_string[cx] == '-' || unit_string[cx] == '+') { + ++cx; + } + const auto startDigit = cx; + bool dpoint_encountered = false; + while (cx < unit_string.size() && isDigitCharacter(unit_string[cx])) { + ++cx; + } + const auto wholeDigitCount = cx - startDigit; + if (cx < unit_string.size() && unit_string[cx] == '.') { + dpoint_encountered = true; + ++cx; + while (cx < unit_string.size() && + isDigitCharacter(unit_string[cx])) { ++cx; - } else if (c == '(') { + } + } + if (wholeDigitCount == 0 && !dpoint_encountered) { + return false; + } + if (cx < unit_string.size() && + (unit_string[cx] == 'e' || unit_string[cx] == 'E')) { + ++cx; + if (cx < unit_string.size() && + (unit_string[cx] == '-' || unit_string[cx] == '+')) { ++cx; - if (unit_string[cx] == '-') { - ++cx; - } - bool dpoint_encountered = false; - while (unit_string[cx] != ')') { - if (!isDigitCharacter(unit_string[cx])) { - if (unit_string[cx] == '.' && !dpoint_encountered) { - dpoint_encountered = true; - } else { - return false; - } - } - ++cx; - } - } else { + } + const auto exponentDigitStart = cx; + while (cx < unit_string.size() && + isDigitCharacter(unit_string[cx])) { + ++cx; + } + if (cx == exponentDigitStart) { return false; } } + if (parenthesized) { + if (cx >= unit_string.size() || unit_string[cx] != ')') { + return false; + } + } else if (cx < unit_string.size() && + unit_string[cx] != '*' && unit_string[cx] != '/' && + unit_string[cx] != '^') { + return false; + } else { + --cx; + } #ifdef UNITS_CONSTEXPR_IF_SUPPORTED if constexpr (detail::bitwidth::base_size == sizeof(std::uint32_t)) { #else if (detail::bitwidth::base_size == sizeof(std::uint32_t)) { #endif - if (unit_string.size() > cx + 1 && - isDigitCharacter(unit_string[cx + 1]) && !ndigit) { + if (wholeDigitCount > 1 && !ndigit) { // non representable unit power return false; } @@ -4774,7 +4797,7 @@ static bool cleanUnitString(std::string& unit_string, std::uint64_t match_flags) if (seq > 1) { auto c2 = unit_string[fnd + seq]; if (c2 != '\0' && c2 != '*' && c2 != '/' && c2 != '^' && - c2 != 'e' && c2 != 'E') { + c2 != 'e' && c2 != 'E' && c2 != '.') { unit_string.insert(fnd + seq, 1, '*'); } } @@ -5297,6 +5320,90 @@ static precise_unit unit_to_the_power_of( return precise::defunit; } +static bool string_power_to_twice_power( + const std::string& powerString, + int& twicePower) +{ + char* eptr{nullptr}; + const auto power = std::strtod(powerString.c_str(), &eptr); + if (eptr != powerString.c_str() + powerString.size() || + !std::isfinite(power)) { + return false; + } + const auto scaledPower = 2.0 * power; + const auto roundedPower = std::round(scaledPower); + if (std::abs(scaledPower - roundedPower) > 1e-12 || + roundedPower > static_cast(std::numeric_limits::max()) || + roundedPower < static_cast(std::numeric_limits::min())) { + return false; + } + twicePower = static_cast(roundedPower); + return true; +} + +static precise_unit root_with_special_units(const precise_unit& un) +{ + auto retunit = root(un, 2); + if (!is_error(retunit)) { + return retunit; + } + + const auto meterPower = un.base_units().meter(); + if (meterPower % 2 != 0) { + const auto adjusted = + (meterPower > 0) ? un / precise::m : un * precise::m; + retunit = root(adjusted, 2); + if (!is_error(retunit)) { + return (meterPower > 0) ? + retunit * precise::special::rootMeter : + retunit / precise::special::rootMeter; + } + } + + const auto secondPower = un.base_units().second(); + if (secondPower % 2 != 0) { + const auto adjusted = + (secondPower < 0) ? un / precise::Hz : un * precise::Hz; + retunit = root(adjusted, 2); + if (!is_error(retunit)) { + return (secondPower < 0) ? + retunit * precise::special::rootHertz : + retunit / precise::special::rootHertz; + } + } + + return precise::invalid; +} + +static precise_unit unit_to_the_half_power_of( + const std::string& unit_string, + int twicePower, + std::uint64_t match_flags) +{ + const auto wholePower = twicePower / 2; + auto retunit = (wholePower == 0) ? + precise::one : + unit_to_the_power_of(unit_string, wholePower, match_flags); + if (is_error(retunit)) { + return precise::invalid; + } + + auto rootUnit = unit_to_the_power_of( + unit_string, (twicePower > 0) ? 1 : -1, match_flags); + if (is_error(rootUnit)) { + return precise::invalid; + } + rootUnit = root_with_special_units(rootUnit); + if (is_error(rootUnit)) { + return precise::invalid; + } + if (wholePower != 0 && + (rootUnit.has_i_flag() || rootUnit.has_e_flag())) { + return precise::invalid; + } + return retunit * rootUnit; +} + static precise_unit checkSIprefix(const std::string& unit_string, std::uint64_t match_flags) { @@ -5632,60 +5739,40 @@ static precise_unit unit_from_string_internal( sep = findOperatorSep(unit_string, "^"); if (sep != std::string::npos) { auto pchar = sep - 1; - if (unit_string[sep + 1] == '(') { - ++sep; - } - const char c1 = unit_string[sep + 1]; - int power{+1}; - if (c1 == '-' || c1 == '+') { - ++sep; - if (unit_string.length() < sep + 2) { - // this should have been caught as an invalid sequence - // earlier - return precise::invalid; // LCOV_EXCL_LINE - } - // the - ',' is a +/- sign - power = -(c1 - ','); - } - if (isDigitCharacter(unit_string[sep + 1])) { + const bool parenthesizedPower = (unit_string[sep + 1] == '('); + const auto powerStart = sep + (parenthesizedPower ? 2 : 1); + auto powerLength = unit_string.size() - powerStart; + if (parenthesizedPower) { + if (unit_string.back() != ')' || powerLength < 2) { + return precise::invalid; + } + --powerLength; + } + int twicePower{0}; + if (!string_power_to_twice_power( + unit_string.substr(powerStart, powerLength), twicePower)) { + return precise::invalid; + } #ifdef UNITS_CONSTEXPR_IF_SUPPORTED - if constexpr (sizeof(UNITS_BASE_TYPE) == 8) { + if constexpr (sizeof(UNITS_BASE_TYPE) != 8) { #else - if (sizeof(UNITS_BASE_TYPE) == 8) { + if (sizeof(UNITS_BASE_TYPE) != 8) { #endif - size_t end = sep + 2; - for (; end < unit_string.size() && - isDigitCharacter(unit_string[end]); - ++end) { - } - auto powerStringLength = end - sep - 1; - if (powerStringLength > 1) { - auto pstring = - unit_string.substr(sep + 1, powerStringLength); - char* eptr{nullptr}; - auto mpower = strtoul(pstring.c_str(), &eptr, 10); - if (eptr - pstring.c_str() == - static_cast(powerStringLength)) { - power *= mpower; - } else { - return precise::invalid; // LCOV_EXCL_LINE - } - } else { - power *= (unit_string[sep + 1] - '0'); - } - - } else { - power *= (unit_string[sep + 1] - '0'); + if (!isDigitCharacter(unit_string[pchar]) && + (twicePower > 18 || twicePower < -18)) { + return precise::invalid; } + } + + const auto powerUnitString = + unit_string.substr(0, (pchar > 0) ? pchar + 1 : 1); + if (twicePower % 2 == 0) { + retunit = unit_to_the_power_of( + powerUnitString, twicePower / 2, match_flags); } else { - // the check functions should catch this but it would be - // problematic if not caught - return precise::invalid; // LCOV_EXCL_LINE - } - retunit = unit_to_the_power_of( - unit_string.substr(0, (pchar > 0) ? pchar + 1 : 1), - power, - match_flags); + retunit = unit_to_the_half_power_of( + powerUnitString, twicePower, match_flags); + } if (retunit != precise::defunit) { return retunit; } diff --git a/units/units_conversion_maps.hpp b/units/units_conversion_maps.hpp index beaea07d..5948f17f 100644 --- a/units/units_conversion_maps.hpp +++ b/units/units_conversion_maps.hpp @@ -13,7 +13,7 @@ SPDX-License-Identifier: BSD-3-Clause namespace UNITS_NAMESPACE { -UNITS_CPP14_CONSTEXPR_OBJECT std::array, 113> +UNITS_CPP14_CONSTEXPR_OBJECT std::array, 114> defined_unit_names_si{ {{m, "m"}, {m * m, "m^2"}, @@ -81,6 +81,7 @@ UNITS_CPP14_CONSTEXPR_OBJECT std::array, 113> {percent, "%"}, {unit_cast(precise::special::ASD), "ASD"}, {unit_cast(precise::special::rootHertz), "rootHertz"}, + {unit_cast(precise::special::rootMeter), "rootMeter"}, {currency, "$"}, {count, "count"}, {ratio, ""}, @@ -204,7 +205,7 @@ UNITS_CPP14_CONSTEXPR_OBJECT std::array, 55> /// definitions for the default units for specific types of measurmeents UNITS_CPP14_CONSTEXPR_OBJECT std::array< std::pair, - 1216> + 1219> defined_unit_strings_si{ {{"", precise::defunit}, {"[]", precise::defunit}, @@ -1409,6 +1410,8 @@ UNITS_CPP14_CONSTEXPR_OBJECT std::array< {"ASD", precise::special::ASD}, {"[m/s2/Hz^(1/2)]", precise::special::ASD}, {"[M/s2/HZ^(1/2)]", precise::special::ASD}, + {"sqrt(m)", precise::special::rootMeter}, + {"sqrt(s)", precise::special::rootHertz.inv()}, {"Hz^(1/2)", precise::special::rootHertz}, {"HZ^(1/2)", precise::special::rootHertz}, {"squarerootofhertz", precise::special::rootHertz}, @@ -1416,6 +1419,7 @@ UNITS_CPP14_CONSTEXPR_OBJECT std::array< {"roothertz", precise::special::rootHertz}, // capitalized version is needed since this is also a generated unit {"rootHertz", precise::special::rootHertz}, + {"rootmeter", precise::special::rootMeter}, {"B", precise::data::byte}, {"bel", precise::log::bel}, {"dB", precise::log::dB}, diff --git a/units/units_decl.hpp b/units/units_decl.hpp index c0167e3f..dc980c24 100644 --- a/units/units_decl.hpp +++ b/units/units_decl.hpp @@ -208,9 +208,9 @@ namespace detail { /// take a unit_data to some power constexpr unit_data pow(int power) const { // the modifier is to handle a few weird operations that operate on - // square_root Hz, + // square_root meter and Hz, return { - meter_ * power, + (meter_ * power) + rootMeterModifier(power), kilogram_ * power, (second_ * power) + rootHertzModifier(power), ampere_ * power, @@ -460,6 +460,13 @@ namespace detail { 0 : (power / 2) * ((second_ < 0) || (power < 0) ? 9 : -9); } + constexpr int rootMeterModifier(int power) const + { + return (meter_ * power == 0 || ((e_flag_ & i_flag_) == 0U) || + power % 2 != 0) ? + 0 : + (power / 2) * ((meter_ < 0) ? 11 : -11); + } // needs to be defined for the full 32 bits(or 64 bits) signed int meter_ : bitwidth::meter; From b50fe2e82ea6319ee4ed29913f139366480ff45c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 6 Aug 2026 13:38:43 +0000 Subject: [PATCH 02/11] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- units/units.cpp | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/units/units.cpp b/units/units.cpp index abc2fc86..7750f04b 100644 --- a/units/units.cpp +++ b/units/units.cpp @@ -4004,9 +4004,9 @@ static bool checkExponentOperations(const std::string& unit_string) if (cx >= unit_string.size() || unit_string[cx] != ')') { return false; } - } else if (cx < unit_string.size() && - unit_string[cx] != '*' && unit_string[cx] != '/' && - unit_string[cx] != '^') { + } else if ( + cx < unit_string.size() && unit_string[cx] != '*' && + unit_string[cx] != '/' && unit_string[cx] != '^') { return false; } else { --cx; @@ -5320,9 +5320,8 @@ static precise_unit unit_to_the_power_of( return precise::defunit; } -static bool string_power_to_twice_power( - const std::string& powerString, - int& twicePower) +static bool + string_power_to_twice_power(const std::string& powerString, int& twicePower) { char* eptr{nullptr}; const auto power = std::strtod(powerString.c_str(), &eptr); @@ -5354,9 +5353,8 @@ static precise_unit root_with_special_units(const precise_unit& un) (meterPower > 0) ? un / precise::m : un * precise::m; retunit = root(adjusted, 2); if (!is_error(retunit)) { - return (meterPower > 0) ? - retunit * precise::special::rootMeter : - retunit / precise::special::rootMeter; + return (meterPower > 0) ? retunit * precise::special::rootMeter : + retunit / precise::special::rootMeter; } } @@ -5366,9 +5364,8 @@ static precise_unit root_with_special_units(const precise_unit& un) (secondPower < 0) ? un / precise::Hz : un * precise::Hz; retunit = root(adjusted, 2); if (!is_error(retunit)) { - return (secondPower < 0) ? - retunit * precise::special::rootHertz : - retunit / precise::special::rootHertz; + return (secondPower < 0) ? retunit * precise::special::rootHertz : + retunit / precise::special::rootHertz; } } @@ -5397,8 +5394,7 @@ static precise_unit unit_to_the_half_power_of( if (is_error(rootUnit)) { return precise::invalid; } - if (wholePower != 0 && - (rootUnit.has_i_flag() || rootUnit.has_e_flag())) { + if (wholePower != 0 && (rootUnit.has_i_flag() || rootUnit.has_e_flag())) { return precise::invalid; } return retunit * rootUnit; From 9b8e8ac9ccc41420d969caac21d1f6f650c0e347 Mon Sep 17 00:00:00 2001 From: Philip Top Date: Thu, 6 Aug 2026 06:58:34 -0700 Subject: [PATCH 03/11] move tests to correct location --- test/test_unit_ops.cpp | 6 ------ test/test_unit_strings.cpp | 5 +++++ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/test/test_unit_ops.cpp b/test/test_unit_ops.cpp index 1cf13b18..0a8a4dc2 100644 --- a/test/test_unit_ops.cpp +++ b/test/test_unit_ops.cpp @@ -836,8 +836,6 @@ TEST(specialOps, rootHertz) auto asd = precise::m / precise::s.pow(2) / rh; EXPECT_EQ(asd, precise::special::ASD); - EXPECT_EQ(unit_from_string("[m/s2/Hz^(1/2)]"), precise::special::ASD); - EXPECT_EQ(unit_from_string("m/s^2/Hz^0.5"), precise::special::ASD); EXPECT_EQ(rh.pow(1), rh); EXPECT_EQ(rh.pow(0), precise::one); @@ -864,10 +862,6 @@ TEST(specialOps, rootMeterAndRootHertzDistinct) auto rh = precise::special::rootHertz; EXPECT_NE(rm, rh); - EXPECT_EQ(rm, unit_from_string("m^0.5")); - EXPECT_EQ(rm, unit_from_string("sqrt(m)")); - EXPECT_EQ(rh, unit_from_string("Hz^0.5")); - EXPECT_EQ(rh.inv(), unit_from_string("sqrt(s)")); EXPECT_EQ(rm.pow(2), precise::m); EXPECT_EQ(rh.pow(2), precise::Hz); diff --git a/test/test_unit_strings.cpp b/test/test_unit_strings.cpp index ace85b8c..872b84c0 100644 --- a/test/test_unit_strings.cpp +++ b/test/test_unit_strings.cpp @@ -562,13 +562,18 @@ TEST(stringToUnits, decimalPowerExponents) EXPECT_EQ(precise::special::rootMeter, unit_from_string("m^0.5")); EXPECT_EQ(precise::special::rootMeter, unit_from_string("m**0.5")); EXPECT_EQ(precise::special::rootMeter, unit_from_string("m^(0.5)")); + EXPECT_EQ(precise::special::rootMeter, unit_from_string("sqrt(m)")); EXPECT_EQ(precise::special::rootMeter.inv(), unit_from_string("m^-0.5")); EXPECT_EQ(precise::special::rootMeter.inv(), unit_from_string("m^(-0.5)")); EXPECT_EQ(precise::m.pow(2), unit_from_string("m^2.0")); EXPECT_EQ(precise::m.pow(2), unit_from_string("m^2.00")); EXPECT_EQ(precise::Hz, unit_from_string("s^-1.0")); EXPECT_EQ(precise::m.pow(2), unit_from_string("m^2e0")); + EXPECT_EQ(precise::special::rootHertz, unit_from_string("Hz^0.5")); EXPECT_EQ(precise::special::rootHertz, unit_from_string("Hz^(0.5)")); + EXPECT_EQ(precise::special::rootHertz.inv(), unit_from_string("sqrt(s)")); + EXPECT_EQ(unit_from_string("[m/s2/Hz^(1/2)]"), precise::special::ASD); + EXPECT_EQ(unit_from_string("m/s^2/Hz^0.5"), precise::special::ASD); const std::vector invalidPowers{ "m^0.25", From a79794500f4d26ebf3b97ce8fba74c8f734760f2 Mon Sep 17 00:00:00 2001 From: Philip Top Date: Thu, 6 Aug 2026 07:21:58 -0700 Subject: [PATCH 04/11] fix some more failing test cases --- test/test_unit_strings.cpp | 4 ++++ units/units.cpp | 4 +++- units/units_decl.hpp | 8 ++++++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/test/test_unit_strings.cpp b/test/test_unit_strings.cpp index 872b84c0..07683076 100644 --- a/test/test_unit_strings.cpp +++ b/test/test_unit_strings.cpp @@ -567,6 +567,10 @@ TEST(stringToUnits, decimalPowerExponents) EXPECT_EQ(precise::special::rootMeter.inv(), unit_from_string("m^(-0.5)")); EXPECT_EQ(precise::m.pow(2), unit_from_string("m^2.0")); EXPECT_EQ(precise::m.pow(2), unit_from_string("m^2.00")); + EXPECT_EQ(precise::kg / precise::m.pow(2), unit_from_string("kg/(m^2)")); + EXPECT_EQ( + precise::kg / precise::m.pow(2), + unit_from_string("kg/(m2)")); EXPECT_EQ(precise::Hz, unit_from_string("s^-1.0")); EXPECT_EQ(precise::m.pow(2), unit_from_string("m^2e0")); EXPECT_EQ(precise::special::rootHertz, unit_from_string("Hz^0.5")); diff --git a/units/units.cpp b/units/units.cpp index 7750f04b..dcd972b4 100644 --- a/units/units.cpp +++ b/units/units.cpp @@ -4006,7 +4006,9 @@ static bool checkExponentOperations(const std::string& unit_string) } } else if ( cx < unit_string.size() && unit_string[cx] != '*' && - unit_string[cx] != '/' && unit_string[cx] != '^') { + unit_string[cx] != '/' && unit_string[cx] != '^' && + unit_string[cx] != ')' && unit_string[cx] != ']' && + unit_string[cx] != '}') { return false; } else { --cx; diff --git a/units/units_decl.hpp b/units/units_decl.hpp index dc980c24..1f8aefad 100644 --- a/units/units_decl.hpp +++ b/units/units_decl.hpp @@ -462,8 +462,12 @@ namespace detail { } constexpr int rootMeterModifier(int power) const { - return (meter_ * power == 0 || ((e_flag_ & i_flag_) == 0U) || - power % 2 != 0) ? + return ((meter_ != -5 && meter_ != 5) || + ((e_flag_ & i_flag_) == 0U) || power % 2 != 0 || + second_ != 0 || kilogram_ != 0 || ampere_ != 0 || + candela_ != 0 || kelvin_ != 0 || mole_ != 0 || + radians_ != 0 || currency_ != 0 || count_ != 0 || + equation_ != 0U) ? 0 : (power / 2) * ((meter_ < 0) ? 11 : -11); } From 23eccde094ee5d032af839392eebd8070f6783ab Mon Sep 17 00:00:00 2001 From: Philip Top Date: Thu, 6 Aug 2026 09:12:03 -0700 Subject: [PATCH 05/11] remove the conditions to square rootMeter --- test/test_unit_ops.cpp | 5 ----- units/units_decl.hpp | 16 ++-------------- 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/test/test_unit_ops.cpp b/test/test_unit_ops.cpp index 0a8a4dc2..08d835aa 100644 --- a/test/test_unit_ops.cpp +++ b/test/test_unit_ops.cpp @@ -483,8 +483,6 @@ TEST(preciseUnitOps, rootMeter) auto m1 = precise::m.pow(1); EXPECT_EQ(precise::m, root(m1, 1)); EXPECT_EQ(precise::m.inv(), root(m1, -1)); - EXPECT_EQ(precise::m, precise::special::rootMeter.pow(2)); - EXPECT_EQ(precise::m.inv(), precise::special::rootMeter.inv().pow(2)); auto m2 = pow(precise::m, 2); // use the alternate free function form EXPECT_EQ(precise::m, root(m2, 2)); EXPECT_EQ(precise::m, sqrt(m2)); @@ -863,12 +861,9 @@ TEST(specialOps, rootMeterAndRootHertzDistinct) EXPECT_NE(rm, rh); - EXPECT_EQ(rm.pow(2), precise::m); EXPECT_EQ(rh.pow(2), precise::Hz); - EXPECT_EQ(rm.pow(-2), precise::m.inv()); EXPECT_EQ(rh.pow(-2), precise::s); - EXPECT_NE(rm.pow(2), precise::Hz); EXPECT_NE(rh.pow(2), precise::m); } diff --git a/units/units_decl.hpp b/units/units_decl.hpp index 1f8aefad..a41bf1ea 100644 --- a/units/units_decl.hpp +++ b/units/units_decl.hpp @@ -208,9 +208,9 @@ namespace detail { /// take a unit_data to some power constexpr unit_data pow(int power) const { // the modifier is to handle a few weird operations that operate on - // square_root meter and Hz, + // square_root Hz, return { - (meter_ * power) + rootMeterModifier(power), + meter_ * power, kilogram_ * power, (second_ * power) + rootHertzModifier(power), ampere_ * power, @@ -460,18 +460,6 @@ namespace detail { 0 : (power / 2) * ((second_ < 0) || (power < 0) ? 9 : -9); } - constexpr int rootMeterModifier(int power) const - { - return ((meter_ != -5 && meter_ != 5) || - ((e_flag_ & i_flag_) == 0U) || power % 2 != 0 || - second_ != 0 || kilogram_ != 0 || ampere_ != 0 || - candela_ != 0 || kelvin_ != 0 || mole_ != 0 || - radians_ != 0 || currency_ != 0 || count_ != 0 || - equation_ != 0U) ? - 0 : - (power / 2) * ((meter_ < 0) ? 11 : -11); - } - // needs to be defined for the full 32 bits(or 64 bits) signed int meter_ : bitwidth::meter; signed int second_ : bitwidth::second; // 8 From e9d79d3c1e2e545ca2b91bd219a9e94c0d8f9332 Mon Sep 17 00:00:00 2001 From: Philip Top Date: Thu, 6 Aug 2026 09:27:00 -0700 Subject: [PATCH 06/11] fix expectation on powers for 64 representation --- test/test_unit_strings.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/test/test_unit_strings.cpp b/test/test_unit_strings.cpp index 07683076..68529931 100644 --- a/test/test_unit_strings.cpp +++ b/test/test_unit_strings.cpp @@ -573,6 +573,15 @@ TEST(stringToUnits, decimalPowerExponents) unit_from_string("kg/(m2)")); EXPECT_EQ(precise::Hz, unit_from_string("s^-1.0")); EXPECT_EQ(precise::m.pow(2), unit_from_string("m^2e0")); +#ifdef UNITS_CONSTEXPR_IF_SUPPORTED + if constexpr (detail::bitwidth::base_size == sizeof(std::uint64_t)) { +#else + if (detail::bitwidth::base_size == sizeof(std::uint64_t)) { +#endif + EXPECT_EQ(precise::m.pow(200), unit_from_string("m^2e2")); + } else { + EXPECT_TRUE(is_error(unit_from_string("m^2e2"))); + } EXPECT_EQ(precise::special::rootHertz, unit_from_string("Hz^0.5")); EXPECT_EQ(precise::special::rootHertz, unit_from_string("Hz^(0.5)")); EXPECT_EQ(precise::special::rootHertz.inv(), unit_from_string("sqrt(s)")); @@ -586,8 +595,7 @@ TEST(stringToUnits, decimalPowerExponents) "m^2.25", "m^2.5", "m^-2.5", - "m^2e", - "m^2e2"}; + "m^2e"}; for (const auto& powerString : invalidPowers) { EXPECT_TRUE(is_error(unit_from_string(powerString))) << powerString; From 545ee501927bf3aae98ba670c940f2e803c01b86 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 6 Aug 2026 16:27:25 +0000 Subject: [PATCH 07/11] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- test/test_unit_strings.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/test/test_unit_strings.cpp b/test/test_unit_strings.cpp index 68529931..4d10d0bc 100644 --- a/test/test_unit_strings.cpp +++ b/test/test_unit_strings.cpp @@ -589,13 +589,7 @@ TEST(stringToUnits, decimalPowerExponents) EXPECT_EQ(unit_from_string("m/s^2/Hz^0.5"), precise::special::ASD); const std::vector invalidPowers{ - "m^0.25", - "m^(0.25)", - "m^1.5", - "m^2.25", - "m^2.5", - "m^-2.5", - "m^2e"}; + "m^0.25", "m^(0.25)", "m^1.5", "m^2.25", "m^2.5", "m^-2.5", "m^2e"}; for (const auto& powerString : invalidPowers) { EXPECT_TRUE(is_error(unit_from_string(powerString))) << powerString; From 7db9775900de79122c8b55df21bfc667ec78e764 Mon Sep 17 00:00:00 2001 From: Philip Top Date: Thu, 6 Aug 2026 10:56:28 -0700 Subject: [PATCH 08/11] add some more checks and support other bracket types --- test/test_unit_strings.cpp | 9 ++++++++- units/units.cpp | 29 ++++++++++++++++++++++++++--- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/test/test_unit_strings.cpp b/test/test_unit_strings.cpp index 4d10d0bc..2599c21a 100644 --- a/test/test_unit_strings.cpp +++ b/test/test_unit_strings.cpp @@ -589,7 +589,14 @@ TEST(stringToUnits, decimalPowerExponents) EXPECT_EQ(unit_from_string("m/s^2/Hz^0.5"), precise::special::ASD); const std::vector invalidPowers{ - "m^0.25", "m^(0.25)", "m^1.5", "m^2.25", "m^2.5", "m^-2.5", "m^2e"}; + "m^0.25", + "m^(0.25)", + "m^1.5", + "m^2.25", + "m^2.5", + "m^-2.5", + "m^2e", + "[(0.5)]^34s"}; for (const auto& powerString : invalidPowers) { EXPECT_TRUE(is_error(unit_from_string(powerString))) << powerString; diff --git a/units/units.cpp b/units/units.cpp index dcd972b4..23fd59cf 100644 --- a/units/units.cpp +++ b/units/units.cpp @@ -3959,6 +3959,28 @@ static bool checkExponentOperations(const std::string& unit_string) auto cx = unit_string.find_first_of('^'); while (cx != std::string::npos) { const bool ndigit = isDigitCharacter(unit_string[cx - 1]); + if (unit_string[cx - 1] == ']') { + int index = static_cast(cx) - 2; + if (segmentcheckReverse(unit_string, '[', index)) { + const auto openBracket = static_cast(index + 1); + auto contentStart = openBracket + 1; + auto contentLength = cx - contentStart - 1; + if (contentLength >= 2 && + unit_string[contentStart] == '(' && + unit_string[contentStart + contentLength - 1] == ')') { + ++contentStart; + contentLength -= 2; + } + char* eptr{nullptr}; + const auto content = + unit_string.substr(contentStart, contentLength); + std::strtod(content.c_str(), &eptr); + if (!content.empty() && + eptr == content.c_str() + content.size()) { + return false; + } + } + } ++cx; const bool parenthesized = (unit_string[cx] == '('); if (parenthesized) { @@ -4110,9 +4132,10 @@ static bool checkValidUnitString( break; } } - if (!checkExponentOperations(unit_string)) { - return false; - } + } + + if (!checkExponentOperations(unit_string)) { + return false; } return true; From 6be63ae7e54635eb7fc0055fe2140cc96f19a982 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 6 Aug 2026 17:57:10 +0000 Subject: [PATCH 09/11] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- units/units.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/units/units.cpp b/units/units.cpp index 23fd59cf..0e8824b8 100644 --- a/units/units.cpp +++ b/units/units.cpp @@ -3965,8 +3965,7 @@ static bool checkExponentOperations(const std::string& unit_string) const auto openBracket = static_cast(index + 1); auto contentStart = openBracket + 1; auto contentLength = cx - contentStart - 1; - if (contentLength >= 2 && - unit_string[contentStart] == '(' && + if (contentLength >= 2 && unit_string[contentStart] == '(' && unit_string[contentStart + contentLength - 1] == ')') { ++contentStart; contentLength -= 2; From 5f59aa36e1202a305c470154a3a317e6c695743d Mon Sep 17 00:00:00 2001 From: Philip Top Date: Thu, 6 Aug 2026 12:24:38 -0700 Subject: [PATCH 10/11] more updates and resolutions --- units/units.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/units/units.cpp b/units/units.cpp index 23fd59cf..e0c675bc 100644 --- a/units/units.cpp +++ b/units/units.cpp @@ -3965,9 +3965,11 @@ static bool checkExponentOperations(const std::string& unit_string) const auto openBracket = static_cast(index + 1); auto contentStart = openBracket + 1; auto contentLength = cx - contentStart - 1; + bool parenthesizedContent = false; if (contentLength >= 2 && unit_string[contentStart] == '(' && unit_string[contentStart + contentLength - 1] == ')') { + parenthesizedContent = true; ++contentStart; contentLength -= 2; } @@ -3975,7 +3977,7 @@ static bool checkExponentOperations(const std::string& unit_string) const auto content = unit_string.substr(contentStart, contentLength); std::strtod(content.c_str(), &eptr); - if (!content.empty() && + if (parenthesizedContent && !content.empty() && eptr == content.c_str() + content.size()) { return false; } From 6e4aed34cde5802ed9ebcba01f5db7634e06d8ad Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 9 Aug 2026 23:28:48 +0000 Subject: [PATCH 11/11] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- units/units.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/units/units.cpp b/units/units.cpp index e0c675bc..35b80fb3 100644 --- a/units/units.cpp +++ b/units/units.cpp @@ -3966,8 +3966,7 @@ static bool checkExponentOperations(const std::string& unit_string) auto contentStart = openBracket + 1; auto contentLength = cx - contentStart - 1; bool parenthesizedContent = false; - if (contentLength >= 2 && - unit_string[contentStart] == '(' && + if (contentLength >= 2 && unit_string[contentStart] == '(' && unit_string[contentStart + contentLength - 1] == ')') { parenthesizedContent = true; ++contentStart;