Merge pull request #10 from icu-units/get_percision

Add precision to the output of UnitsRouter#route
This commit is contained in:
Hugo van der Merwe 2020-07-20 14:25:10 +02:00 committed by GitHub
commit 030bda3ec8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 60 additions and 29 deletions

View file

@ -12,7 +12,6 @@
#include "uassert.h"
#include "unicode/fmtable.h"
#include "unicode/localpointer.h"
#include "unicode/measure.h"
#include "unitconverter.h"
U_NAMESPACE_BEGIN

View file

@ -9,14 +9,12 @@
#include "cmemory.h"
#include "unicode/measunit.h"
#include "unicode/measure.h"
#include "unitconverter.h"
#include "unitsdata.h"
U_NAMESPACE_BEGIN
// Forward declarations
class Measure;
namespace units {
/**

View file

@ -99,15 +99,17 @@ void UsagePrefsHandler::processQuantity(DecimalQuantity &quantity, MicroProps &m
}
quantity.roundToInfinity(); // Enables toDouble
auto routed = fUnitsRouter.route(quantity.toDouble(), status);
micros.outputUnit = routed[0]->getUnit();
quantity.setToDouble(routed[0]->getNumber().getDouble());
const auto routed = fUnitsRouter.route(quantity.toDouble(), status);
const auto& routedUnits = routed.measures;
micros.outputUnit = routedUnits[0]->getUnit();
quantity.setToDouble(routedUnits[0]->getNumber().getDouble());
// TODO(units): here we are always overriding Precision. (1) get precision
// from fUnitsRouter, (2) ensure we use the UnitPreference skeleton's
// precision only when there isn't an explicit override we prefer to use.
// This needs to be handled within
// NumberFormatterImpl::macrosToMicroGenerator in number_formatimpl.cpp
// TODO: Use precision from `routed` result.
Precision precision = Precision::integer().withMinDigits(2);
UNumberFormatRoundingMode roundingMode;
// Temporary until ICU 64?

View file

@ -248,9 +248,7 @@ class UnitPreferencesSink : public ResourceSink {
dq.setToDecNumber(geq.data(), status);
up->geq = dq.toDouble();
} else if (uprv_strcmp(key, "skeleton") == 0) {
int32_t length;
const UChar *s = value.getString(length, status);
up->skeleton.appendInvariantChars(s, length, status);
up->skeleton = value.getUnicodeString(status);
}
}
}

View file

@ -7,6 +7,8 @@
#ifndef __GETUNITSDATA_H__
#define __GETUNITSDATA_H__
#include <limits>
#include "charstr.h"
#include "cmemory.h"
#include "unicode/stringpiece.h"
@ -96,10 +98,11 @@ class U_I18N_API ConversionRates {
// Encapsulates unitPreferenceData information from units resources, specifying
// a sequence of output unit preferences.
struct U_I18N_API UnitPreference : public UMemory {
UnitPreference() : geq(1) {}
// Set geq to 1.0 by default
UnitPreference() : geq(1.0) {}
CharString unit;
double geq;
CharString skeleton;
UnicodeString skeleton;
};
namespace {

View file

@ -37,30 +37,49 @@ UnitsRouter::UnitsRouter(MeasureUnit inputUnit, StringPiece region, StringPiece
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, complexTargetUnit);
converterPreferences_.emplaceBackAndCheckErrorCode(status, inputUnit, complexTargetUnit,
preference.geq, conversionRates, status);
converterPreferences_.emplaceBackAndCheckErrorCode(
status, inputUnit, complexTargetUnit, preference.geq, precision, conversionRates, status);
if (U_FAILURE(status)) {
return;
}
}
}
MaybeStackVector<Measure> UnitsRouter::route(double quantity, UErrorCode &status) const {
RouteResult UnitsRouter::route(double quantity, UErrorCode &status) const {
for (int i = 0, n = converterPreferences_.length(); i < n; i++) {
const auto &converterPreference = *converterPreferences_[i];
if (converterPreference.converter.greaterThanOrEqual(quantity, converterPreference.limit)) {
return converterPreference.converter.convert(quantity, status);
return RouteResult(converterPreference.converter.convert(quantity, status), //
converterPreference.precision //
);
}
}
// In case of the `quantity` does not fit in any converter limit, use the last converter.
const auto &lastConverter = (*converterPreferences_[converterPreferences_.length() - 1]).converter;
return lastConverter.convert(quantity, status);
const auto &lastConverterPreference = (*converterPreferences_[converterPreferences_.length() - 1]);
return RouteResult(lastConverterPreference.converter.convert(quantity, status), //
lastConverterPreference.precision //
);
}
const MaybeStackVector<MeasureUnit> *UnitsRouter::getOutputUnits() const {
// TODO: consider pulling this from converterPreferences_ and dropping
// outputUnits_?
return &outputUnits_;
}

View file

@ -23,6 +23,14 @@ class Measure;
namespace units {
struct RouteResult : UMemory {
MaybeStackVector<Measure> measures;
UnicodeString precision;
RouteResult(MaybeStackVector<Measure> measures, UnicodeString precision)
: measures(std::move(measures)), precision(std::move(precision)) {}
};
/**
* Contains the complex unit converter and the limit which representing the smallest value that the
* converter should accept. For example, if the converter is converting to `foot+inch` and the limit
@ -35,15 +43,17 @@ namespace units {
struct ConverterPreference : UMemory {
ComplexUnitsConverter converter;
double limit;
UnicodeString precision;
// In case there is no limit, the limit will be -inf.
ConverterPreference(MeasureUnit source, MeasureUnit complexTarget, UnicodeString precision,
const ConversionRates &ratesInfo, UErrorCode &status)
: ConverterPreference(source, complexTarget, std::numeric_limits<double>::lowest(), precision,
ratesInfo, status) {}
ConverterPreference(MeasureUnit source, MeasureUnit complexTarget, double limit,
const ConversionRates &ratesInfo, UErrorCode &status)
: converter(source, complexTarget, ratesInfo, status), limit(limit) {}
ConverterPreference(MeasureUnit source, MeasureUnit complexTarget, const ConversionRates &ratesInfo,
UErrorCode &status)
: ConverterPreference(source, complexTarget, std::numeric_limits<double>::lowest(), ratesInfo,
status) {}
UnicodeString precision, const ConversionRates &ratesInfo, UErrorCode &status)
: converter(source, complexTarget, ratesInfo, status), limit(limit), precision(precision) {}
};
/**
@ -78,7 +88,7 @@ class U_I18N_API UnitsRouter {
public:
UnitsRouter(MeasureUnit inputUnit, StringPiece locale, StringPiece usage, UErrorCode &status);
MaybeStackVector<Measure> route(double quantity, UErrorCode &status) const;
RouteResult route(double quantity, UErrorCode &status) const;
/**
* Returns the list of possible output units, i.e. the full set of
@ -90,7 +100,9 @@ class U_I18N_API UnitsRouter {
const MaybeStackVector<MeasureUnit> *getOutputUnits() const;
private:
// List of possible output units
// List of possible output units. TODO: converterPreferences_ now also has
// this data available. Maybe drop outputUnits_ and have getOutputUnits
// construct a the list from data in converterPreferences_ instead?
MaybeStackVector<MeasureUnit> outputUnits_;
MaybeStackVector<ConverterPreference> converterPreferences_;

View file

@ -617,12 +617,12 @@ void unitPreferencesTestDataLineFn(void *context, char *fields[][2], int32_t fie
if (status.errIfFailureAndReset("Failure before router.route")) {
return;
}
MaybeStackVector<Measure> result = router.route(inputAmount, status);
auto routeResult = router.route(inputAmount, status);
if (status.errIfFailureAndReset("router.route(inputAmount, ...)")) {
return;
}
// TODO: revisit this experimentally chosen precision:
checkOutput(unitsTest, msg.data(), expected, result, 0.0000000001);
checkOutput(unitsTest, msg.data(), expected, routeResult.measures, 0.0000000001);
}
/**