Files
ark_js_runtime/ecmascript/base/number_helper.cpp
T
zhangyukun 7ffcc4b20f Sync js_runtime to openharmony
Signed-off-by: zhangyukun <zhangyukun8@huawei.com>
2021-09-08 09:20:53 +08:00

686 lines
22 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* Copyright (c) 2021 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ecmascript/base/number_helper.h"
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <iomanip>
#include <sstream>
#include "ecmascript/base/builtins_base.h"
#include "ecmascript/js_tagged_value-inl.h"
#include "ecmascript/object_factory.h"
namespace panda::ecmascript::base {
enum class Sign { NONE, NEG, POS };
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define RETURN_IF_CONVERSION_END(p, end, result) \
if ((p) == (end)) { \
return (result); \
}
constexpr char CHARS[] = "0123456789abcdefghijklmnopqrstuvwxyz"; // NOLINT (modernize-avoid-c-arrays)
constexpr uint64_t MAX_MANTISSA = 0x1ULL << 52U;
static inline uint8_t ToDigit(uint8_t c)
{
if (c >= '0' && c <= '9') {
return c - '0';
}
if (c >= 'A' && c <= 'Z') {
return c - 'A' + DECIMAL;
}
if (c >= 'a' && c <= 'z') {
return c - 'a' + DECIMAL;
}
return '$';
}
bool NumberHelper::IsNonspace(uint16_t c)
{
int i;
int len = sizeof(SPACE_OR_LINE_TERMINAL) / sizeof(SPACE_OR_LINE_TERMINAL[0]);
for (i = 0; i < len; i++) {
if (c == SPACE_OR_LINE_TERMINAL[i]) {
return false;
}
if (c < SPACE_OR_LINE_TERMINAL[i]) {
return true;
}
}
return true;
}
bool NumberHelper::GotoNonspace(uint8_t **ptr, const uint8_t *end)
{
while (*ptr < end) {
uint16_t c = **ptr;
size_t size = 1;
if (**ptr > INT8_MAX) {
size = 0;
uint16_t utf8Bit = INT8_MAX + 1; // equal 0b1000'0000
while (utf8Bit > 0 && (c & utf8Bit) == utf8Bit) {
++size;
utf8Bit >>= 1UL;
}
if (base::utf_helper::ConvertRegionUtf8ToUtf16(*ptr, &c, 1, 0) <= 0) {
return true;
}
}
if (IsNonspace(c)) {
return true;
}
*ptr += size; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
}
return false;
}
static inline double SignedZero(Sign sign)
{
return sign == Sign::NEG ? -0.0 : 0.0;
}
bool NumberHelper::IsEmptyString(const uint8_t *start, const uint8_t *end)
{
auto p = const_cast<uint8_t *>(start);
return !NumberHelper::GotoNonspace(&p, end);
}
JSTaggedValue NumberHelper::DoubleToString(JSThread *thread, double number, int radix)
{
bool negative = false;
if (number < 0.0) {
negative = true;
number = -number;
}
double numberInteger = std::floor(number);
double numberFraction = number - numberInteger;
auto value = bit_cast<uint64_t>(number);
value += 1;
double delta = HALF * (bit_cast<double>(value) - number);
CString result;
if (numberFraction != 0 && numberFraction >= delta) {
result += ".";
result += DecimalsToString(&numberInteger, numberFraction, radix, delta);
}
result = IntergerToString(numberInteger, radix) + result;
if (negative) {
result = "-" + result;
}
return BuiltinsBase::GetTaggedString(thread, result.c_str());
}
JSTaggedValue NumberHelper::DoubleToExponential(JSThread *thread, double number, int digit)
{
CStringStream ss;
if (digit < 0) {
ss << std::setiosflags(std::ios::scientific) << std::setprecision(base::MAX_PRECISION) << number;
} else {
ss << std::setiosflags(std::ios::scientific) << std::setprecision(digit) << number;
}
CString result = ss.str();
size_t found = result.find_last_of('e');
if (found != CString::npos && found < result.size() - 2 && result[found + 2] == '0') {
result.erase(found + 2, 1); // 2:offset of e
}
if (digit < 0) {
size_t end = found;
while (--found > 0) {
if (result[found] != '0') {
break;
}
}
if (result[found] == '.') {
found--;
}
if (found < end - 1) {
result.erase(found + 1, end - found - 1);
}
}
return BuiltinsBase::GetTaggedString(thread, result.c_str());
}
JSTaggedValue NumberHelper::DoubleToFixed(JSThread *thread, double number, int digit)
{
CStringStream ss;
ss << std::setiosflags(std::ios::fixed) << std::setprecision(digit) << number;
return BuiltinsBase::GetTaggedString(thread, ss.str().c_str());
}
JSTaggedValue NumberHelper::DoubleToPrecision(JSThread *thread, double number, int digit)
{
if (number == 0.0) {
return DoubleToFixed(thread, number, digit - 1);
}
CStringStream ss;
double positiveNumber = number > 0 ? number : -number;
int logDigit = std::floor(log10(positiveNumber));
int radixDigit = digit - logDigit - 1;
const int MAX_EXPONENT_DIGIT = 6;
if ((logDigit >= 0 && radixDigit >= 0) || (logDigit < 0 && radixDigit <= MAX_EXPONENT_DIGIT)) {
return DoubleToFixed(thread, number, std::abs(radixDigit));
}
return DoubleToExponential(thread, number, digit - 1);
}
JSTaggedValue NumberHelper::StringToDoubleWithRadix(const uint8_t *start, const uint8_t *end, int radix)
{
auto p = const_cast<uint8_t *>(start);
JSTaggedValue nanResult = BuiltinsBase::GetTaggedDouble(NAN_VALUE);
// 1. skip space and line terminal
if (!NumberHelper::GotoNonspace(&p, end)) {
return nanResult;
}
// 2. sign bit
bool negative = false;
if (*p == '-') {
negative = true;
RETURN_IF_CONVERSION_END(++p, end, nanResult);
} else if (*p == '+') {
RETURN_IF_CONVERSION_END(++p, end, nanResult);
}
// 3. 0x or 0X
bool stripPrefix = true;
// 4. If R  0, then
// a. If R < 2 or R > 36, return NaN.
// b. If R  16, let stripPrefix be false.
if (radix != 0) {
if (radix < MIN_RADIX || radix > MAX_RADIX) {
return nanResult;
}
if (radix != HEXADECIMAL) {
stripPrefix = false;
}
} else {
radix = DECIMAL;
}
int size = 0;
if (stripPrefix) {
if (*p == '0') {
size++;
if (++p != end && (*p == 'x' || *p == 'X')) {
RETURN_IF_CONVERSION_END(++p, end, nanResult);
radix = HEXADECIMAL;
}
}
}
double result = 0;
bool isDone = false;
do {
double part = 0;
uint32_t multiplier = 1;
for (; p != end; ++p) {
// The maximum value to ensure that uint32_t will not overflow
const uint32_t MAX_MULTIPER = 0xffffffffU / 36;
uint32_t m = multiplier * radix;
if (m > MAX_MULTIPER) {
break;
}
int currentBit = ToDigit(*p);
if (currentBit >= radix) {
isDone = true;
break;
}
size++;
part = part * radix + currentBit;
multiplier = m;
}
result = result * multiplier + part;
if (isDone) {
break;
}
} while (p != end);
if (size == 0) {
return nanResult;
}
if (negative) {
result = -result;
}
return BuiltinsBase::GetTaggedDouble(result);
}
char NumberHelper::Carry(char current, int radix)
{
int digit = (current > '9') ? (current - 'a' + DECIMAL) : (current - '0');
digit = (digit == (radix - 1)) ? 0 : digit + 1;
return CHARS[digit];
}
CString NumberHelper::IntergerToString(double number, int radix)
{
ASSERT(radix >= MIN_RADIX && radix <= MAX_RADIX);
CString result;
while (number / radix > MAX_MANTISSA) {
number /= radix;
result = CString("0").append(result);
}
do {
double remainder = std::fmod(number, radix);
result = CHARS[static_cast<int>(remainder)] + result;
number = (number - remainder) / radix;
} while (number > 0);
return result;
}
CString NumberHelper::DecimalsToString(double *numberInteger, double fraction, int radix, double delta)
{
CString result;
while (fraction >= delta) {
fraction *= radix;
delta *= radix;
int64_t integer = std::floor(fraction);
fraction -= integer;
result += CHARS[integer];
if (fraction > HALF && fraction + delta > 1) {
size_t fractionEnd = result.size() - 1;
result[fractionEnd] = Carry(*result.rbegin(), radix);
for (; fractionEnd > 0; fractionEnd--) {
if (result[fractionEnd] == '0') {
result[fractionEnd - 1] = Carry(result[fractionEnd - 1], radix);
} else {
break;
}
}
if (fractionEnd == 0) {
(*numberInteger)++;
}
break;
}
}
// delete 0 in the end
size_t found = result.find_last_not_of('0');
if (found != CString::npos) {
result.erase(found + 1);
}
return result;
}
CString NumberHelper::IntToString(int number)
{
return ToCString(number);
}
// 7.1.12.1 ToString Applied to the Number Type
JSHandle<EcmaString> NumberHelper::NumberToString(const JSThread *thread, JSTaggedValue number)
{
ASSERT(number.IsNumber());
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
if (number.IsInt()) {
return factory->NewFromCanBeCompressString(IntToString(number.GetInt()));
}
double d = number.GetDouble();
if (std::isnan(d)) {
return factory->NewFromCanBeCompressString("NaN");
}
if (d == 0.0) {
return factory->NewFromCanBeCompressString("0");
}
if (d >= INT32_MIN + 1 && d <= INT32_MAX && d == static_cast<double>(static_cast<int32_t>(d))) {
return factory->NewFromCanBeCompressString(IntToString(static_cast<int32_t>(d)));
}
std::string result;
if (d < 0) {
result += "-";
d = -d;
}
if (std::isinf(d)) {
result += "Infinity";
return factory->NewFromStdStringUnCheck(result, true);
}
ASSERT(d > 0);
// 5. Otherwise, let n, k, and s be integers such that k ≥ 1, 10k1 ≤ s < 10k, the Number value for s × 10nk is m,
// and k is as small as possible. If there are multiple possibilities for s, choose the value of s for which s ×
// 10nk is closest in value to m. If there are two such possible values of s, choose the one that is even. Note
// that k is the number of digits in the decimal representation of s and that s is not divisible by 10.
CStringStream str;
str << std::scientific << std::showpoint << std::setprecision(DOUBLE_MAX_PRECISION) << d;
if (strtod(str.str().c_str(), nullptr) != d) {
str.clear();
str.str("");
str << std::scientific << std::showpoint << std::setprecision(DOUBLE_MAX_PRECISION + 1) << d;
}
std::string scientificStr(str.str());
auto indexOfE = scientificStr.find_last_of('e');
ASSERT(indexOfE != std::string::npos);
std::string base = scientificStr.substr(0, indexOfE);
// skip trailing zeros, and base must not be empty.
base = base.substr(0, base.find_last_not_of('0') + 1);
int k = static_cast<int>(base.size()) - 1;
int n = std::stoi(scientificStr.substr(indexOfE + 1)) + 1;
if (n > 0 && n <= 21) { // NOLINT(readability-magic-numbers)
base.erase(1, 1);
if (k <= n) {
// 6. If k ≤ n ≤ 21, return the String consisting of the code units of the k digits of the decimal
// representation of s (in order, with no leading zeroes), followed by nk occurrences of the code unit
// 0x0030 (DIGIT ZERO).
base += std::string(n - k, '0');
} else {
// 7. If 0 < n ≤ 21, return the String consisting of the code units of the most significant n digits of the
// decimal representation of s, followed by the code unit 0x002E (FULL STOP), followed by the code units of
// the remaining kn digits of the decimal representation of s.
base.insert(n, 1, '.');
}
} else if (-6 < n && n <= 0) { // NOLINT(readability-magic-numbers)
// 8. If 6 < n ≤ 0, return the String consisting of the code unit 0x0030 (DIGIT ZERO), followed by the code
// unit 0x002E (FULL STOP), followed by n occurrences of the code unit 0x0030 (DIGIT ZERO), followed by the
// code units of the k digits of the decimal representation of s.
base.erase(1, 1);
base = std::string("0.") + std::string(-n, '0') + base;
} else {
if (k == 1) {
// 9. Otherwise, if k = 1, return the String consisting of the code unit of the single digit of s
base.erase(1, 1);
}
// followed by code unit 0x0065 (LATIN SMALL LETTER E), followed by the code unit 0x002B (PLUS SIGN) or the code
// unit 0x002D (HYPHEN-MINUS) according to whether n1 is positive or negative, followed by the code units of
// the decimal representation of the integer abs(n1) (with no leading zeroes).
base += "e" + (n >= 1 ? std::string("+") : "") + std::to_string(n - 1);
}
result += base;
return factory->NewFromStdStringUnCheck(result, true);
}
double NumberHelper::TruncateDouble(double d)
{
if (std::isnan(d)) {
return 0;
}
if (!std::isfinite(d)) {
return d;
}
// -0 to +0
if (d == 0.0) {
return 0;
}
return (d >= 0) ? std::floor(d) : std::ceil(d);
}
double NumberHelper::StringToDouble(const uint8_t *start, const uint8_t *end, uint8_t radix, uint32_t flags)
{
auto p = const_cast<uint8_t *>(start);
// 1. skip space and line terminal
if (!NumberHelper::GotoNonspace(&p, end)) {
return 0.0;
}
// 2. get number sign
Sign sign = Sign::NONE;
if (*p == '+') {
RETURN_IF_CONVERSION_END(++p, end, NAN_VALUE);
sign = Sign::POS;
} else if (*p == '-') {
RETURN_IF_CONVERSION_END(++p, end, NAN_VALUE);
sign = Sign::NEG;
}
bool ignoreTrailing = (flags & IGNORE_TRAILING) != 0;
// 3. judge Infinity
static const char INF[] = "Infinity"; // NOLINT(modernize-avoid-c-arrays)
if (*p == INF[0]) {
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
for (const char *i = &INF[1]; *i != '\0'; ++i) {
if (++p == end || *p != *i) {
return NAN_VALUE;
}
}
++p;
if (!ignoreTrailing && NumberHelper::GotoNonspace(&p, end)) {
return NAN_VALUE;
}
return sign == Sign::NEG ? -POSITIVE_INFINITY : POSITIVE_INFINITY;
}
// 4. get number radix
bool leadingZero = false;
bool prefixRadix = false;
if (*p == '0' && radix == 0) {
RETURN_IF_CONVERSION_END(++p, end, SignedZero(sign));
if (*p == 'x' || *p == 'X') {
if ((flags & ALLOW_HEX) == 0) {
return ignoreTrailing ? SignedZero(sign) : NAN_VALUE;
}
RETURN_IF_CONVERSION_END(++p, end, NAN_VALUE);
if (sign != Sign::NONE) {
return NAN_VALUE;
}
prefixRadix = true;
radix = HEXADECIMAL;
} else if (*p == 'o' || *p == 'O') {
if ((flags & ALLOW_OCTAL) == 0) {
return ignoreTrailing ? SignedZero(sign) : NAN_VALUE;
}
RETURN_IF_CONVERSION_END(++p, end, NAN_VALUE);
if (sign != Sign::NONE) {
return NAN_VALUE;
}
prefixRadix = true;
radix = OCTAL;
} else if (*p == 'b' || *p == 'B') {
if ((flags & ALLOW_BINARY) == 0) {
return ignoreTrailing ? SignedZero(sign) : NAN_VALUE;
}
RETURN_IF_CONVERSION_END(++p, end, NAN_VALUE);
if (sign != Sign::NONE) {
return NAN_VALUE;
}
prefixRadix = true;
radix = BINARY;
} else {
leadingZero = true;
}
}
if (radix == 0) {
radix = DECIMAL;
}
auto pStart = p;
// 5. skip leading '0'
while (*p == '0') {
RETURN_IF_CONVERSION_END(++p, end, SignedZero(sign));
leadingZero = true;
}
// 6. parse to number
uint64_t intNumber = 0;
uint64_t numberMax = (UINT64_MAX - (radix - 1)) / radix;
int digits = 0;
int exponent = 0;
do {
uint8_t c = ToDigit(*p);
if (c >= radix) {
if (!prefixRadix || ignoreTrailing || (pStart != p && !NumberHelper::GotoNonspace(&p, end))) {
break;
}
// "0b" "0x1.2" "0b1e2" ...
return NAN_VALUE;
}
++digits;
if (intNumber < numberMax) {
intNumber = intNumber * radix + c;
} else {
++exponent;
}
} while (++p != end);
auto number = static_cast<double>(intNumber);
if (sign == Sign::NEG) {
if (number == 0) {
number = -0.0;
} else {
number = -number;
}
}
// 7. deal with other radix except DECIMAL
if (p == end || radix != DECIMAL) {
if ((digits == 0 && !leadingZero) || (p != end && !ignoreTrailing && NumberHelper::GotoNonspace(&p, end))) {
// no digits there, like "0x", "0xh", or error trailing of "0x3q"
return NAN_VALUE;
}
return number * std::pow(radix, exponent);
}
// 8. parse '.'
if (radix == DECIMAL && *p == '.') {
RETURN_IF_CONVERSION_END(++p, end, (digits > 0) ? (number * std::pow(radix, exponent)) : NAN_VALUE);
while (ToDigit(*p) < radix) {
--exponent;
++digits;
if (++p == end) {
break;
}
}
}
if (digits == 0 && !leadingZero) {
// no digits there, like ".", "sss", or ".e1"
return NAN_VALUE;
}
auto pEnd = p;
// 9. parse 'e/E' with '+/-'
char exponentSign = '+';
int additionalExponent = 0;
constexpr int MAX_EXPONENT = INT32_MAX / 2;
if (radix == DECIMAL && (p != end && (*p == 'e' || *p == 'E'))) {
RETURN_IF_CONVERSION_END(++p, end, NAN_VALUE);
// 10. parse exponent number
if (*p == '+' || *p == '-') {
exponentSign = static_cast<char>(*p);
RETURN_IF_CONVERSION_END(++p, end, NAN_VALUE);
}
uint8_t digit;
while ((digit = ToDigit(*p)) < radix) {
if (additionalExponent > MAX_EXPONENT / radix) {
additionalExponent = MAX_EXPONENT;
} else {
additionalExponent = additionalExponent * radix + digit;
}
if (++p == end) {
break;
}
}
}
exponent += (exponentSign == '-' ? -additionalExponent : additionalExponent);
if (!ignoreTrailing && NumberHelper::GotoNonspace(&p, end)) {
return NAN_VALUE;
}
// 10. build StringNumericLiteral string
CString buffer;
if (sign == Sign::NEG) {
buffer += "-";
}
for (uint8_t *i = pStart; i < pEnd; ++i) { // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
if (*i != static_cast<uint8_t>('.')) {
buffer += *i;
}
}
// 11. convert none-prefix radix string
return Strtod(buffer.c_str(), exponent, radix);
}
double NumberHelper::Strtod(const char *str, int exponent, uint8_t radix)
{
ASSERT(str != nullptr);
ASSERT(radix >= base::MIN_RADIX && radix <= base::MAX_RADIX);
auto p = const_cast<char *>(str);
Sign sign = Sign::NONE;
uint64_t number = 0;
uint64_t numberMax = (UINT64_MAX - (radix - 1)) / radix;
double result = 0.0;
if (*p == '-') {
sign = Sign::NEG;
++p;
}
while (*p == '0') {
++p;
}
while (*p != '\0') {
uint8_t digit = ToDigit(static_cast<uint8_t>(*p));
if (digit >= radix) {
break;
}
if (number < numberMax) {
number = number * radix + digit;
} else {
++exponent;
}
++p;
}
if (exponent < 0) {
result = number / std::pow(radix, -exponent);
} else {
result = number * std::pow(radix, exponent);
}
return sign == Sign::NEG ? -result : result;
}
int32_t NumberHelper::DoubleToInt(double d, size_t bits)
{
int32_t ret = 0;
auto u64 = bit_cast<uint64_t>(d);
int exp = static_cast<int>((u64 & DOUBLE_EXPONENT_MASK) >> DOUBLE_SIGNIFICAND_SIZE) - DOUBLE_EXPONENT_BIAS;
if (exp < static_cast<int>(bits - 1)) {
// smaller than INT<bits>_MAX, fast conversion
ret = static_cast<int32_t>(d);
} else if (exp < static_cast<int>(bits + DOUBLE_SIGNIFICAND_SIZE)) {
// Still has significand bits after mod 2^<bits>
// Get low <bits> bits by shift left <64 - bits> and shift right <64 - bits>
uint64_t value = (((u64 & DOUBLE_SIGNIFICAND_MASK) | DOUBLE_HIDDEN_BIT)
<< (exp - DOUBLE_SIGNIFICAND_SIZE + INT64_BITS - bits)) >>
(INT64_BITS - bits);
ret = static_cast<int32_t>(value);
if ((u64 & DOUBLE_SIGN_MASK) == DOUBLE_SIGN_MASK && ret != INT32_MIN) {
ret = -ret;
}
} else {
// No significand bits after mod 2^<bits>, contains NaN and INF
ret = 0;
}
return ret;
}
int32_t NumberHelper::DoubleInRangeInt32(double d)
{
if (d > INT_MAX) {
return INT_MAX;
}
if (d < INT_MIN) {
return INT_MIN;
}
return base::NumberHelper::DoubleToInt(d, base::INT32_BITS);
}
} // namespace panda::ecmascript::base