units_router.cpp 5.6 KB

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