]> granicus.if.org Git - icu/commitdiff
add MeasureUnit#simplify method to simplify the units when we check the convertibility
authoryounies <younies@chromium.org>
Sat, 13 Jun 2020 13:28:32 +0000 (15:28 +0200)
committeryounies <younies@chromium.org>
Sat, 13 Jun 2020 13:28:32 +0000 (15:28 +0200)
icu4c/source/i18n/measunit_extra.cpp
icu4c/source/i18n/unicode/measunit.h
icu4c/source/i18n/unitconverter.cpp

index b997167b3113e820d67c94e19119e232c993e27f..d6576e34110f316efdffbabe64094a9b0df3b0b0 100644 (file)
@@ -769,6 +769,71 @@ MeasureUnit MeasureUnit::product(const MeasureUnit& other, UErrorCode& status) c
     return std::move(impl).build(status);
 }
 
+
+/**
+ * Returns the sign of the dimensionality.
+ * 
+ * NOTE:
+ *      it returns `0` when the dimensionality is zero. 
+ */ 
+int32_t dimensionSign(int32_t dimension) {
+    if (dimension == 0) return 0;
+    if (dimension > 0) return 1;
+    return -1;
+}
+
+/**
+ * Searches the `simplifiedUnits` for a unit with the same base identifier in the `newUnit`, for example
+ * `meter` and `meter` or `millimeter` and `centimeter`.
+ * After finding a match, the matched unit in simplified unit will be merged with the `newUnit` and
+ * `true` will be returned. Otherwise, `false` will be returned.
+ */
+bool findAndSet(MaybeStackVector<MeasureUnit> &simplifiedUnits, const MeasureUnit &newUnit,
+                UErrorCode &status) {
+    for (int i = 0, n = simplifiedUnits.length(); i < n; i++) {
+        auto simplifiedUnitImpl = SingleUnitImpl::forMeasureUnit(*simplifiedUnits[i], status);
+        auto newUnitImpl = SingleUnitImpl::forMeasureUnit(newUnit, status);
+        if (simplifiedUnitImpl.identifier == newUnitImpl.identifier) {
+            int32_t newDimensionality = simplifiedUnitImpl.dimensionality + newUnitImpl.dimensionality;
+            UMeasureSIPrefix newSIprefix = static_cast<UMeasureSIPrefix>(
+                simplifiedUnitImpl.siPrefix * dimensionSign(simplifiedUnitImpl.dimensionality) +
+                newUnitImpl.siPrefix * dimensionSign(newUnitImpl.dimensionality));
+
+            auto &simplifiedUnit = *simplifiedUnits[i];
+            simplifiedUnit = simplifiedUnit.withDimensionality(newDimensionality, status);
+            simplifiedUnit = simplifiedUnit.withSIPrefix(newSIprefix ,status);
+
+            return true;
+        }
+    }
+
+    return false;
+}
+
+MeasureUnit MeasureUnit::simplify(UErrorCode &status) const {
+    MeasureUnit result;
+    if (this->getComplexity(status) == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED) {
+        status = U_INTERNAL_PROGRAM_ERROR;
+        return result;
+    }
+
+    int32_t outCount;
+    auto singleUnits = this->splitToSingleUnits(outCount, status);
+
+    MaybeStackVector<MeasureUnit> simplifiedUnits;
+    for (int i = 0 ; i < outCount ; ++i) {
+        if (findAndSet(simplifiedUnits,singleUnits[i] , status )) { continue;}
+
+        simplifiedUnits.emplaceBackAndConfirm(status, singleUnits[i]);
+    }
+
+    for (int i = 0 , n = simplifiedUnits.length() ; i < n ; ++i) {
+        result = result.product(*simplifiedUnits[i] , status);
+    }
+
+    return result;
+}
+
 LocalArray<MeasureUnit> MeasureUnit::splitToSingleUnits(int32_t& outCount, UErrorCode& status) const {
     MeasureUnitImpl temp;
     const MeasureUnitImpl& impl = MeasureUnitImpl::forMeasureUnit(*this, temp, status);
index d221fd88390ace149ad034f0aa7115bacfed251a..d857d8aeb79e2f416409b7e2c58c941290fcdaae 100644 (file)
@@ -430,7 +430,7 @@ class U_I18N_API MeasureUnit: public UObject {
      * For example, if the receiver is "kilowatt" and the argument is "hour-per-day", then the
      * unit "kilowatt-hour-per-day" is returned.
      *
-     * NOTE: Only works on SINGLE and COMPOUND units. If either unit (receivee and argument) is a
+     * NOTE: Only works on SINGLE and COMPOUND units. If either unit (receiver and argument) is a
      * MIXED unit, an error will occur. For more information, see UMeasureUnitComplexity.
      *
      * @param other The MeasureUnit to multiply with the target.
@@ -439,6 +439,20 @@ class U_I18N_API MeasureUnit: public UObject {
      * @draft ICU 67
      */
     MeasureUnit product(const MeasureUnit& other, UErrorCode& status) const;
+
+    /**
+     * Extracts a simplified version of the unit (i.e. no repetition in the internal units). For example,
+     * "square-meter-per-meter" will be "meter".
+     *
+     * NOTE: Only works on SINGLE and COMPOUND units. If the current unit is a
+     * MIXED unit, an error will occur. For more information, see UMeasureUnitComplexity.
+     *
+     * @param status Set if this is a MIXED unit or if another error occurs.
+     * @return a simplified version of the current unit.
+     * @draft ICU 67
+     */
+    MeasureUnit simplify( UErrorCode& status) const;
+
 #endif // U_HIDE_DRAFT_API
 
 #ifndef U_HIDE_INTERNAL_API
index b959a792755fa0601cc65406f3d54dea506dcba0..0711fda2cf7fb089191346eaedb266acadd6832b 100644 (file)
@@ -441,6 +441,12 @@ UnitsConvertibilityState U_I18N_API checkConvertibility(const MeasureUnit &sourc
     if (sourceBaseUnit == targetBaseUnit) return CONVERTIBLE;
     if (sourceBaseUnit == targetBaseUnit.reciprocal(status)) return RECIPROCAL;
 
+    auto sourceSimplified = sourceBaseUnit.simplify(status);
+    auto targetSimplified = targetBaseUnit.simplify(status);
+    
+    if (sourceSimplified == targetSimplified) return CONVERTIBLE;
+    if (sourceSimplified == targetSimplified.reciprocal(status)) return RECIPROCAL;
+
     return UNCONVERTIBLE;
 }