units_router.cpp (5740B)
1 // © 2020 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html 3 4 #include "unicode/utypes.h" 5 6 #if !UCONFIG_NO_FORMATTING 7 8 #include "charstr.h" 9 #include "cmemory.h" 10 #include "cstring.h" 11 #include "measunit_impl.h" 12 #include "number_decimalquantity.h" 13 #include "number_roundingutils.h" 14 #include "resource.h" 15 #include "unicode/measure.h" 16 #include "units_data.h" 17 #include "units_router.h" 18 #include <cmath> 19 20 U_NAMESPACE_BEGIN 21 namespace units { 22 23 using number::Precision; 24 using number::impl::parseIncrementOption; 25 26 Precision UnitsRouter::parseSkeletonToPrecision(icu::UnicodeString precisionSkeleton, 27 UErrorCode &status) { 28 if (U_FAILURE(status)) { 29 // As a member of UsagePrefsHandler, which is a friend of Precision, we 30 // get access to the default constructor. 31 return {}; 32 } 33 constexpr int32_t kSkelPrefixLen = 20; 34 if (!precisionSkeleton.startsWith(UNICODE_STRING_SIMPLE("precision-increment/"))) { 35 status = U_INVALID_FORMAT_ERROR; 36 return {}; 37 } 38 U_ASSERT(precisionSkeleton[kSkelPrefixLen - 1] == u'/'); 39 StringSegment segment(precisionSkeleton, false); 40 segment.adjustOffset(kSkelPrefixLen); 41 Precision result; 42 parseIncrementOption(segment, result, status); 43 return result; 44 } 45 46 UnitsRouter::UnitsRouter(StringPiece inputUnitIdentifier, const Locale &locale, StringPiece usage, 47 UErrorCode &status) { 48 this->init(MeasureUnit::forIdentifier(inputUnitIdentifier, status), locale, usage, status); 49 } 50 51 UnitsRouter::UnitsRouter(const MeasureUnit &inputUnit, const Locale &locale, StringPiece usage, 52 UErrorCode &status) { 53 this->init(std::move(inputUnit), locale, usage, status); 54 } 55 56 void UnitsRouter::init(const MeasureUnit &inputUnit, const Locale &locale, StringPiece usage, 57 UErrorCode &status) { 58 59 if (U_FAILURE(status)) { 60 return; 61 } 62 63 // TODO: do we want to pass in ConversionRates and UnitPreferences instead 64 // of loading in each UnitsRouter instance? (Or make global?) 65 ConversionRates conversionRates(status); 66 UnitPreferences prefs(status); 67 68 MeasureUnitImpl inputUnitImpl = MeasureUnitImpl::forMeasureUnitMaybeCopy(inputUnit, status); 69 MeasureUnitImpl baseUnitImpl = 70 (extractCompoundBaseUnit(inputUnitImpl, conversionRates, status)); 71 CharString category = getUnitQuantity(baseUnitImpl, status); 72 if (U_FAILURE(status)) { 73 return; 74 } 75 76 const MaybeStackVector<UnitPreference> unitPrefs = 77 prefs.getPreferencesFor(category.toStringPiece(), usage, locale, status); 78 for (int32_t i = 0, n = unitPrefs.length(); i < n; ++i) { 79 U_ASSERT(unitPrefs[i] != nullptr); 80 const auto* const preference = unitPrefs[i]; 81 82 MeasureUnitImpl complexTargetUnitImpl = 83 MeasureUnitImpl::forIdentifier(preference->unit.data(), status); 84 if (U_FAILURE(status)) { 85 return; 86 } 87 88 UnicodeString precision = preference->skeleton; 89 90 // For now, we only have "precision-increment" in Units Preferences skeleton. 91 // Therefore, we check if the skeleton starts with "precision-increment" and force the program to 92 // fail otherwise. 93 // NOTE: 94 // It is allowed to have an empty precision. 95 if (!precision.isEmpty() && !precision.startsWith(u"precision-increment", 19)) { 96 status = U_INTERNAL_PROGRAM_ERROR; 97 return; 98 } 99 100 outputUnits_.emplaceBackAndCheckErrorCode(status, 101 complexTargetUnitImpl.copy(status).build(status)); 102 converterPreferences_.emplaceBackAndCheckErrorCode(status, inputUnitImpl, complexTargetUnitImpl, 103 preference->geq, std::move(precision), 104 conversionRates, status); 105 106 if (U_FAILURE(status)) { 107 return; 108 } 109 } 110 } 111 112 RouteResult UnitsRouter::route(double quantity, icu::number::impl::RoundingImpl *rounder, UErrorCode &status) const { 113 // Find the matching preference 114 const ConverterPreference *converterPreference = nullptr; 115 for (int32_t i = 0, n = converterPreferences_.length(); i < n; i++) { 116 converterPreference = converterPreferences_[i]; 117 if (converterPreference->converter.greaterThanOrEqual(std::abs(quantity) * (1 + DBL_EPSILON), 118 converterPreference->limit)) { 119 break; 120 } 121 } 122 U_ASSERT(converterPreference != nullptr); 123 124 // Set up the rounder for this preference's precision 125 if (rounder != nullptr && rounder->fPrecision.isBogus()) { 126 if (converterPreference->precision.length() > 0) { 127 rounder->fPrecision = parseSkeletonToPrecision(converterPreference->precision, status); 128 } else { 129 // We use the same rounding mode as COMPACT notation: known to be a 130 // human-friendly rounding mode: integers, but add a decimal digit 131 // as needed to ensure we have at least 2 significant digits. 132 rounder->fPrecision = Precision::integer().withMinDigits(2); 133 } 134 } 135 136 return RouteResult(converterPreference->converter.convert(quantity, rounder, status), 137 converterPreference->targetUnit.copy(status)); 138 } 139 140 const MaybeStackVector<MeasureUnit> *UnitsRouter::getOutputUnits() const { 141 // TODO: consider pulling this from converterPreferences_ and dropping 142 // outputUnits_? 143 return &outputUnits_; 144 } 145 146 } // namespace units 147 U_NAMESPACE_END 148 149 #endif /* #if !UCONFIG_NO_FORMATTING */