ICU-20775 Complete set of toDecimalNumber functions in ICU4C

See #1310
This commit is contained in:
Shane F. Carr 2020-09-12 00:18:33 +00:00
parent a667b279d4
commit 6198151510
14 changed files with 323 additions and 22 deletions

View file

@ -13,6 +13,7 @@
#include "number_utypes.h"
#include "numparse_types.h"
#include "formattedval_impl.h"
#include "number_decnum.h"
#include "unicode/numberformatter.h"
#include "unicode/unumberformatter.h"
@ -196,6 +197,23 @@ unumf_resultGetAllFieldPositions(const UFormattedNumber* uresult, UFieldPosition
result->fData.getAllFieldPositions(fpih, *ec);
}
U_CAPI int32_t U_EXPORT2
unumf_resultToDecimalNumber(
const UFormattedNumber* uresult,
char* dest,
int32_t destCapacity,
UErrorCode* ec) {
const auto* result = UFormattedNumberApiHelper::validate(uresult, *ec);
if (U_FAILURE(*ec)) {
return 0;
}
DecNum decnum;
return result->fData.quantity
.toDecNum(decnum, *ec)
.toCharString(*ec)
.extract(dest, destCapacity, *ec);
}
U_CAPI void U_EXPORT2
unumf_close(UNumberFormatter* f) {
UErrorCode localStatus = U_ZERO_ERROR;

View file

@ -627,7 +627,7 @@ double DecimalQuantity::toDouble() const {
&count);
}
void DecimalQuantity::toDecNum(DecNum& output, UErrorCode& status) const {
DecNum& DecimalQuantity::toDecNum(DecNum& output, UErrorCode& status) const {
// Special handling for zero
if (precision == 0) {
output.setTo("0", status);
@ -637,12 +637,13 @@ void DecimalQuantity::toDecNum(DecNum& output, UErrorCode& status) const {
// The decNumber constructor expects most-significant first, but we store least-significant first.
MaybeStackArray<uint8_t, 20> ubcd(precision, status);
if (U_FAILURE(status)) {
return;
return output;
}
for (int32_t m = 0; m < precision; m++) {
ubcd[precision - m - 1] = static_cast<uint8_t>(getDigitPos(m));
}
output.setTo(ubcd.getAlias(), precision, scale, isNegative(), status);
return output;
}
void DecimalQuantity::truncate() {

View file

@ -209,7 +209,7 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory {
double toDouble() const;
/** Computes a DecNum representation of this DecimalQuantity, saving it to the output parameter. */
void toDecNum(DecNum& output, UErrorCode& status) const;
DecNum& toDecNum(DecNum& output, UErrorCode& status) const;
DecimalQuantity &setToInt(int32_t n);

View file

@ -9,6 +9,7 @@
#include "decNumber.h"
#include "charstr.h"
#include "bytesinkutil.h"
U_NAMESPACE_BEGIN
@ -57,6 +58,13 @@ class U_I18N_API DecNum : public UMemory {
void toString(ByteSink& output, UErrorCode& status) const;
inline CharString toCharString(UErrorCode& status) const {
CharString cstr;
CharStringByteSink sink(&cstr);
toString(sink, status);
return cstr;
}
inline const decNumber* getRawDecNumber() const {
return fData.getAlias();
}

View file

@ -14,6 +14,7 @@
#include "numparse_types.h"
#include "formattedval_impl.h"
#include "numrange_impl.h"
#include "number_decnum.h"
#include "unicode/numberrangeformatter.h"
#include "unicode/unumberrangeformatter.h"
@ -138,6 +139,39 @@ unumrf_resultGetIdentityResult(
return result->fData.identityResult;
}
U_CAPI int32_t U_EXPORT2
unumrf_resultGetFirstDecimalNumber(
const UFormattedNumberRange* uresult,
char* dest,
int32_t destCapacity,
UErrorCode* ec) {
const auto* result = UFormattedNumberRangeApiHelper::validate(uresult, *ec);
if (U_FAILURE(*ec)) {
return 0;
}
DecNum decnum;
return result->fData.quantity1.toDecNum(decnum, *ec)
.toCharString(*ec)
.extract(dest, destCapacity, *ec);
}
U_CAPI int32_t U_EXPORT2
unumrf_resultGetSecondDecimalNumber(
const UFormattedNumberRange* uresult,
char* dest,
int32_t destCapacity,
UErrorCode* ec) {
const auto* result = UFormattedNumberRangeApiHelper::validate(uresult, *ec);
if (U_FAILURE(*ec)) {
return 0;
}
DecNum decnum;
return result->fData.quantity2
.toDecNum(decnum, *ec)
.toCharString(*ec)
.extract(dest, destCapacity, *ec);
}
U_CAPI void U_EXPORT2
unumrf_close(UNumberRangeFormatter* f) {
UErrorCode localStatus = U_ZERO_ERROR;

View file

@ -12,6 +12,7 @@
#include "numrange_impl.h"
#include "util.h"
#include "number_utypes.h"
#include "number_decnum.h"
using namespace icu;
using namespace icu::number;
@ -389,6 +390,14 @@ UnicodeString FormattedNumberRange::getSecondDecimal(UErrorCode& status) const {
return fData->quantity2.toScientificString();
}
void FormattedNumberRange::getDecimalNumbers(ByteSink& sink1, ByteSink& sink2, UErrorCode& status) const {
UPRV_FORMATTED_VALUE_METHOD_GUARD(UPRV_NOARG)
impl::DecNum decnum1;
impl::DecNum decnum2;
fData->quantity1.toDecNum(decnum1, status).toString(sink1, status);
fData->quantity2.toDecNum(decnum2, status).toString(sink2, status);
}
UNumberRangeIdentityResult FormattedNumberRange::getIdentityResult(UErrorCode& status) const {
UPRV_FORMATTED_VALUE_METHOD_GUARD(UNUM_IDENTITY_RESULT_NOT_EQUAL)
return fData->identityResult;

View file

@ -2679,7 +2679,6 @@ class U_I18N_API FormattedNumber : public UMemory, public FormattedValue {
explicit FormattedNumber(UErrorCode errorCode)
: fData(nullptr), fErrorCode(errorCode) {}
// TODO(ICU-20775): Propose this as API.
void toDecimalNumber(ByteSink& sink, UErrorCode& status) const;
// To give LocalizedNumberFormatter format methods access to this class's constructor:

View file

@ -605,18 +605,22 @@ class U_I18N_API FormattedNumberRange : public UMemory, public FormattedValue {
/** @copydoc FormattedValue::nextPosition() */
UBool nextPosition(ConstrainedFieldPosition& cfpos, UErrorCode& status) const U_OVERRIDE;
#ifndef U_HIDE_DRAFT_API
#ifndef U_HIDE_DEPRECATED_API
/**
* Export the first formatted number as a decimal number. This endpoint
* is useful for obtaining the exact number being printed after scaling
* and rounding have been applied by the number range formatting pipeline.
*
*
* The syntax of the unformatted number is a "numeric string"
* as defined in the Decimal Arithmetic Specification, available at
* http://speleotrove.com/decimal
*
* TODO(ICU-21275): This function will be removed in ICU 69.
* Use getDecimalNumbers() instead.
*
* @param status Set if an error occurs.
* @return A decimal representation of the first formatted number.
* @draft ICU 63
* @deprecated ICU 68 Use getDecimalNumbers instead.
* @see NumberRangeFormatter
* @see #getSecondDecimal
*/
@ -626,17 +630,46 @@ class U_I18N_API FormattedNumberRange : public UMemory, public FormattedValue {
* Export the second formatted number as a decimal number. This endpoint
* is useful for obtaining the exact number being printed after scaling
* and rounding have been applied by the number range formatting pipeline.
*
*
* The syntax of the unformatted number is a "numeric string"
* as defined in the Decimal Arithmetic Specification, available at
* http://speleotrove.com/decimal
*
* TODO(ICU-21275): This function will be removed in ICU 69.
* Use getDecimalNumbers() instead.
*
* @param status Set if an error occurs.
* @return A decimal representation of the second formatted number.
* @draft ICU 63
* @deprecated ICU 68 Use getDecimalNumbers instead.
* @see NumberRangeFormatter
* @see #getFirstDecimal
*/
UnicodeString getSecondDecimal(UErrorCode& status) const;
#endif // U_HIDE_DEPRECATED_API
#ifndef U_HIDE_DRAFT_API
/**
* Extracts the formatted range as a pair of decimal numbers. This endpoint
* is useful for obtaining the exact number being printed after scaling
* and rounding have been applied by the number range formatting pipeline.
*
* The syntax of the unformatted numbers is a "numeric string"
* as defined in the Decimal Arithmetic Specification, available at
* http://speleotrove.com/decimal
*
* Example C++17 call site:
*
* auto [ first, second ] = range.getDecimalNumbers<std::string>(status);
*
* @tparam StringClass A string class compatible with StringByteSink;
* for example, std::string.
* @param status Set if an error occurs.
* @return A pair of StringClasses containing the numeric strings.
* @draft ICU 68
*/
template<typename StringClass>
inline std::pair<StringClass, StringClass> getDecimalNumbers(UErrorCode& status) const;
#endif // U_HIDE_DRAFT_API
/**
@ -698,6 +731,8 @@ class U_I18N_API FormattedNumberRange : public UMemory, public FormattedValue {
void getAllFieldPositionsImpl(FieldPositionIteratorHandler& fpih, UErrorCode& status) const;
void getDecimalNumbers(ByteSink& sink1, ByteSink& sink2, UErrorCode& status) const;
// To give LocalizedNumberRangeFormatter format methods access to this class's constructor:
friend class LocalizedNumberRangeFormatter;
@ -705,6 +740,19 @@ class U_I18N_API FormattedNumberRange : public UMemory, public FormattedValue {
friend struct impl::UFormattedNumberRangeImpl;
};
#ifndef U_HIDE_DRAFT_API
// Note: This is draft ICU 68
template<typename StringClass>
std::pair<StringClass, StringClass> FormattedNumberRange::getDecimalNumbers(UErrorCode& status) const {
StringClass str1;
StringClass str2;
StringByteSink<StringClass> sink1(&str1);
StringByteSink<StringClass> sink2(&str2);
getDecimalNumbers(sink1, sink2, status);
return std::make_pair(str1, str2);
}
#endif // U_HIDE_DRAFT_API
/**
* See the main description in numberrangeformatter.h for documentation and examples.
*

View file

@ -659,11 +659,32 @@ unumf_resultGetAllFieldPositions(const UFormattedNumber* uresult, UFieldPosition
UErrorCode* ec);
// TODO(ICU-20775): Propose this as API.
// NOTE: This is not currently implemented.
// U_CAPI int32_t U_EXPORT2
// unumf_resultToDecimalNumber(const UFormattedNumber* uresult, char* buffer, int32_t bufferCapacity,
// UErrorCode* ec);
#ifndef U_HIDE_DRAFT_API
/**
* Extracts the formatted number as a "numeric string" conforming to the
* syntax defined in the Decimal Arithmetic Specification, available at
* http://speleotrove.com/decimal
*
* This endpoint is useful for obtaining the exact number being printed
* after scaling and rounding have been applied by the number formatter.
*
* @param uresult The input object containing the formatted number.
* @param dest the 8-bit char buffer into which the decimal number is placed
* @param destCapacity The size, in chars, of the destination buffer. May be zero
* for precomputing the required size.
* @param ec receives any error status.
* If U_BUFFER_OVERFLOW_ERROR: Returns number of chars for
* preflighting.
* @return Number of chars in the data. Does not include a trailing NUL.
* @draft ICU 68
*/
U_CAPI int32_t U_EXPORT2
unumf_resultToDecimalNumber(
const UFormattedNumber* uresult,
char* dest,
int32_t destCapacity,
UErrorCode* ec);
#endif // U_HIDE_DRAFT_API
/**

View file

@ -354,15 +354,60 @@ unumrf_resultGetIdentityResult(
UErrorCode* ec);
#ifndef U_HIDE_DRAFT_API
/**
* Extracts the first formatted number as a decimal number. This endpoint
* is useful for obtaining the exact number being printed after scaling
* and rounding have been applied by the number range formatting pipeline.
*
* The syntax of the unformatted number is a "numeric string"
* as defined in the Decimal Arithmetic Specification, available at
* http://speleotrove.com/decimal
*
* @param uresult The input object containing the formatted number range.
* @param dest the 8-bit char buffer into which the decimal number is placed
* @param destCapacity The size, in chars, of the destination buffer. May be zero
* for precomputing the required size.
* @param ec receives any error status.
* If U_BUFFER_OVERFLOW_ERROR: Returns number of chars for
* preflighting.
* @return Number of chars in the data. Does not include a trailing NUL.
* @draft ICU 68
*/
U_CAPI int32_t U_EXPORT2
unumrf_resultGetFirstDecimalNumber(
const UFormattedNumberRange* uresult,
char* dest,
int32_t destCapacity,
UErrorCode* ec);
// TODO(ICU-20775): Propose these as API.
// NOTE: This is not currently implemented.
// U_CAPI int32_t U_EXPORT2
// unumf_resultGetFirstDecimal(const UFormattedNumberRange* uresult, char* buffer, int32_t bufferCapacity,
// UErrorCode* ec);
// U_CAPI int32_t U_EXPORT2
// unumf_resultGetSecondDecimal(const UFormattedNumberRange* uresult, char* buffer, int32_t bufferCapacity,
// UErrorCode* ec);
/**
* Extracts the second formatted number as a decimal number. This endpoint
* is useful for obtaining the exact number being printed after scaling
* and rounding have been applied by the number range formatting pipeline.
*
* The syntax of the unformatted number is a "numeric string"
* as defined in the Decimal Arithmetic Specification, available at
* http://speleotrove.com/decimal
*
* @param uresult The input object containing the formatted number range.
* @param dest the 8-bit char buffer into which the decimal number is placed
* @param destCapacity The size, in chars, of the destination buffer. May be zero
* for precomputing the required size.
* @param ec receives any error status.
* If U_BUFFER_OVERFLOW_ERROR: Returns number of chars for
* preflighting.
* @return Number of chars in the data. Does not include a trailing NUL.
* @draft ICU 68
*/
U_CAPI int32_t U_EXPORT2
unumrf_resultGetSecondDecimalNumber(
const UFormattedNumberRange* uresult,
char* dest,
int32_t destCapacity,
UErrorCode* ec);
#endif // U_HIDE_DRAFT_API
/**

View file

@ -28,6 +28,8 @@ static void TestFormattedValue(void);
static void TestSkeletonParseError(void);
static void TestToDecimalNumber(void);
static void TestPerUnitInArabic(void);
void addUNumberFormatterTest(TestNode** root);
@ -40,6 +42,7 @@ void addUNumberFormatterTest(TestNode** root) {
TESTCASE(TestExampleCode);
TESTCASE(TestFormattedValue);
TESTCASE(TestSkeletonParseError);
TESTCASE(TestToDecimalNumber);
TESTCASE(TestPerUnitInArabic);
}
@ -259,6 +262,35 @@ static void TestSkeletonParseError() {
unumf_close(uformatter);
}
static void TestToDecimalNumber() {
UErrorCode ec = U_ZERO_ERROR;
UNumberFormatter* uformatter = unumf_openForSkeletonAndLocale(
u"currency/USD",
-1,
"en-US",
&ec);
assertSuccessCheck("Should create without error", &ec, TRUE);
UFormattedNumber* uresult = unumf_openResult(&ec);
assertSuccess("Should create result without error", &ec);
unumf_formatDouble(uformatter, 3.0, uresult, &ec);
const UChar* str = ufmtval_getString(unumf_resultAsValue(uresult, &ec), NULL, &ec);
assertSuccessCheck("Formatting should succeed", &ec, TRUE);
assertUEquals("Should produce expected string result", u"$3.00", str);
char buffer[CAPACITY];
int32_t len = unumf_resultToDecimalNumber(uresult, buffer, CAPACITY, &ec);
assertIntEquals("Length should be as expected", strlen(buffer), len);
assertEquals("Decimal should be as expected", "3", buffer);
// cleanup:
unumf_closeResult(uresult);
unumf_close(uformatter);
}
static void TestPerUnitInArabic() {
const char* simpleMeasureUnits[] = {
"area-acre",

View file

@ -25,6 +25,8 @@ static void TestFormattedValue(void);
static void TestSkeletonParseError(void);
static void TestGetDecimalNumbers(void);
void addUNumberRangeFormatterTest(TestNode** root);
#define TESTCASE(x) addTest(root, &x, "tsformat/unumberrangeformatter/" #x)
@ -33,9 +35,13 @@ void addUNumberRangeFormatterTest(TestNode** root) {
TESTCASE(TestExampleCode);
TESTCASE(TestFormattedValue);
TESTCASE(TestSkeletonParseError);
TESTCASE(TestGetDecimalNumbers);
}
#define CAPACITY 30
static void TestExampleCode() {
// This is the example code given in unumberrangeformatter.h.
@ -61,6 +67,7 @@ static void TestExampleCode() {
const UChar* str = ufmtval_getString(unumrf_resultAsValue(uresult, &ec), &len, &ec);
assertSuccessCheck("There should not be a failure in the example code", &ec, TRUE);
assertUEquals("Should produce expected string result", u"$3 $5", str);
assertIntEquals("Length should be as expected", u_strlen(str), len);
// Cleanup:
unumrf_close(uformatter);
@ -149,4 +156,39 @@ static void TestSkeletonParseError() {
unumrf_close(uformatter);
}
static void TestGetDecimalNumbers() {
UErrorCode ec = U_ZERO_ERROR;
UNumberRangeFormatter* uformatter = unumrf_openForSkeletonWithCollapseAndIdentityFallback(
u"currency/USD",
-1,
UNUM_RANGE_COLLAPSE_AUTO,
UNUM_IDENTITY_FALLBACK_APPROXIMATELY,
"en-US",
NULL,
&ec);
assertSuccessCheck("Should create without error", &ec, TRUE);
UFormattedNumberRange* uresult = unumrf_openResult(&ec);
assertSuccess("Should create result without error", &ec);
unumrf_formatDoubleRange(uformatter, 3.0, 5.0, uresult, &ec);
const UChar* str = ufmtval_getString(unumrf_resultAsValue(uresult, &ec), NULL, &ec);
assertSuccessCheck("Formatting should succeed", &ec, TRUE);
assertUEquals("Should produce expected string result", u"$3.00 \u2013 $5.00", str);
char buffer[CAPACITY];
int32_t len = unumrf_resultGetFirstDecimalNumber(uresult, buffer, CAPACITY, &ec);
assertIntEquals("First len should be as expected", strlen(buffer), len);
assertEquals("First decimal should be as expected", "3", buffer);
len = unumrf_resultGetSecondDecimalNumber(uresult, buffer, CAPACITY, &ec);
assertIntEquals("Second len should be as expected", strlen(buffer), len);
assertEquals("Second decimal should be as expected", "5", buffer);
// cleanup:
unumrf_closeResult(uresult);
unumrf_close(uformatter);
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -286,6 +286,7 @@ class NumberRangeFormatterTest : public IntlTestWithFieldPosition {
void testFieldPositions();
void testCopyMove();
void toObject();
void testGetDecimalNumbers();
void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0);

View file

@ -51,6 +51,7 @@ void NumberRangeFormatterTest::runIndexedTest(int32_t index, UBool exec, const c
TESTCASE_AUTO(testFieldPositions);
TESTCASE_AUTO(testCopyMove);
TESTCASE_AUTO(toObject);
TESTCASE_AUTO(testGetDecimalNumbers);
TESTCASE_AUTO_END;
}
@ -860,6 +861,48 @@ void NumberRangeFormatterTest::toObject() {
}
}
void NumberRangeFormatterTest::testGetDecimalNumbers() {
IcuTestErrorCode status(*this, "testGetDecimalNumbers");
LocalizedNumberRangeFormatter lnf = NumberRangeFormatter::withLocale("en")
.numberFormatterBoth(NumberFormatter::with().unit(USD));
// Range of numbers
{
FormattedNumberRange range = lnf.formatFormattableRange(1, 5, status);
assertEquals("Range: Formatted string should be as expected",
u"$1.00 \u2013 $5.00",
range.toString(status));
auto decimalNumbers = range.getDecimalNumbers<std::string>(status);
// TODO(ICU-21281): DecNum doesn't retain trailing zeros. Is that a problem?
if (logKnownIssue("ICU-21281")) {
assertEquals("First decimal number", "1", decimalNumbers.first.c_str());
assertEquals("Second decimal number", "5", decimalNumbers.second.c_str());
} else {
assertEquals("First decimal number", "1.00", decimalNumbers.first.c_str());
assertEquals("Second decimal number", "5.00", decimalNumbers.second.c_str());
}
}
// Identity fallback
{
FormattedNumberRange range = lnf.formatFormattableRange(3, 3, status);
assertEquals("Identity: Formatted string should be as expected",
u"~$3.00",
range.toString(status));
auto decimalNumbers = range.getDecimalNumbers<std::string>(status);
// NOTE: DecNum doesn't retain trailing zeros. Is that a problem?
// TODO(ICU-21281): DecNum doesn't retain trailing zeros. Is that a problem?
if (logKnownIssue("ICU-21281")) {
assertEquals("First decimal number", "3", decimalNumbers.first.c_str());
assertEquals("Second decimal number", "3", decimalNumbers.second.c_str());
} else {
assertEquals("First decimal number", "3.00", decimalNumbers.first.c_str());
assertEquals("Second decimal number", "3.00", decimalNumbers.second.c_str());
}
}
}
void NumberRangeFormatterTest::assertFormatRange(
const char16_t* message,
const UnlocalizedNumberRangeFormatter& f,