[Intl] Speed up Intl.NumberFormat constructor x4
1. Use the newer LocalizedNumberFormatter API which improve
the performance score x3.3.
Here are how I got the performance score:
$ python -u tools/run_perf.py --binary-override-path \
out/x64.release/d8 --filter "JSTests/Intl" \
test/js-perf-test/JSTests5.json
Look for NewIntlNumberFormat-Intl(Score) for 3 runs.
BEFORE: 539 507 507
AFTER: 2009 2069 1994
2. Also add symbol and enum to prepare implementing of the unified
number proposal.
Bug: v8:8515
Change-Id: Ie1ca1dba1e806449632cc96b81d44f0dc61b6093
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1392233
Commit-Queue: Frank Tang <ftang@chromium.org>
Reviewed-by: Sathya Gunasekaran <gsathya@chromium.org>
Reviewed-by: Frank Tang <ftang@chromium.org>
Cr-Commit-Position: refs/heads/master@{#61421}
diff --git a/src/builtins/builtins-intl.cc b/src/builtins/builtins-intl.cc
index a5ef6a3..acf8b2b 100644
--- a/src/builtins/builtins-intl.cc
+++ b/src/builtins/builtins-intl.cc
@@ -482,13 +482,14 @@
Object::ToNumber(isolate, value));
}
- icu::NumberFormat* icu_number_format =
- number_format->icu_number_format()->raw();
- CHECK_NOT_NULL(icu_number_format);
+ icu::number::LocalizedNumberFormatter* icu_localized_number_formatter =
+ number_format->icu_number_formatter()->raw();
+ CHECK_NOT_NULL(icu_localized_number_formatter);
+ // Return FormatNumber(nf, x).
RETURN_RESULT_OR_FAILURE(
- isolate,
- JSNumberFormat::FormatNumeric(isolate, *icu_number_format, numeric_obj));
+ isolate, JSNumberFormat::FormatNumeric(
+ isolate, *icu_localized_number_formatter, numeric_obj));
}
BUILTIN(DateTimeFormatConstructor) {
diff --git a/src/heap-symbols.h b/src/heap-symbols.h
index 478c896..add9262 100644
--- a/src/heap-symbols.h
+++ b/src/heap-symbols.h
@@ -9,20 +9,30 @@
#define INTERNALIZED_STRING_LIST_GENERATOR_INTL(V, _) \
V(_, adoptText_string, "adoptText") \
V(_, baseName_string, "baseName") \
+ V(_, accounting_string, "accounting") \
V(_, breakType_string, "breakType") \
V(_, calendar_string, "calendar") \
V(_, cardinal_string, "cardinal") \
V(_, caseFirst_string, "caseFirst") \
V(_, compare_string, "compare") \
V(_, current_string, "current") \
+ V(_, collation_string, "collation") \
+ V(_, compact_string, "compact") \
+ V(_, currency_string, "currency") \
+ V(_, currencyDisplay_string, "currencyDisplay") \
V(_, dateStyle_string, "dateStyle") \
V(_, day_string, "day") \
V(_, dayPeriod_string, "dayPeriod") \
V(_, decimal_string, "decimal") \
V(_, endRange_string, "endRange") \
+ V(_, engineering_string, "engineering") \
V(_, era_string, "era") \
V(_, first_string, "first") \
V(_, format_string, "format") \
+ V(_, except_zero_string, "except-zero") \
+ V(_, exponentInteger_string, "exponentInteger") \
+ V(_, exponentMinusSign_string, "exponentMinusSign") \
+ V(_, exponentSeparator_string, "exponentSeparator") \
V(_, fraction_string, "fraction") \
V(_, full_string, "full") \
V(_, granularity_string, "granularity") \
@@ -35,9 +45,6 @@
V(_, hour_string, "hour") \
V(_, hour12_string, "hour12") \
V(_, hourCycle_string, "hourCycle") \
- V(_, collation_string, "collation") \
- V(_, currency_string, "currency") \
- V(_, currencyDisplay_string, "currencyDisplay") \
V(_, ideo_string, "ideo") \
V(_, ignorePunctuation_string, "ignorePunctuation") \
V(_, Invalid_Date_string, "Invalid Date") \
@@ -59,6 +66,8 @@
V(_, minute_string, "minute") \
V(_, month_string, "month") \
V(_, nan_string, "nan") \
+ V(_, narrow_symbol_string, "narrow-symbol") \
+ V(_, never_string, "never") \
V(_, none_string, "none") \
V(_, normal_string, "normal") \
V(_, numberingSystem_string, "numberingSystem") \
@@ -68,12 +77,14 @@
V(_, plusSign_string, "plusSign") \
V(_, quarter_string, "quarter") \
V(_, region_string, "region") \
+ V(_, scientific_string, "scientific") \
V(_, second_string, "second") \
V(_, segment_string, "segment") \
V(_, SegmentIterator_string, "Segment Iterator") \
V(_, sensitivity_string, "sensitivity") \
V(_, sep_string, "sep") \
V(_, shared_string, "shared") \
+ V(_, standard_string, "standard") \
V(_, startRange_string, "startRange") \
V(_, strict_string, "strict") \
V(_, style_string, "style") \
diff --git a/src/isolate.cc b/src/isolate.cc
index 56efd34..30299bc 100644
--- a/src/isolate.cc
+++ b/src/isolate.cc
@@ -4626,12 +4626,12 @@
}
#ifdef V8_INTL_SUPPORT
-icu::UObject* Isolate::get_cached_icu_object(ICUObjectCacheType cache_type) {
+icu::UMemory* Isolate::get_cached_icu_object(ICUObjectCacheType cache_type) {
return icu_object_cache_[cache_type].get();
}
void Isolate::set_icu_object_in_cache(ICUObjectCacheType cache_type,
- std::shared_ptr<icu::UObject> obj) {
+ std::shared_ptr<icu::UMemory> obj) {
icu_object_cache_[cache_type] = obj;
}
diff --git a/src/isolate.h b/src/isolate.h
index e35e40b..2fa2aa0 100644
--- a/src/isolate.h
+++ b/src/isolate.h
@@ -38,7 +38,7 @@
#ifdef V8_INTL_SUPPORT
#include "unicode/uversion.h" // Define U_ICU_NAMESPACE.
namespace U_ICU_NAMESPACE {
-class UObject;
+class UMemory;
} // namespace U_ICU_NAMESPACE
#endif // V8_INTL_SUPPORT
@@ -1143,9 +1143,9 @@
kDefaultCollator, kDefaultNumberFormat, kDefaultSimpleDateFormat,
kDefaultSimpleDateFormatForTime, kDefaultSimpleDateFormatForDate};
- icu::UObject* get_cached_icu_object(ICUObjectCacheType cache_type);
+ icu::UMemory* get_cached_icu_object(ICUObjectCacheType cache_type);
void set_icu_object_in_cache(ICUObjectCacheType cache_type,
- std::shared_ptr<icu::UObject> obj);
+ std::shared_ptr<icu::UMemory> obj);
void clear_cached_icu_object(ICUObjectCacheType cache_type);
#endif // V8_INTL_SUPPORT
@@ -1709,7 +1709,7 @@
return static_cast<std::size_t>(a);
}
};
- std::unordered_map<ICUObjectCacheType, std::shared_ptr<icu::UObject>,
+ std::unordered_map<ICUObjectCacheType, std::shared_ptr<icu::UMemory>,
ICUObjectCacheTypeHash>
icu_object_cache_;
diff --git a/src/objects-debug.cc b/src/objects-debug.cc
index e23e570..bf20748 100644
--- a/src/objects-debug.cc
+++ b/src/objects-debug.cc
@@ -2268,7 +2268,7 @@
CHECK(IsJSNumberFormat());
JSObjectVerify(isolate);
VerifyObjectField(isolate, kLocaleOffset);
- VerifyObjectField(isolate, kIcuNumberFormatOffset);
+ VerifyObjectField(isolate, kIcuNumberFormatterOffset);
VerifyObjectField(isolate, kBoundFormatOffset);
CHECK(bound_format()->IsUndefined(isolate) || bound_format()->IsJSFunction());
VerifySmiField(kFlagsOffset);
diff --git a/src/objects-printer.cc b/src/objects-printer.cc
index afdce18..f8fdb5a 100644
--- a/src/objects-printer.cc
+++ b/src/objects-printer.cc
@@ -2164,10 +2164,8 @@
void JSNumberFormat::JSNumberFormatPrint(std::ostream& os) { // NOLINT
JSObjectPrintHeader(os, *this, "JSNumberFormat");
os << "\n - locale: " << Brief(locale());
- os << "\n - icu_number_format: " << Brief(icu_number_format());
+ os << "\n - icu_number_formatter: " << Brief(icu_number_formatter());
os << "\n - bound_format: " << Brief(bound_format());
- os << "\n - style: " << StyleAsString();
- os << "\n - currency_display: " << CurrencyDisplayAsString();
JSObjectPrintBody(os, *this);
}
diff --git a/src/objects/intl-objects.cc b/src/objects/intl-objects.cc
index aa12624..c7cb8df 100644
--- a/src/objects/intl-objects.cc
+++ b/src/objects/intl-objects.cc
@@ -977,7 +977,7 @@
if (can_cache) {
isolate->set_icu_object_in_cache(
Isolate::ICUObjectCacheType::kDefaultCollator,
- std::static_pointer_cast<icu::UObject>(
+ std::static_pointer_cast<icu::UMemory>(
collator->icu_collator()->get()));
}
icu::Collator* icu_collator = collator->icu_collator()->raw();
@@ -1039,9 +1039,10 @@
bool can_cache =
locales->IsUndefined(isolate) && options->IsUndefined(isolate);
if (can_cache) {
- icu::NumberFormat* cached_number_format =
- static_cast<icu::NumberFormat*>(isolate->get_cached_icu_object(
- Isolate::ICUObjectCacheType::kDefaultNumberFormat));
+ icu::number::LocalizedNumberFormatter* cached_number_format =
+ static_cast<icu::number::LocalizedNumberFormatter*>(
+ isolate->get_cached_icu_object(
+ Isolate::ICUObjectCacheType::kDefaultNumberFormat));
// We may use the cached icu::NumberFormat for a fast path.
if (cached_number_format != nullptr) {
return JSNumberFormat::FormatNumeric(isolate, *cached_number_format,
@@ -1062,13 +1063,13 @@
if (can_cache) {
isolate->set_icu_object_in_cache(
Isolate::ICUObjectCacheType::kDefaultNumberFormat,
- std::static_pointer_cast<icu::UObject>(
- number_format->icu_number_format()->get()));
+ std::static_pointer_cast<icu::UMemory>(
+ number_format->icu_number_formatter()->get()));
}
// Return FormatNumber(numberFormat, x).
- icu::NumberFormat* icu_number_format =
- number_format->icu_number_format()->raw();
+ icu::number::LocalizedNumberFormatter* icu_number_format =
+ number_format->icu_number_formatter()->raw();
return JSNumberFormat::FormatNumeric(isolate, *icu_number_format,
numeric_obj);
}
@@ -1130,19 +1131,17 @@
} // namespace
-Maybe<bool> Intl::SetNumberFormatDigitOptions(Isolate* isolate,
- icu::DecimalFormat* number_format,
- Handle<JSReceiver> options,
- int mnfd_default,
- int mxfd_default) {
- CHECK_NOT_NULL(number_format);
+Maybe<Intl::NumberFormatDigitOptions> Intl::SetNumberFormatDigitOptions(
+ Isolate* isolate, Handle<JSReceiver> options, int mnfd_default,
+ int mxfd_default) {
+ Intl::NumberFormatDigitOptions digit_options;
// 5. Let mnid be ? GetNumberOption(options, "minimumIntegerDigits,", 1, 21,
// 1).
int mnid;
if (!GetNumberOption(isolate, options, "minimumIntegerDigits", 1, 21, 1)
.To(&mnid)) {
- return Nothing<bool>();
+ return Nothing<NumberFormatDigitOptions>();
}
// 6. Let mnfd be ? GetNumberOption(options, "minimumFractionDigits", 0, 20,
@@ -1151,7 +1150,7 @@
if (!GetNumberOption(isolate, options, "minimumFractionDigits", 0, 20,
mnfd_default)
.To(&mnfd)) {
- return Nothing<bool>();
+ return Nothing<NumberFormatDigitOptions>();
}
// 7. Let mxfdActualDefault be max( mnfd, mxfdDefault ).
@@ -1163,7 +1162,7 @@
if (!GetNumberOption(isolate, options, "maximumFractionDigits", mnfd, 20,
mxfd_actual_default)
.To(&mxfd)) {
- return Nothing<bool>();
+ return Nothing<NumberFormatDigitOptions>();
}
// 9. Let mnsd be ? Get(options, "minimumSignificantDigits").
@@ -1172,7 +1171,7 @@
isolate->factory()->minimumSignificantDigits_string();
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, mnsd_obj, JSReceiver::GetProperty(isolate, options, mnsd_str),
- Nothing<bool>());
+ Nothing<NumberFormatDigitOptions>());
// 10. Let mxsd be ? Get(options, "maximumSignificantDigits").
Handle<Object> mxsd_obj;
@@ -1180,45 +1179,43 @@
isolate->factory()->maximumSignificantDigits_string();
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, mxsd_obj, JSReceiver::GetProperty(isolate, options, mxsd_str),
- Nothing<bool>());
+ Nothing<NumberFormatDigitOptions>());
// 11. Set intlObj.[[MinimumIntegerDigits]] to mnid.
- number_format->setMinimumIntegerDigits(mnid);
+ digit_options.minimum_integer_digits = mnid;
// 12. Set intlObj.[[MinimumFractionDigits]] to mnfd.
- number_format->setMinimumFractionDigits(mnfd);
+ digit_options.minimum_fraction_digits = mnfd;
// 13. Set intlObj.[[MaximumFractionDigits]] to mxfd.
- number_format->setMaximumFractionDigits(mxfd);
+ digit_options.maximum_fraction_digits = mxfd;
- bool significant_digits_used = false;
// 14. If mnsd is not undefined or mxsd is not undefined, then
if (!mnsd_obj->IsUndefined(isolate) || !mxsd_obj->IsUndefined(isolate)) {
// 14. a. Let mnsd be ? DefaultNumberOption(mnsd, 1, 21, 1).
int mnsd;
if (!DefaultNumberOption(isolate, mnsd_obj, 1, 21, 1, mnsd_str).To(&mnsd)) {
- return Nothing<bool>();
+ return Nothing<NumberFormatDigitOptions>();
}
// 14. b. Let mxsd be ? DefaultNumberOption(mxsd, mnsd, 21, 21).
int mxsd;
if (!DefaultNumberOption(isolate, mxsd_obj, mnsd, 21, 21, mxsd_str)
.To(&mxsd)) {
- return Nothing<bool>();
+ return Nothing<NumberFormatDigitOptions>();
}
- significant_digits_used = true;
-
// 14. c. Set intlObj.[[MinimumSignificantDigits]] to mnsd.
- number_format->setMinimumSignificantDigits(mnsd);
+ digit_options.minimum_significant_digits = mnsd;
// 14. d. Set intlObj.[[MaximumSignificantDigits]] to mxsd.
- number_format->setMaximumSignificantDigits(mxsd);
+ digit_options.maximum_significant_digits = mxsd;
+ } else {
+ digit_options.minimum_significant_digits = 0;
+ digit_options.maximum_significant_digits = 0;
}
- number_format->setSignificantDigitsUsed(significant_digits_used);
- number_format->setRoundingMode(icu::DecimalFormat::kRoundHalfUp);
- return Just(true);
+ return Just(digit_options);
}
namespace {
@@ -1972,6 +1969,11 @@
UNREACHABLE();
return Handle<String>();
+ case UNUM_COMPACT_FIELD:
+ return isolate->factory()->compact_string();
+ case UNUM_MEASURE_UNIT_FIELD:
+ return isolate->factory()->unit_string();
+
default:
UNREACHABLE();
return Handle<String>();
diff --git a/src/objects/intl-objects.h b/src/objects/intl-objects.h
index b35cb75..e14fde2 100644
--- a/src/objects/intl-objects.h
+++ b/src/objects/intl-objects.h
@@ -25,11 +25,10 @@
namespace U_ICU_NAMESPACE {
class BreakIterator;
class Collator;
-class DecimalFormat;
class FormattedValue;
class SimpleDateFormat;
class UnicodeString;
-}
+} // namespace U_ICU_NAMESPACE
namespace v8 {
namespace internal {
@@ -172,9 +171,16 @@
Handle<Object> options);
// ecma402/#sec-setnfdigitoptions
- V8_WARN_UNUSED_RESULT static Maybe<bool> SetNumberFormatDigitOptions(
- Isolate* isolate, icu::DecimalFormat* number_format,
- Handle<JSReceiver> options, int mnfd_default, int mxfd_default);
+ struct NumberFormatDigitOptions {
+ int minimum_integer_digits;
+ int minimum_fraction_digits;
+ int maximum_fraction_digits;
+ int minimum_significant_digits;
+ int maximum_significant_digits;
+ };
+ V8_WARN_UNUSED_RESULT static Maybe<NumberFormatDigitOptions>
+ SetNumberFormatDigitOptions(Isolate* isolate, Handle<JSReceiver> options,
+ int mnfd_default, int mxfd_default);
static icu::Locale CreateICULocale(const std::string& bcp47_locale);
diff --git a/src/objects/intl-objects.tq b/src/objects/intl-objects.tq
index 4b32925..67d8537 100644
--- a/src/objects/intl-objects.tq
+++ b/src/objects/intl-objects.tq
@@ -28,7 +28,8 @@
extern class JSNumberFormat extends JSObject {
locale: String;
- icu_number_format: Foreign; // Managed<icu::NumberFormat>
+ icu_number_formatter:
+ Foreign; // Managed<icu::number::LocalizedNumberFormatter>
bound_format: JSFunction | Undefined;
flags: Smi;
}
diff --git a/src/objects/js-date-time-format.cc b/src/objects/js-date-time-format.cc
index 8efdb2e..93fe151 100644
--- a/src/objects/js-date-time-format.cc
+++ b/src/objects/js-date-time-format.cc
@@ -658,7 +658,7 @@
if (can_cache) {
isolate->set_icu_object_in_cache(
- cache_type, std::static_pointer_cast<icu::UObject>(
+ cache_type, std::static_pointer_cast<icu::UMemory>(
date_time_format->icu_simple_date_format()->get()));
}
// 5. Return FormatDateTime(dateFormat, x).
diff --git a/src/objects/js-number-format-inl.h b/src/objects/js-number-format-inl.h
index 491800b..0374f92 100644
--- a/src/objects/js-number-format-inl.h
+++ b/src/objects/js-number-format-inl.h
@@ -21,35 +21,51 @@
OBJECT_CONSTRUCTORS_IMPL(JSNumberFormat, JSObject)
ACCESSORS(JSNumberFormat, locale, String, kLocaleOffset)
-ACCESSORS(JSNumberFormat, icu_number_format, Managed<icu::NumberFormat>,
- kIcuNumberFormatOffset)
+ACCESSORS(JSNumberFormat, icu_number_formatter,
+ Managed<icu::number::LocalizedNumberFormatter>,
+ kIcuNumberFormatterOffset)
ACCESSORS(JSNumberFormat, bound_format, Object, kBoundFormatOffset)
+
+// Currenct ECMA 402 spec mandate to record (Min|Max)imumFractionDigits
+// uncondictionally while the unified number proposal eventually will only
+// record either (Min|Max)imumFractionDigits or (Min|Max)imumSignaficantDigits
+// Since LocalizedNumberFormatter can only remember one set, and during
+// 2019-1-17 ECMA402 meeting that the committee decide not to take a PR to
+// address that prior to the unified number proposal, we have to add these two
+// 5 bits int into flags to remember the (Min|Max)imumFractionDigits while
+// (Min|Max)imumSignaficantDigits is present.
+// TODO(ftang) remove the following once we ship int-number-format-unified
+// * SMI_ACCESSORS of flags
+// * Four inline functions: (set_)?(min|max)imum_fraction_digits
+
SMI_ACCESSORS(JSNumberFormat, flags, kFlagsOffset)
-inline void JSNumberFormat::set_style(Style style) {
- DCHECK_LT(style, Style::COUNT);
+inline int JSNumberFormat::minimum_fraction_digits() const {
+ return MinimumFractionDigitsBits::decode(flags());
+}
+
+inline void JSNumberFormat::set_minimum_fraction_digits(int digits) {
+ DCHECK_GE(MinimumFractionDigitsBits::kMax, digits);
+ DCHECK_LE(0, digits);
+ DCHECK_GE(20, digits);
int hints = flags();
- hints = StyleBits::update(hints, style);
+ hints = MinimumFractionDigitsBits::update(hints, digits);
set_flags(hints);
}
-inline JSNumberFormat::Style JSNumberFormat::style() const {
- return StyleBits::decode(flags());
+inline int JSNumberFormat::maximum_fraction_digits() const {
+ return MaximumFractionDigitsBits::decode(flags());
}
-inline void JSNumberFormat::set_currency_display(
- CurrencyDisplay currency_display) {
- DCHECK_LT(currency_display, CurrencyDisplay::COUNT);
+inline void JSNumberFormat::set_maximum_fraction_digits(int digits) {
+ DCHECK_GE(MaximumFractionDigitsBits::kMax, digits);
+ DCHECK_LE(0, digits);
+ DCHECK_GE(20, digits);
int hints = flags();
- hints = CurrencyDisplayBits::update(hints, currency_display);
+ hints = MaximumFractionDigitsBits::update(hints, digits);
set_flags(hints);
}
-inline JSNumberFormat::CurrencyDisplay JSNumberFormat::currency_display()
- const {
- return CurrencyDisplayBits::decode(flags());
-}
-
CAST_ACCESSOR(JSNumberFormat)
} // namespace internal
diff --git a/src/objects/js-number-format.cc b/src/objects/js-number-format.cc
index 6bac26b..22fe97a 100644
--- a/src/objects/js-number-format.cc
+++ b/src/objects/js-number-format.cc
@@ -15,27 +15,106 @@
#include "src/objects-inl.h"
#include "src/objects/intl-objects.h"
#include "src/objects/js-number-format-inl.h"
+#include "unicode/currunit.h"
#include "unicode/decimfmt.h"
#include "unicode/locid.h"
+#include "unicode/nounit.h"
+#include "unicode/numberformatter.h"
#include "unicode/numfmt.h"
+#include "unicode/ucurr.h"
#include "unicode/uloc.h"
+#include "unicode/unumberformatter.h"
+#include "unicode/uvernum.h" // for U_ICU_VERSION_MAJOR_NUM
namespace v8 {
namespace internal {
namespace {
-UNumberFormatStyle ToNumberFormatStyle(
- JSNumberFormat::CurrencyDisplay currency_display) {
+// [[Style]] is one of the values "decimal", "percent", "currency",
+// or "unit" identifying the style of the number format.
+// Note: "unit" is added in proposal-unified-intl-numberformat
+enum class Style {
+ DECIMAL,
+ PERCENT,
+ CURRENCY,
+ UNIT,
+};
+
+// [[CurrencyDisplay]] is one of the values "code", "symbol", "name",
+// or "narrow-symbol" identifying the display of the currency number format.
+// Note: "narrow-symbol" is added in proposal-unified-intl-numberformat
+enum class CurrencyDisplay {
+ CODE,
+ SYMBOL,
+ NAME,
+ NARROW_SYMBOL,
+};
+
+// [[CurrencySign]] is one of the String values "standard" or "accounting",
+// specifying whether to render negative numbers in accounting format, often
+// signified by parenthesis. It is only used when [[Style]] has the value
+// "currency" and when [[SignDisplay]] is not "never".
+enum class CurrencySign {
+ STANDARD,
+ ACCOUNTING,
+};
+
+// [[UnitDisplay]] is one of the String values "short", "narrow", or "long",
+// specifying whether to display the unit as a symbol, narrow symbol, or
+// localized long name if formatting with the "unit" or "percent" style. It is
+// only used when [[Style]] has the value "unit" or "percent".
+enum class UnitDisplay {
+ SHORT,
+ NARROW,
+ LONG,
+};
+
+// [[Notation]] is one of the String values "standard", "scientific",
+// "engineering", or "compact", specifying whether the number should be
+// displayed without scaling, scaled to the units place with the power of ten
+// in scientific notation, scaled to the nearest thousand with the power of
+// ten in scientific notation, or scaled to the nearest locale-dependent
+// compact decimal notation power of ten with the corresponding compact
+// decimal notation affix.
+
+enum class Notation {
+ STANDARD,
+ SCIENTIFIC,
+ ENGINEERING,
+ COMPACT,
+};
+
+// [[CompactDisplay]] is one of the String values "short" or "long",
+// specifying whether to display compact notation affixes in short form ("5K")
+// or long form ("5 thousand") if formatting with the "compact" notation. It
+// is only used when [[Notation]] has the value "compact".
+enum class CompactDisplay {
+ SHORT,
+ LONG,
+};
+
+// [[SignDisplay]] is one of the String values "auto", "always", "never", or
+// "except-zero", specifying whether to show the sign on negative numbers
+// only, positive and negative numbers including zero, neither positive nor
+// negative numbers, or positive and negative numbers but not zero.
+enum class SignDisplay {
+ AUTO,
+ ALWAYS,
+ NEVER,
+ EXCEPT_ZERO,
+};
+
+UNumberUnitWidth ToUNumberUnitWidth(CurrencyDisplay currency_display) {
switch (currency_display) {
- case JSNumberFormat::CurrencyDisplay::SYMBOL:
- return UNUM_CURRENCY;
- case JSNumberFormat::CurrencyDisplay::CODE:
- return UNUM_CURRENCY_ISO;
- case JSNumberFormat::CurrencyDisplay::NAME:
- return UNUM_CURRENCY_PLURAL;
- case JSNumberFormat::CurrencyDisplay::COUNT:
- UNREACHABLE();
+ case CurrencyDisplay::SYMBOL:
+ return UNumberUnitWidth::UNUM_UNIT_WIDTH_SHORT;
+ case CurrencyDisplay::CODE:
+ return UNumberUnitWidth::UNUM_UNIT_WIDTH_ISO_CODE;
+ case CurrencyDisplay::NAME:
+ return UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME;
+ case CurrencyDisplay::NARROW_SYMBOL:
+ return UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW;
}
}
@@ -69,23 +148,162 @@
return (IsAToZ(currency[0]) && IsAToZ(currency[1]) && IsAToZ(currency[2]));
}
+// Parse the 'style' from the skeleton.
+Handle<String> StyleString(Isolate* isolate,
+ const icu::UnicodeString& skeleton) {
+ // Ex: skeleton as
+ // "percent precision-integer rounding-mode-half-up scale/100"
+ if (skeleton.indexOf("percent") >= 0) {
+ return ReadOnlyRoots(isolate).percent_string_handle();
+ }
+ // Ex: skeleton as "currency/TWD .00 rounding-mode-half-up"
+ if (skeleton.indexOf("currency") >= 0) {
+ return ReadOnlyRoots(isolate).currency_string_handle();
+ }
+ // Ex: skeleton as
+ // "measure-unit/length-meter .### rounding-mode-half-up unit-width-narrow"
+ if (skeleton.indexOf("measure-unit") >= 0) {
+ return ReadOnlyRoots(isolate).unit_string_handle();
+ }
+ // Ex: skeleton as ".### rounding-mode-half-up"
+ return ReadOnlyRoots(isolate).decimal_string_handle();
+}
+
+// Parse the 'currencyDisplay' from the skeleton.
+Handle<String> CurrencyDisplayString(Isolate* isolate,
+ const icu::UnicodeString& skeleton) {
+ // Ex: skeleton as
+ // "currency/TWD .00 rounding-mode-half-up unit-width-iso-code"
+ if (skeleton.indexOf("unit-width-iso-code") >= 0) {
+ return ReadOnlyRoots(isolate).code_string_handle();
+ }
+ // Ex: skeleton as
+ // "currency/TWD .00 rounding-mode-half-up unit-width-full-name;
+ if (skeleton.indexOf("unit-width-full-name") >= 0) {
+ return ReadOnlyRoots(isolate).name_string_handle();
+ }
+ // Ex: skeleton as
+ // "currency/TWD .00 rounding-mode-half-up unit-width-narrow;
+ if (skeleton.indexOf("unit-width-narrow") >= 0) {
+ return ReadOnlyRoots(isolate).narrow_symbol_string_handle();
+ }
+ // Ex: skeleton as "currency/TWD .00 rounding-mode-half-up"
+ return ReadOnlyRoots(isolate).symbol_string_handle();
+}
+
+// Return true if there are no "group-off" in the skeleton.
+bool UseGroupingFromSkeleton(const icu::UnicodeString& skeleton) {
+ return skeleton.indexOf("group-off") == -1;
+}
+
+// Parse currency code from skeleton. For example, skeleton as
+// "currency/TWD .00 rounding-mode-half-up unit-width-full-name;
+std::string CurrencyFromSkeleton(const icu::UnicodeString& skeleton) {
+ std::string str;
+ str = skeleton.toUTF8String<std::string>(str);
+ std::string search("currency/");
+ size_t index = str.find(search);
+ if (index == str.npos) return "";
+ return str.substr(index + search.size(), 3);
+}
+
+// Return the minimum integer digits by counting the number of '0' after
+// "integer-width/+" in the skeleton.
+// Ex: Return 15 for skeleton as
+// “currency/TWD .00 rounding-mode-half-up integer-width/+000000000000000”
+// 1
+// 123456789012345
+// Return default value as 1 if there are no "integer-width/+".
+int32_t MinimumIntegerDigitsFromSkeleton(const icu::UnicodeString& skeleton) {
+ // count the number of 0 after "integer-width/+"
+ icu::UnicodeString search("integer-width/+");
+ int32_t index = skeleton.indexOf(search);
+ if (index < 0) return 1; // return 1 if cannot find it.
+ index += search.length();
+ int32_t matched = 0;
+ while (index < skeleton.length() && skeleton[index] == '0') {
+ matched++;
+ index++;
+ }
+ CHECK_GT(matched, 0);
+ return matched;
+}
+
+// Return true if there are fraction digits, false if not.
+// The minimum fraction digits is the number of '0' after '.' in the skeleton
+// The maximum fraction digits is the number of '#' after the above '0's plus
+// the minimum fraction digits.
+// For example, as skeleton “.000#### rounding-mode-half-up”
+// 123
+// 4567
+// Set The minimum as 3 and maximum as 7.
+bool FractionDigitsFromSkeleton(const icu::UnicodeString& skeleton,
+ int32_t* minimum, int32_t* maximum) {
+ icu::UnicodeString search(".");
+ int32_t index = skeleton.indexOf(search);
+ if (index < 0) return false;
+ *minimum = 0;
+ index++; // skip the '.'
+ while (index < skeleton.length() && skeleton[index] == '0') {
+ (*minimum)++;
+ index++;
+ }
+ *maximum = *minimum;
+ while (index < skeleton.length() && skeleton[index] == '#') {
+ (*maximum)++;
+ index++;
+ }
+ return true;
+}
+
+// Return true if there are significant digits, false if not.
+// The minimum significant digits is the number of '@' in the skeleton
+// The maximum significant digits is the number of '#' after these '@'s plus
+// the minimum significant digits.
+// Ex: Skeleton as "@@@@@####### rounding-mode-half-up"
+// 12345
+// 6789012
+// Set The minimum as 5 and maximum as 12.
+bool SignificantDigitsFromSkeleton(const icu::UnicodeString& skeleton,
+ int32_t* minimum, int32_t* maximum) {
+ icu::UnicodeString search("@");
+ int32_t index = skeleton.indexOf(search);
+ if (index < 0) return false;
+ *minimum = 1;
+ index++; // skip the first '@'
+ while (index < skeleton.length() && skeleton[index] == '@') {
+ (*minimum)++;
+ index++;
+ }
+ *maximum = *minimum;
+ while (index < skeleton.length() && skeleton[index] == '#') {
+ (*maximum)++;
+ index++;
+ }
+ return true;
+}
+
} // anonymous namespace
// static
// ecma402 #sec-intl.numberformat.prototype.resolvedoptions
Handle<JSObject> JSNumberFormat::ResolvedOptions(
- Isolate* isolate, Handle<JSNumberFormat> number_format_holder) {
+ Isolate* isolate, Handle<JSNumberFormat> number_format) {
Factory* factory = isolate->factory();
+ UErrorCode status = U_ZERO_ERROR;
+ icu::number::LocalizedNumberFormatter* icu_number_formatter =
+ number_format->icu_number_formatter()->raw();
+ icu::UnicodeString skeleton = icu_number_formatter->toSkeleton(status);
+ CHECK(U_SUCCESS(status));
+
+ std::string s_str;
+ s_str = skeleton.toUTF8String<std::string>(s_str);
+
// 4. Let options be ! ObjectCreate(%ObjectPrototype%).
Handle<JSObject> options = factory->NewJSObject(isolate->object_function());
- icu::NumberFormat* number_format =
- number_format_holder->icu_number_format()->raw();
- CHECK_NOT_NULL(number_format);
-
- Handle<String> locale =
- Handle<String>(number_format_holder->locale(), isolate);
+ Handle<String> locale = Handle<String>(number_format->locale(), isolate);
std::unique_ptr<char[]> locale_str = locale->ToCString();
icu::Locale icu_locale = Intl::CreateICULocale(locale_str.get());
@@ -119,65 +337,67 @@
}
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->style_string(),
- number_format_holder->StyleAsString(), Just(kDontThrow))
+ StyleString(isolate, skeleton), Just(kDontThrow))
.FromJust());
- if (number_format_holder->style() == Style::CURRENCY) {
- icu::UnicodeString currency(number_format->getCurrency());
- DCHECK(!currency.isEmpty());
+ std::string currency = CurrencyFromSkeleton(skeleton);
+ if (!currency.empty()) {
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->currency_string(),
- factory
- ->NewStringFromTwoByte(Vector<const uint16_t>(
- reinterpret_cast<const uint16_t*>(currency.getBuffer()),
- currency.length()))
- .ToHandleChecked(),
+ factory->NewStringFromAsciiChecked(currency.c_str()),
Just(kDontThrow))
.FromJust());
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->currencyDisplay_string(),
- number_format_holder->CurrencyDisplayAsString(), Just(kDontThrow))
+ CurrencyDisplayString(isolate, skeleton), Just(kDontThrow))
.FromJust());
}
+ CHECK(
+ JSReceiver::CreateDataProperty(
+ isolate, options, factory->minimumIntegerDigits_string(),
+ factory->NewNumberFromInt(MinimumIntegerDigitsFromSkeleton(skeleton)),
+ Just(kDontThrow))
+ .FromJust());
+ int32_t minimum = 0, maximum = 0;
+ if (!FractionDigitsFromSkeleton(skeleton, &minimum, &maximum)) {
+ // Currenct ECMA 402 spec mandate to record (Min|Max)imumFractionDigits
+ // uncondictionally while the unified number proposal eventually will only
+ // record either (Min|Max)imumFractionDigits or
+ // (Min|Max)imumSignaficantDigits Since LocalizedNumberFormatter can only
+ // remember one set, and during 2019-1-17 ECMA402 meeting that the committee
+ // decide not to take a PR to address that prior to the unified number
+ // proposal, we have to add these two 5 bits int into flags to remember the
+ // (Min|Max)imumFractionDigits while (Min|Max)imumSignaficantDigits is
+ // present.
+ // TODO(ftang) remove the following two lines once we ship
+ // int-number-format-unified
+ minimum = number_format->minimum_fraction_digits();
+ maximum = number_format->maximum_fraction_digits();
+ }
CHECK(JSReceiver::CreateDataProperty(
- isolate, options, factory->minimumIntegerDigits_string(),
- factory->NewNumberFromInt(number_format->getMinimumIntegerDigits()),
- Just(kDontThrow))
+ isolate, options, factory->minimumFractionDigits_string(),
+ factory->NewNumberFromInt(minimum), Just(kDontThrow))
.FromJust());
- CHECK(
- JSReceiver::CreateDataProperty(
- isolate, options, factory->minimumFractionDigits_string(),
- factory->NewNumberFromInt(number_format->getMinimumFractionDigits()),
- Just(kDontThrow))
- .FromJust());
- CHECK(
- JSReceiver::CreateDataProperty(
- isolate, options, factory->maximumFractionDigits_string(),
- factory->NewNumberFromInt(number_format->getMaximumFractionDigits()),
- Just(kDontThrow))
- .FromJust());
- CHECK(number_format->getDynamicClassID() ==
- icu::DecimalFormat::getStaticClassID());
- icu::DecimalFormat* decimal_format =
- static_cast<icu::DecimalFormat*>(number_format);
- CHECK_NOT_NULL(decimal_format);
- if (decimal_format->areSignificantDigitsUsed()) {
+ CHECK(JSReceiver::CreateDataProperty(
+ isolate, options, factory->maximumFractionDigits_string(),
+ factory->NewNumberFromInt(maximum), Just(kDontThrow))
+ .FromJust());
+ minimum = 0;
+ maximum = 0;
+ if (SignificantDigitsFromSkeleton(skeleton, &minimum, &maximum)) {
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->minimumSignificantDigits_string(),
- factory->NewNumberFromInt(
- decimal_format->getMinimumSignificantDigits()),
- Just(kDontThrow))
+ factory->NewNumberFromInt(minimum), Just(kDontThrow))
.FromJust());
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->maximumSignificantDigits_string(),
- factory->NewNumberFromInt(
- decimal_format->getMaximumSignificantDigits()),
- Just(kDontThrow))
+ factory->NewNumberFromInt(maximum), Just(kDontThrow))
.FromJust());
}
+
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->useGrouping_string(),
- factory->ToBoolean((number_format->isGroupingUsed() == TRUE)),
+ factory->ToBoolean(UseGroupingFromSkeleton(skeleton)),
Just(kDontThrow))
.FromJust());
return options;
@@ -216,8 +436,8 @@
MaybeHandle<JSNumberFormat> JSNumberFormat::Initialize(
Isolate* isolate, Handle<JSNumberFormat> number_format,
Handle<Object> locales, Handle<Object> options_obj) {
- // set the flags to 0 ASAP.
number_format->set_flags(0);
+ // set the flags to 0 ASAP.
Factory* factory = isolate->factory();
// 1. Let requestedLocales be ? CanonicalizeLocaleList(locales).
@@ -299,7 +519,6 @@
Style style = maybe_style.FromJust();
// 13. Set numberFormat.[[Style]] to style.
- number_format->set_style(style);
// 14. Let currency be ? GetOption(options, "currency", "string", undefined,
// undefined).
@@ -355,75 +574,35 @@
CurrencyDisplay::SYMBOL);
MAYBE_RETURN(maybe_currencyDisplay, MaybeHandle<JSNumberFormat>());
CurrencyDisplay currency_display = maybe_currencyDisplay.FromJust();
- UNumberFormatStyle format_style = ToNumberFormatStyle(currency_display);
- std::unique_ptr<icu::NumberFormat> icu_number_format;
- icu::Locale no_extension_locale(r.icu_locale.getBaseName());
- if (style == Style::DECIMAL) {
- icu_number_format.reset(
- icu::NumberFormat::createInstance(r.icu_locale, status));
- // If the subclass is not DecimalFormat, fallback to no extension
- // because other subclass has not support the format() with
- // FieldPositionIterator yet.
- if (U_FAILURE(status) || icu_number_format.get() == nullptr ||
- icu_number_format->getDynamicClassID() !=
- icu::DecimalFormat::getStaticClassID()) {
- status = U_ZERO_ERROR;
- icu_number_format.reset(
- icu::NumberFormat::createInstance(no_extension_locale, status));
- }
- } else if (style == Style::PERCENT) {
- icu_number_format.reset(
- icu::NumberFormat::createPercentInstance(r.icu_locale, status));
- // If the subclass is not DecimalFormat, fallback to no extension
- // because other subclass has not support the format() with
- // FieldPositionIterator yet.
- if (U_FAILURE(status) || icu_number_format.get() == nullptr ||
- icu_number_format->getDynamicClassID() !=
- icu::DecimalFormat::getStaticClassID()) {
- status = U_ZERO_ERROR;
- icu_number_format.reset(icu::NumberFormat::createPercentInstance(
- no_extension_locale, status));
- }
- } else {
- DCHECK_EQ(style, Style::CURRENCY);
- icu_number_format.reset(
- icu::NumberFormat::createInstance(r.icu_locale, format_style, status));
- // If the subclass is not DecimalFormat, fallback to no extension
- // because other subclass has not support the format() with
- // FieldPositionIterator yet.
- if (U_FAILURE(status) || icu_number_format.get() == nullptr ||
- icu_number_format->getDynamicClassID() !=
- icu::DecimalFormat::getStaticClassID()) {
- status = U_ZERO_ERROR;
- icu_number_format.reset(icu::NumberFormat::createInstance(
- no_extension_locale, format_style, status));
- }
+ icu::number::LocalizedNumberFormatter icu_number_formatter =
+ icu::number::NumberFormatter::withLocale(r.icu_locale)
+ .roundingMode(UNUM_ROUND_HALFUP);
+ if (style == Style::PERCENT) {
+ icu_number_formatter = icu_number_formatter.unit(icu::NoUnit::percent())
+ .scale(icu::number::Scale::powerOfTen(2));
}
- if (U_FAILURE(status) || icu_number_format.get() == nullptr) {
- status = U_ZERO_ERROR;
- // Remove extensions and try again.
- icu_number_format.reset(
- icu::NumberFormat::createInstance(no_extension_locale, status));
-
- if (U_FAILURE(status) || icu_number_format.get() == nullptr) {
- FATAL("Failed to create ICU number_format, are ICU data files missing?");
- }
- }
- DCHECK(U_SUCCESS(status));
- CHECK_NOT_NULL(icu_number_format.get());
- CHECK(icu_number_format->getDynamicClassID() ==
- icu::DecimalFormat::getStaticClassID());
if (style == Style::CURRENCY) {
// 19. If style is "currency", set numberFormat.[[CurrencyDisplay]] to
// currencyDisplay.
- number_format->set_currency_display(currency_display);
// 17.b. Set numberFormat.[[Currency]] to currency.
if (!currency_ustr.isEmpty()) {
- status = U_ZERO_ERROR;
- icu_number_format->setCurrency(currency_ustr.getBuffer(), status);
+ Handle<String> currency_string;
+ ASSIGN_RETURN_ON_EXCEPTION(isolate, currency_string,
+ Intl::ToString(isolate, currency_ustr),
+ JSNumberFormat);
+
+ icu_number_formatter = icu_number_formatter.unit(
+ icu::CurrencyUnit(currency_ustr.getBuffer(), status));
+ CHECK(U_SUCCESS(status));
+ // The default unitWidth is SHORT in ICU and that mapped from
+ // Symbol so we can skip the setting for optimization.
+ if (currency_display != CurrencyDisplay::SYMBOL) {
+ icu_number_formatter = icu_number_formatter.unitWidth(
+ ToUNumberUnitWidth(currency_display));
+ }
CHECK(U_SUCCESS(status));
}
}
@@ -451,14 +630,45 @@
}
// 22. Perform ? SetNumberFormatDigitOptions(numberFormat, options,
// mnfdDefault, mxfdDefault).
- CHECK(icu_number_format->getDynamicClassID() ==
- icu::DecimalFormat::getStaticClassID());
- icu::DecimalFormat* icu_decimal_format =
- static_cast<icu::DecimalFormat*>(icu_number_format.get());
- Maybe<bool> maybe_set_number_for_digit_options =
- Intl::SetNumberFormatDigitOptions(isolate, icu_decimal_format, options,
- mnfd_default, mxfd_default);
- MAYBE_RETURN(maybe_set_number_for_digit_options, Handle<JSNumberFormat>());
+ Maybe<Intl::NumberFormatDigitOptions> maybe_digit_options =
+ Intl::SetNumberFormatDigitOptions(isolate, options, mnfd_default,
+ mxfd_default);
+ MAYBE_RETURN(maybe_digit_options, Handle<JSNumberFormat>());
+ Intl::NumberFormatDigitOptions digit_options = maybe_digit_options.FromJust();
+
+ icu::number::Precision precision =
+ (digit_options.minimum_significant_digits > 0)
+ ? icu::number::Precision::minMaxSignificantDigits(
+ digit_options.minimum_significant_digits,
+ digit_options.maximum_significant_digits)
+ : icu::number::Precision::minMaxFraction(
+ digit_options.minimum_fraction_digits,
+ digit_options.maximum_fraction_digits);
+
+ if (digit_options.minimum_significant_digits > 0) {
+ // Currenct ECMA 402 spec mandate to record (Min|Max)imumFractionDigits
+ // uncondictionally while the unified number proposal eventually will only
+ // record either (Min|Max)imumFractionDigits or
+ // (Min|Max)imumSignaficantDigits Since LocalizedNumberFormatter can only
+ // remember one set, and during 2019-1-17 ECMA402 meeting that the committee
+ // decide not to take a PR to address that prior to the unified number
+ // proposal, we have to add these two 5 bits int into flags to remember the
+ // (Min|Max)imumFractionDigits while (Min|Max)imumSignaficantDigits is
+ // present.
+ // TODO(ftang) remove the following two lines once we ship
+ // int-number-format-unified
+ number_format->set_minimum_fraction_digits(
+ digit_options.minimum_fraction_digits);
+ number_format->set_maximum_fraction_digits(
+ digit_options.maximum_fraction_digits);
+ }
+
+ icu_number_formatter = icu_number_formatter.precision(precision);
+ if (digit_options.minimum_integer_digits > 1) {
+ icu_number_formatter =
+ icu_number_formatter.integerWidth(icu::number::IntegerWidth::zeroFillTo(
+ digit_options.minimum_integer_digits));
+ }
// 23. Let useGrouping be ? GetOption(options, "useGrouping", "boolean",
// undefined, true).
@@ -467,7 +677,10 @@
isolate, options, "useGrouping", service, &use_grouping);
MAYBE_RETURN(found_use_grouping, MaybeHandle<JSNumberFormat>());
// 24. Set numberFormat.[[UseGrouping]] to useGrouping.
- icu_number_format->setGroupingUsed(use_grouping ? TRUE : FALSE);
+ if (!use_grouping) {
+ icu_number_formatter = icu_number_formatter.grouping(
+ UNumberGroupingStrategy::UNUM_GROUPING_OFF);
+ }
// 25. Let dataLocaleData be localeData.[[<dataLocale>]].
//
@@ -482,63 +695,41 @@
//
// 30. Set numberFormat.[[NegativePattern]] to
// stylePatterns.[[negativePattern]].
-
- Handle<Managed<icu::NumberFormat>> managed_number_format =
- Managed<icu::NumberFormat>::FromUniquePtr(isolate, 0,
- std::move(icu_number_format));
- number_format->set_icu_number_format(*managed_number_format);
+ //
+ Handle<Managed<icu::number::LocalizedNumberFormatter>>
+ managed_number_formatter =
+ Managed<icu::number::LocalizedNumberFormatter>::FromRawPtr(
+ isolate, 0,
+ new icu::number::LocalizedNumberFormatter(icu_number_formatter));
+ number_format->set_icu_number_formatter(*managed_number_formatter);
number_format->set_bound_format(*factory->undefined_value());
// 31. Return numberFormat.
return number_format;
}
-Handle<String> JSNumberFormat::StyleAsString() const {
- switch (style()) {
- case Style::DECIMAL:
- return GetReadOnlyRoots().decimal_string_handle();
- case Style::PERCENT:
- return GetReadOnlyRoots().percent_string_handle();
- case Style::CURRENCY:
- return GetReadOnlyRoots().currency_string_handle();
- case Style::COUNT:
- UNREACHABLE();
- }
-}
-
-Handle<String> JSNumberFormat::CurrencyDisplayAsString() const {
- switch (currency_display()) {
- case CurrencyDisplay::CODE:
- return GetReadOnlyRoots().code_string_handle();
- case CurrencyDisplay::SYMBOL:
- return GetReadOnlyRoots().symbol_string_handle();
- case CurrencyDisplay::NAME:
- return GetReadOnlyRoots().name_string_handle();
- case CurrencyDisplay::COUNT:
- UNREACHABLE();
- }
-}
-
namespace {
Maybe<icu::UnicodeString> IcuFormatNumber(
- Isolate* isolate, const icu::NumberFormat& number_format,
+ Isolate* isolate,
+ const icu::number::LocalizedNumberFormatter& number_format,
Handle<Object> numeric_obj, icu::FieldPositionIterator* fp_iter) {
- icu::UnicodeString result;
// If it is BigInt, handle it differently.
UErrorCode status = U_ZERO_ERROR;
+ icu::number::FormattedNumber formatted;
if (numeric_obj->IsBigInt()) {
Handle<BigInt> big_int = Handle<BigInt>::cast(numeric_obj);
Handle<String> big_int_string;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, big_int_string,
BigInt::ToString(isolate, big_int),
Nothing<icu::UnicodeString>());
- number_format.format(
- {big_int_string->ToCString().get(), big_int_string->length()}, result,
- fp_iter, status);
+ formatted = number_format.formatDecimal(
+ {big_int_string->ToCString().get(), big_int_string->length()}, status);
} else {
double number = numeric_obj->Number();
- number_format.format(number, result, fp_iter, status);
+ formatted = number_format.formatDouble(number, status);
}
+ formatted.getAllFieldPositions(*fp_iter, status);
+ icu::UnicodeString result = formatted.toString(status);
if (U_FAILURE(status)) {
THROW_NEW_ERROR_RETURN_VALUE(isolate,
NewTypeError(MessageTemplate::kIcuError),
@@ -550,17 +741,15 @@
} // namespace
MaybeHandle<String> JSNumberFormat::FormatNumeric(
- Isolate* isolate, const icu::NumberFormat& number_format,
+ Isolate* isolate,
+ const icu::number::LocalizedNumberFormatter& number_format,
Handle<Object> numeric_obj) {
DCHECK(numeric_obj->IsNumeric());
Maybe<icu::UnicodeString> maybe_format =
IcuFormatNumber(isolate, number_format, numeric_obj, nullptr);
MAYBE_RETURN(maybe_format, Handle<String>());
- icu::UnicodeString result = maybe_format.FromJust();
-
- return isolate->factory()->NewStringFromTwoByte(Vector<const uint16_t>(
- reinterpret_cast<const uint16_t*>(result.getBuffer()), result.length()));
+ return Intl::ToString(isolate, maybe_format.FromJust());
}
namespace {
@@ -672,19 +861,12 @@
return out_parts;
}
-Maybe<int> JSNumberFormat::FormatToParts(Isolate* isolate,
- Handle<JSArray> result,
- int start_index,
- const icu::NumberFormat& number_format,
- Handle<Object> numeric_obj,
- Handle<String> unit) {
+namespace {
+Maybe<int> ConstructParts(Isolate* isolate, const icu::UnicodeString& formatted,
+ icu::FieldPositionIterator* fp_iter,
+ Handle<JSArray> result, int start_index,
+ Handle<Object> numeric_obj, Handle<String> unit) {
DCHECK(numeric_obj->IsNumeric());
- icu::FieldPositionIterator fp_iter;
- Maybe<icu::UnicodeString> maybe_format =
- IcuFormatNumber(isolate, number_format, numeric_obj, &fp_iter);
- MAYBE_RETURN(maybe_format, Nothing<int>());
- icu::UnicodeString formatted = maybe_format.FromJust();
-
int32_t length = formatted.length();
int index = start_index;
if (length == 0) return Just(index);
@@ -698,7 +880,7 @@
{
icu::FieldPosition fp;
- while (fp_iter.next(fp)) {
+ while (fp_iter->next(fp)) {
regions.push_back(NumberFormatSpan(fp.getField(), fp.getBeginIndex(),
fp.getEndIndex()));
}
@@ -729,18 +911,26 @@
return Just(index);
}
+} // namespace
+
MaybeHandle<JSArray> JSNumberFormat::FormatToParts(
Isolate* isolate, Handle<JSNumberFormat> number_format,
Handle<Object> numeric_obj) {
CHECK(numeric_obj->IsNumeric());
Factory* factory = isolate->factory();
- icu::NumberFormat* fmt = number_format->icu_number_format()->raw();
+ icu::number::LocalizedNumberFormatter* fmt =
+ number_format->icu_number_formatter()->raw();
CHECK_NOT_NULL(fmt);
- Handle<JSArray> result = factory->NewJSArray(0);
+ icu::FieldPositionIterator fp_iter;
+ Maybe<icu::UnicodeString> maybe_format =
+ IcuFormatNumber(isolate, *fmt, numeric_obj, &fp_iter);
+ MAYBE_RETURN(maybe_format, Handle<JSArray>());
- Maybe<int> maybe_format_to_parts = JSNumberFormat::FormatToParts(
- isolate, result, 0, *fmt, numeric_obj, Handle<String>());
+ Handle<JSArray> result = factory->NewJSArray(0);
+ Maybe<int> maybe_format_to_parts =
+ ConstructParts(isolate, maybe_format.FromJust(), &fp_iter, result, 0,
+ numeric_obj, Handle<String>());
MAYBE_RETURN(maybe_format_to_parts, Handle<JSArray>());
return result;
diff --git a/src/objects/js-number-format.h b/src/objects/js-number-format.h
index 51cc401..a7940e0 100644
--- a/src/objects/js-number-format.h
+++ b/src/objects/js-number-format.h
@@ -17,12 +17,14 @@
#include "src/objects.h"
#include "src/objects/intl-objects.h"
#include "src/objects/managed.h"
+#include "unicode/numberformatter.h"
// Has to be the last include (doesn't have include guards):
#include "src/objects/object-macros.h"
namespace U_ICU_NAMESPACE {
class NumberFormat;
+class UnicodeString;
} // namespace U_ICU_NAMESPACE
namespace v8 {
@@ -47,78 +49,55 @@
Isolate* isolate, Handle<JSNumberFormat> number_format,
Handle<Object> numeric_obj);
- // A utility function used by the above JSNumberFormat::FormatToParts()
- // and JSRelativeTimeFormat::FormatToParts().
- // Format the number by using the icu::NumberFormat to get the field
- // information. It add an object into the result array, starting from the
- // start_index and return the total number of elements in the result array.
- // For each object added as element, it set the substring of the field as
- // "value", the field type as "type". If the unit is not null, it also set
- // unit as "unit" to each added object.
- V8_WARN_UNUSED_RESULT static Maybe<int> FormatToParts(
- Isolate* isolate, Handle<JSArray> result, int start_index,
- const icu::NumberFormat& fmt, Handle<Object> numeric_obj,
- Handle<String> unit);
-
V8_WARN_UNUSED_RESULT static MaybeHandle<String> FormatNumeric(
- Isolate* isolate, const icu::NumberFormat& number_format,
+ Isolate* isolate,
+ const icu::number::LocalizedNumberFormatter& number_format,
Handle<Object> numeric_obj);
V8_EXPORT_PRIVATE static const std::set<std::string>& GetAvailableLocales();
- Handle<String> StyleAsString() const;
- Handle<String> CurrencyDisplayAsString() const;
-
DECL_CAST(JSNumberFormat)
DECL_PRINTER(JSNumberFormat)
DECL_VERIFIER(JSNumberFormat)
- // [[Style]] is one of the values "decimal", "percent" or "currency",
- // identifying the style of the number format.
- enum class Style {
- DECIMAL,
- PERCENT,
- CURRENCY,
+ // Current ECMA 402 spec mandates to record (Min|Max)imumFractionDigits
+ // unconditionally while the unified number proposal eventually will only
+ // record either (Min|Max)imumFractionDigits or (Min|Max)imumSignaficantDigits
+ // Since LocalizedNumberFormatter can only remember one set, and during
+ // 2019-1-17 ECMA402 meeting that the committee decide not to take a PR to
+ // address that prior to the unified number proposal, we have to add these two
+ // 5 bits int into flags to remember the (Min|Max)imumFractionDigits while
+ // (Min|Max)imumSignaficantDigits is present.
+ // TODO(ftang) remove the following once we ship int-number-format-unified
+ // * Four inline functions: (set_)?(min|max)imum_fraction_digits
+ // * kFlagsOffset
+ // * #define FLAGS_BIT_FIELDS
+ // * DECL_INT_ACCESSORS(flags)
- COUNT
- };
- inline void set_style(Style style);
- inline Style style() const;
+ inline int minimum_fraction_digits() const;
+ inline void set_minimum_fraction_digits(int digits);
- // [[CurrencyDisplay]] is one of the values "code", "symbol" or "name",
- // identifying the display of the currency number format.
- enum class CurrencyDisplay {
- CODE,
- SYMBOL,
- NAME,
+ inline int maximum_fraction_digits() const;
+ inline void set_maximum_fraction_digits(int digits);
- COUNT
- };
- inline void set_currency_display(CurrencyDisplay currency_display);
- inline CurrencyDisplay currency_display() const;
-
-// Layout description.
+ // Layout description.
DEFINE_FIELD_OFFSET_CONSTANTS(JSObject::kHeaderSize,
TORQUE_GENERATED_JSNUMBER_FORMAT_FIELDS)
// Bit positions in |flags|.
-#define FLAGS_BIT_FIELDS(V, _) \
- V(StyleBits, Style, 2, _) \
- V(CurrencyDisplayBits, CurrencyDisplay, 2, _)
+#define FLAGS_BIT_FIELDS(V, _) \
+ V(MinimumFractionDigitsBits, int, 5, _) \
+ V(MaximumFractionDigitsBits, int, 5, _)
DEFINE_BIT_FIELDS(FLAGS_BIT_FIELDS)
#undef FLAGS_BIT_FIELDS
- STATIC_ASSERT(Style::DECIMAL <= StyleBits::kMax);
- STATIC_ASSERT(Style::PERCENT <= StyleBits::kMax);
- STATIC_ASSERT(Style::CURRENCY <= StyleBits::kMax);
-
- STATIC_ASSERT(CurrencyDisplay::CODE <= CurrencyDisplayBits::kMax);
- STATIC_ASSERT(CurrencyDisplay::SYMBOL <= CurrencyDisplayBits::kMax);
- STATIC_ASSERT(CurrencyDisplay::NAME <= CurrencyDisplayBits::kMax);
+ STATIC_ASSERT(20 <= MinimumFractionDigitsBits::kMax);
+ STATIC_ASSERT(20 <= MaximumFractionDigitsBits::kMax);
DECL_ACCESSORS(locale, String)
- DECL_ACCESSORS(icu_number_format, Managed<icu::NumberFormat>)
+ DECL_ACCESSORS(icu_number_formatter,
+ Managed<icu::number::LocalizedNumberFormatter>)
DECL_ACCESSORS(bound_format, Object)
DECL_INT_ACCESSORS(flags)
diff --git a/src/objects/js-plural-rules.cc b/src/objects/js-plural-rules.cc
index da349dc..bf4e3db 100644
--- a/src/objects/js-plural-rules.cc
+++ b/src/objects/js-plural-rules.cc
@@ -164,9 +164,24 @@
CHECK_NOT_NULL(icu_decimal_format.get());
// 9. Perform ? SetNumberFormatDigitOptions(pluralRules, options, 0, 3).
- Maybe<bool> done = Intl::SetNumberFormatDigitOptions(
- isolate, icu_decimal_format.get(), options, 0, 3);
- MAYBE_RETURN(done, MaybeHandle<JSPluralRules>());
+ Maybe<Intl::NumberFormatDigitOptions> maybe_digit_options =
+ Intl::SetNumberFormatDigitOptions(isolate, options, 0, 3);
+ MAYBE_RETURN(maybe_digit_options, MaybeHandle<JSPluralRules>());
+ Intl::NumberFormatDigitOptions digit_options = maybe_digit_options.FromJust();
+
+ icu_decimal_format->setRoundingMode(icu::DecimalFormat::kRoundHalfUp);
+ icu_decimal_format->setMinimumIntegerDigits(
+ digit_options.minimum_integer_digits);
+ icu_decimal_format->setMinimumFractionDigits(
+ digit_options.minimum_fraction_digits);
+ icu_decimal_format->setMaximumFractionDigits(
+ digit_options.maximum_fraction_digits);
+ if (digit_options.minimum_significant_digits > 0) {
+ icu_decimal_format->setMinimumSignificantDigits(
+ digit_options.minimum_significant_digits);
+ icu_decimal_format->setMaximumSignificantDigits(
+ digit_options.maximum_significant_digits);
+ }
Handle<Managed<icu::PluralRules>> managed_plural_rules =
Managed<icu::PluralRules>::FromUniquePtr(isolate, 0,
diff --git a/src/objects/js-plural-rules.h b/src/objects/js-plural-rules.h
index 0440bc1..8448662 100644
--- a/src/objects/js-plural-rules.h
+++ b/src/objects/js-plural-rules.h
@@ -22,6 +22,7 @@
#include "src/objects/object-macros.h"
namespace U_ICU_NAMESPACE {
+class DecimalFormat;
class PluralRules;
} // namespace U_ICU_NAMESPACE
diff --git a/tools/v8heapconst.py b/tools/v8heapconst.py
index b854009..3ce8b2d 100644
--- a/tools/v8heapconst.py
+++ b/tools/v8heapconst.py
@@ -306,49 +306,49 @@
("read_only_space", 0x014b9): (98, "EnumCacheMap"),
("read_only_space", 0x01509): (115, "ArrayBoilerplateDescriptionMap"),
("read_only_space", 0x016e1): (101, "InterceptorInfoMap"),
- ("read_only_space", 0x03445): (89, "AccessCheckInfoMap"),
- ("read_only_space", 0x0346d): (90, "AccessorInfoMap"),
- ("read_only_space", 0x03495): (91, "AccessorPairMap"),
- ("read_only_space", 0x034bd): (92, "AliasedArgumentsEntryMap"),
- ("read_only_space", 0x034e5): (93, "AllocationMementoMap"),
- ("read_only_space", 0x0350d): (94, "AsmWasmDataMap"),
- ("read_only_space", 0x03535): (95, "AsyncGeneratorRequestMap"),
- ("read_only_space", 0x0355d): (96, "ClassPositionsMap"),
- ("read_only_space", 0x03585): (97, "DebugInfoMap"),
- ("read_only_space", 0x035ad): (99, "FunctionTemplateInfoMap"),
- ("read_only_space", 0x035d5): (100, "FunctionTemplateRareDataMap"),
- ("read_only_space", 0x035fd): (102, "InterpreterDataMap"),
- ("read_only_space", 0x03625): (103, "ModuleInfoEntryMap"),
- ("read_only_space", 0x0364d): (104, "ModuleMap"),
- ("read_only_space", 0x03675): (105, "ObjectTemplateInfoMap"),
- ("read_only_space", 0x0369d): (106, "PromiseCapabilityMap"),
- ("read_only_space", 0x036c5): (107, "PromiseReactionMap"),
- ("read_only_space", 0x036ed): (108, "PrototypeInfoMap"),
- ("read_only_space", 0x03715): (109, "ScriptMap"),
- ("read_only_space", 0x0373d): (110, "SourcePositionTableWithFrameCacheMap"),
- ("read_only_space", 0x03765): (111, "StackFrameInfoMap"),
- ("read_only_space", 0x0378d): (112, "StackTraceFrameMap"),
- ("read_only_space", 0x037b5): (113, "Tuple2Map"),
- ("read_only_space", 0x037dd): (114, "Tuple3Map"),
- ("read_only_space", 0x03805): (116, "WasmCapiFunctionDataMap"),
- ("read_only_space", 0x0382d): (117, "WasmDebugInfoMap"),
- ("read_only_space", 0x03855): (118, "WasmExceptionTagMap"),
- ("read_only_space", 0x0387d): (119, "WasmExportedFunctionDataMap"),
- ("read_only_space", 0x038a5): (120, "CallableTaskMap"),
- ("read_only_space", 0x038cd): (121, "CallbackTaskMap"),
- ("read_only_space", 0x038f5): (122, "PromiseFulfillReactionJobTaskMap"),
- ("read_only_space", 0x0391d): (123, "PromiseRejectReactionJobTaskMap"),
- ("read_only_space", 0x03945): (124, "PromiseResolveThenableJobTaskMap"),
- ("read_only_space", 0x0396d): (125, "FinalizationGroupCleanupJobTaskMap"),
- ("read_only_space", 0x03995): (126, "AllocationSiteWithWeakNextMap"),
- ("read_only_space", 0x039bd): (126, "AllocationSiteWithoutWeakNextMap"),
- ("read_only_space", 0x039e5): (161, "LoadHandler1Map"),
- ("read_only_space", 0x03a0d): (161, "LoadHandler2Map"),
- ("read_only_space", 0x03a35): (161, "LoadHandler3Map"),
- ("read_only_space", 0x03a5d): (169, "StoreHandler0Map"),
- ("read_only_space", 0x03a85): (169, "StoreHandler1Map"),
- ("read_only_space", 0x03aad): (169, "StoreHandler2Map"),
- ("read_only_space", 0x03ad5): (169, "StoreHandler3Map"),
+ ("read_only_space", 0x03559): (89, "AccessCheckInfoMap"),
+ ("read_only_space", 0x03581): (90, "AccessorInfoMap"),
+ ("read_only_space", 0x035a9): (91, "AccessorPairMap"),
+ ("read_only_space", 0x035d1): (92, "AliasedArgumentsEntryMap"),
+ ("read_only_space", 0x035f9): (93, "AllocationMementoMap"),
+ ("read_only_space", 0x03621): (94, "AsmWasmDataMap"),
+ ("read_only_space", 0x03649): (95, "AsyncGeneratorRequestMap"),
+ ("read_only_space", 0x03671): (96, "ClassPositionsMap"),
+ ("read_only_space", 0x03699): (97, "DebugInfoMap"),
+ ("read_only_space", 0x036c1): (99, "FunctionTemplateInfoMap"),
+ ("read_only_space", 0x036e9): (100, "FunctionTemplateRareDataMap"),
+ ("read_only_space", 0x03711): (102, "InterpreterDataMap"),
+ ("read_only_space", 0x03739): (103, "ModuleInfoEntryMap"),
+ ("read_only_space", 0x03761): (104, "ModuleMap"),
+ ("read_only_space", 0x03789): (105, "ObjectTemplateInfoMap"),
+ ("read_only_space", 0x037b1): (106, "PromiseCapabilityMap"),
+ ("read_only_space", 0x037d9): (107, "PromiseReactionMap"),
+ ("read_only_space", 0x03801): (108, "PrototypeInfoMap"),
+ ("read_only_space", 0x03829): (109, "ScriptMap"),
+ ("read_only_space", 0x03851): (110, "SourcePositionTableWithFrameCacheMap"),
+ ("read_only_space", 0x03879): (111, "StackFrameInfoMap"),
+ ("read_only_space", 0x038a1): (112, "StackTraceFrameMap"),
+ ("read_only_space", 0x038c9): (113, "Tuple2Map"),
+ ("read_only_space", 0x038f1): (114, "Tuple3Map"),
+ ("read_only_space", 0x03919): (116, "WasmCapiFunctionDataMap"),
+ ("read_only_space", 0x03941): (117, "WasmDebugInfoMap"),
+ ("read_only_space", 0x03969): (118, "WasmExceptionTagMap"),
+ ("read_only_space", 0x03991): (119, "WasmExportedFunctionDataMap"),
+ ("read_only_space", 0x039b9): (120, "CallableTaskMap"),
+ ("read_only_space", 0x039e1): (121, "CallbackTaskMap"),
+ ("read_only_space", 0x03a09): (122, "PromiseFulfillReactionJobTaskMap"),
+ ("read_only_space", 0x03a31): (123, "PromiseRejectReactionJobTaskMap"),
+ ("read_only_space", 0x03a59): (124, "PromiseResolveThenableJobTaskMap"),
+ ("read_only_space", 0x03a81): (125, "FinalizationGroupCleanupJobTaskMap"),
+ ("read_only_space", 0x03aa9): (126, "AllocationSiteWithWeakNextMap"),
+ ("read_only_space", 0x03ad1): (126, "AllocationSiteWithoutWeakNextMap"),
+ ("read_only_space", 0x03af9): (161, "LoadHandler1Map"),
+ ("read_only_space", 0x03b21): (161, "LoadHandler2Map"),
+ ("read_only_space", 0x03b49): (161, "LoadHandler3Map"),
+ ("read_only_space", 0x03b71): (169, "StoreHandler0Map"),
+ ("read_only_space", 0x03b99): (169, "StoreHandler1Map"),
+ ("read_only_space", 0x03bc1): (169, "StoreHandler2Map"),
+ ("read_only_space", 0x03be9): (169, "StoreHandler3Map"),
("map_space", 0x00141): (1057, "ExternalMap"),
("map_space", 0x00169): (1073, "JSMessageObjectMap"),
}