units_complexconverter.cpp (11712B)
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 <cmath> 9 10 #include "cmemory.h" 11 #include "number_decimalquantity.h" 12 #include "number_roundingutils.h" 13 #include "putilimp.h" 14 #include "uarrsort.h" 15 #include "uassert.h" 16 #include "unicode/fmtable.h" 17 #include "unicode/localpointer.h" 18 #include "unicode/measunit.h" 19 #include "unicode/measure.h" 20 #include "units_complexconverter.h" 21 #include "units_converter.h" 22 23 U_NAMESPACE_BEGIN 24 namespace units { 25 ComplexUnitsConverter::ComplexUnitsConverter(const MeasureUnitImpl &targetUnit, 26 const ConversionRates &ratesInfo, UErrorCode &status) 27 : units_(targetUnit.extractIndividualUnitsWithIndices(status)) { 28 if (U_FAILURE(status)) { 29 return; 30 } 31 U_ASSERT(units_.length() != 0); 32 33 // Just borrowing a pointer to the instance 34 MeasureUnitImpl *biggestUnit = &units_[0]->unitImpl; 35 for (int32_t i = 1; i < units_.length(); i++) { 36 if (UnitsConverter::compareTwoUnits(units_[i]->unitImpl, *biggestUnit, ratesInfo, status) > 0 && 37 U_SUCCESS(status)) { 38 biggestUnit = &units_[i]->unitImpl; 39 } 40 41 if (U_FAILURE(status)) { 42 return; 43 } 44 } 45 46 this->init(*biggestUnit, ratesInfo, status); 47 } 48 49 ComplexUnitsConverter::ComplexUnitsConverter(StringPiece inputUnitIdentifier, 50 StringPiece outputUnitsIdentifier, UErrorCode &status) { 51 if (U_FAILURE(status)) { 52 return; 53 } 54 MeasureUnitImpl inputUnit = MeasureUnitImpl::forIdentifier(inputUnitIdentifier, status); 55 MeasureUnitImpl outputUnits = MeasureUnitImpl::forIdentifier(outputUnitsIdentifier, status); 56 57 this->units_ = outputUnits.extractIndividualUnitsWithIndices(status); 58 U_ASSERT(units_.length() != 0); 59 60 this->init(inputUnit, ConversionRates(status), status); 61 } 62 63 ComplexUnitsConverter::ComplexUnitsConverter(const MeasureUnitImpl &inputUnit, 64 const MeasureUnitImpl &outputUnits, 65 const ConversionRates &ratesInfo, UErrorCode &status) 66 : units_(outputUnits.extractIndividualUnitsWithIndices(status)) { 67 if (U_FAILURE(status)) { 68 return; 69 } 70 71 U_ASSERT(units_.length() != 0); 72 73 this->init(inputUnit, ratesInfo, status); 74 } 75 76 void ComplexUnitsConverter::init(const MeasureUnitImpl &inputUnit, 77 const ConversionRates &ratesInfo, 78 UErrorCode &status) { 79 // Sorts units in descending order. Therefore, we return -1 if 80 // the left is bigger than right and so on. 81 auto descendingCompareUnits = [](const void *context, const void *left, const void *right) { 82 UErrorCode status = U_ZERO_ERROR; 83 84 const auto *leftPointer = static_cast<const MeasureUnitImplWithIndex *const *>(left); 85 const auto *rightPointer = static_cast<const MeasureUnitImplWithIndex *const *>(right); 86 87 // Multiply by -1 to sort in descending order 88 return (-1) * UnitsConverter::compareTwoUnits((**leftPointer).unitImpl, // 89 (**rightPointer).unitImpl, // 90 *static_cast<const ConversionRates *>(context), // 91 status); 92 }; 93 94 uprv_sortArray(units_.getAlias(), // 95 units_.length(), // 96 sizeof units_[0], /* NOTE: we have already asserted that the units_ is not empty.*/ // 97 descendingCompareUnits, // 98 &ratesInfo, // 99 false, // 100 &status // 101 ); 102 103 // In case the `outputUnits` are `UMEASURE_UNIT_MIXED` such as `foot+inch`. In this case we need more 104 // converters to convert from the `inputUnit` to the first unit in the `outputUnits`. Then, a 105 // converter from the first unit in the `outputUnits` to the second unit and so on. 106 // For Example: 107 // - inputUnit is `meter` 108 // - outputUnits is `foot+inch` 109 // - Therefore, we need to have two converters: 110 // 1. a converter from `meter` to `foot` 111 // 2. a converter from `foot` to `inch` 112 // - Therefore, if the input is `2 meter`: 113 // 1. convert `meter` to `foot` --> 2 meter to 6.56168 feet 114 // 2. convert the residual of 6.56168 feet (0.56168) to inches, which will be (6.74016 115 // inches) 116 // 3. then, the final result will be (6 feet and 6.74016 inches) 117 for (int i = 0, n = units_.length(); i < n; i++) { 118 if (i == 0) { // first element 119 unitsConverters_.emplaceBackAndCheckErrorCode(status, inputUnit, units_[i]->unitImpl, 120 ratesInfo, status); 121 } else { 122 unitsConverters_.emplaceBackAndCheckErrorCode(status, units_[i - 1]->unitImpl, 123 units_[i]->unitImpl, ratesInfo, status); 124 } 125 126 if (U_FAILURE(status)) { 127 return; 128 } 129 } 130 } 131 132 UBool ComplexUnitsConverter::greaterThanOrEqual(double quantity, double limit) const { 133 U_ASSERT(unitsConverters_.length() > 0); 134 135 // First converter converts to the biggest quantity. 136 double newQuantity = unitsConverters_[0]->convert(quantity); 137 return newQuantity >= limit; 138 } 139 140 MaybeStackVector<Measure> ComplexUnitsConverter::convert(double quantity, 141 icu::number::impl::RoundingImpl *rounder, 142 UErrorCode &status) const { 143 // TODO: return an error for "foot-and-foot"? 144 MaybeStackVector<Measure> result; 145 int sign = 1; 146 if (quantity < 0 && unitsConverters_.length() > 1) { 147 quantity *= -1; 148 sign = -1; 149 } 150 151 // For N converters: 152 // - the first converter converts from the input unit to the largest unit, 153 // - the following N-2 converters convert to bigger units for which we want integers, 154 // - the Nth converter (index N-1) converts to the smallest unit, for which 155 // we keep a double. 156 MaybeStackArray<int64_t, 5> intValues(unitsConverters_.length() - 1, status); 157 if (U_FAILURE(status)) { 158 return result; 159 } 160 uprv_memset(intValues.getAlias(), 0, (unitsConverters_.length() - 1) * sizeof(int64_t)); 161 162 for (int i = 0, n = unitsConverters_.length(); i < n; ++i) { 163 quantity = (*unitsConverters_[i]).convert(quantity); 164 if (i < n - 1) { 165 // If quantity is at the limits of double's precision from an 166 // integer value, we take that integer value. 167 int64_t flooredQuantity; 168 if (uprv_isNaN(quantity)) { 169 // With clang on Linux: floor does not support NaN, resulting in 170 // a giant negative number. For now, we produce "0 feet, NaN 171 // inches". TODO(icu-units#131): revisit desired output. 172 flooredQuantity = 0; 173 } else { 174 flooredQuantity = static_cast<int64_t>(floor(quantity * (1 + DBL_EPSILON))); 175 } 176 intValues[i] = flooredQuantity; 177 178 // Keep the residual of the quantity. 179 // For example: `3.6 feet`, keep only `0.6 feet` 180 double remainder = quantity - flooredQuantity; 181 if (remainder < 0) { 182 // Because we nudged flooredQuantity up by eps, remainder may be 183 // negative: we must treat such a remainder as zero. 184 quantity = 0; 185 } else { 186 quantity = remainder; 187 } 188 } 189 } 190 191 applyRounder(intValues, quantity, rounder, status); 192 193 // Initialize empty result. We use a MaybeStackArray directly so we can 194 // assign pointers - for this privilege we have to take care of cleanup. 195 MaybeStackArray<Measure *, 4> tmpResult(unitsConverters_.length(), status); 196 if (U_FAILURE(status)) { 197 return result; 198 } 199 200 // Package values into temporary Measure instances in tmpResult: 201 for (int i = 0, n = unitsConverters_.length(); i < n; ++i) { 202 if (i < n - 1) { 203 Formattable formattableQuantity(intValues[i] * sign); 204 // Measure takes ownership of the MeasureUnit* 205 MeasureUnit *type = new MeasureUnit(units_[i]->unitImpl.copy(status).build(status)); 206 tmpResult[units_[i]->index] = new Measure(formattableQuantity, type, status); 207 } else { // LAST ELEMENT 208 Formattable formattableQuantity(quantity * sign); 209 // Measure takes ownership of the MeasureUnit* 210 MeasureUnit *type = new MeasureUnit(units_[i]->unitImpl.copy(status).build(status)); 211 tmpResult[units_[i]->index] = new Measure(formattableQuantity, type, status); 212 } 213 } 214 215 // Transfer values into result and return: 216 for(int32_t i = 0, n = unitsConverters_.length(); i < n; ++i) { 217 U_ASSERT(tmpResult[i] != nullptr); 218 result.emplaceBackAndCheckErrorCode(status, *tmpResult[i]); 219 delete tmpResult[i]; 220 } 221 222 return result; 223 } 224 225 void ComplexUnitsConverter::applyRounder(MaybeStackArray<int64_t, 5> &intValues, double &quantity, 226 icu::number::impl::RoundingImpl *rounder, 227 UErrorCode &status) const { 228 if (uprv_isInfinite(quantity) || uprv_isNaN(quantity)) { 229 // Inf and NaN can't be rounded, and calculating `carry` below is known 230 // to fail on Gentoo on HPPA and OpenSUSE on riscv64. Nothing to do. 231 return; 232 } 233 234 if (rounder == nullptr) { 235 // Nothing to do for the quantity. 236 return; 237 } 238 239 number::impl::DecimalQuantity decimalQuantity; 240 decimalQuantity.setToDouble(quantity); 241 rounder->apply(decimalQuantity, status); 242 if (U_FAILURE(status)) { 243 return; 244 } 245 quantity = decimalQuantity.toDouble(); 246 247 int32_t lastIndex = unitsConverters_.length() - 1; 248 if (lastIndex == 0) { 249 // Only one element, no need to bubble up the carry 250 return; 251 } 252 253 // Check if there's a carry, and bubble it back up the resulting intValues. 254 int64_t carry = static_cast<int64_t>(floor(unitsConverters_[lastIndex]->convertInverse(quantity) * (1 + DBL_EPSILON))); 255 if (carry <= 0) { 256 return; 257 } 258 quantity -= unitsConverters_[lastIndex]->convert(static_cast<double>(carry)); 259 intValues[lastIndex - 1] += carry; 260 261 // We don't use the first converter: that one is for the input unit 262 for (int32_t j = lastIndex - 1; j > 0; j--) { 263 carry = static_cast<int64_t>(floor(unitsConverters_[j]->convertInverse(static_cast<double>(intValues[j])) * (1 + DBL_EPSILON))); 264 if (carry <= 0) { 265 return; 266 } 267 intValues[j] -= static_cast<int64_t>(round(unitsConverters_[j]->convert(static_cast<double>(carry)))); 268 intValues[j - 1] += carry; 269 } 270 } 271 272 } // namespace units 273 U_NAMESPACE_END 274 275 #endif /* #if !UCONFIG_NO_FORMATTING */