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