diff --git a/test/test_unit_ops.cpp b/test/test_unit_ops.cpp index 2bb1d769..08d835aa 100644 --- a/test/test_unit_ops.cpp +++ b/test/test_unit_ops.cpp @@ -831,6 +831,9 @@ 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(rh.pow(1), rh); EXPECT_EQ(rh.pow(0), precise::one); @@ -851,6 +854,19 @@ 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(rh.pow(2), precise::Hz); + EXPECT_EQ(rh.pow(-2), precise::s); + + 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..2599c21a 100644 --- a/test/test_unit_strings.cpp +++ b/test/test_unit_strings.cpp @@ -557,6 +557,52 @@ 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, 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::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")); +#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)")); + 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", + "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; + } +} + TEST(stringToUnits, specialUnits) { EXPECT_EQ( diff --git a/units/units.cpp b/units/units.cpp index 980d1438..35b80fb3 100644 --- a/units/units.cpp +++ b/units/units.cpp @@ -3959,41 +3959,89 @@ 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]); - ++cx; - const char c = unit_string[cx]; - if (!isDigitCharacter(c)) { - if (c == '-') { - if (!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; + bool parenthesizedContent = false; + if (contentLength >= 2 && unit_string[contentStart] == '(' && + unit_string[contentStart + contentLength - 1] == ')') { + parenthesizedContent = true; + ++contentStart; + contentLength -= 2; + } + char* eptr{nullptr}; + const auto content = + unit_string.substr(contentStart, contentLength); + std::strtod(content.c_str(), &eptr); + if (parenthesizedContent && !content.empty() && + eptr == content.c_str() + content.size()) { return false; } + } + } + ++cx; + 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] != '^' && + 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; } @@ -4085,9 +4133,10 @@ static bool checkValidUnitString( break; } } - if (!checkExponentOperations(unit_string)) { - return false; - } + } + + if (!checkExponentOperations(unit_string)) { + return false; } return true; @@ -4774,7 +4823,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 +5346,86 @@ 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 +5761,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..a41bf1ea 100644 --- a/units/units_decl.hpp +++ b/units/units_decl.hpp @@ -460,7 +460,6 @@ namespace detail { 0 : (power / 2) * ((second_ < 0) || (power < 0) ? 9 : -9); } - // needs to be defined for the full 32 bits(or 64 bits) signed int meter_ : bitwidth::meter; signed int second_ : bitwidth::second; // 8