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