2013-05-06 03:23:15 +04:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2012 Google Inc. All rights reserved.
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions are
|
|
|
|
* met:
|
|
|
|
*
|
|
|
|
* * Redistributions of source code must retain the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer.
|
|
|
|
* * Redistributions in binary form must reproduce the above
|
|
|
|
* copyright notice, this list of conditions and the following disclaimer
|
|
|
|
* in the documentation and/or other materials provided with the
|
|
|
|
* distribution.
|
|
|
|
* * Neither the name of Google Inc. nor the names of its
|
|
|
|
* contributors may be used to endorse or promote products derived from
|
|
|
|
* this software without specific prior written permission.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
|
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
#include "Decimal.h"
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
#include <float.h>
|
|
|
|
|
|
|
|
#include <wtf/Assertions.h>
|
|
|
|
#include <wtf/MathExtras.h>
|
|
|
|
#include <wtf/Noncopyable.h>
|
|
|
|
#include <wtf/text/StringBuilder.h>
|
|
|
|
|
|
|
|
namespace WebCore {
|
|
|
|
|
|
|
|
namespace DecimalPrivate {
|
|
|
|
|
|
|
|
static int const ExponentMax = 1023;
|
|
|
|
static int const ExponentMin = -1023;
|
|
|
|
static int const Precision = 18;
|
|
|
|
|
|
|
|
static const uint64_t MaxCoefficient = UINT64_C(0x16345785D89FFFF); // 999999999999999999 == 18 9's
|
|
|
|
|
|
|
|
// This class handles Decimal special values.
|
|
|
|
class SpecialValueHandler {
|
|
|
|
WTF_MAKE_NONCOPYABLE(SpecialValueHandler);
|
|
|
|
public:
|
|
|
|
enum HandleResult {
|
|
|
|
BothFinite,
|
|
|
|
BothInfinity,
|
|
|
|
EitherNaN,
|
|
|
|
LHSIsInfinity,
|
|
|
|
RHSIsInfinity,
|
|
|
|
};
|
|
|
|
|
|
|
|
SpecialValueHandler(const Decimal& lhs, const Decimal& rhs);
|
|
|
|
HandleResult handle();
|
|
|
|
Decimal value() const;
|
|
|
|
|
|
|
|
private:
|
|
|
|
enum Result {
|
|
|
|
ResultIsLHS,
|
|
|
|
ResultIsRHS,
|
|
|
|
ResultIsUnknown,
|
|
|
|
};
|
|
|
|
|
|
|
|
const Decimal& m_lhs;
|
|
|
|
const Decimal& m_rhs;
|
|
|
|
Result m_result;
|
|
|
|
};
|
|
|
|
|
|
|
|
SpecialValueHandler::SpecialValueHandler(const Decimal& lhs, const Decimal& rhs)
|
|
|
|
: m_lhs(lhs), m_rhs(rhs), m_result(ResultIsUnknown)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
SpecialValueHandler::HandleResult SpecialValueHandler::handle()
|
|
|
|
{
|
|
|
|
if (m_lhs.isFinite() && m_rhs.isFinite())
|
|
|
|
return BothFinite;
|
|
|
|
|
|
|
|
const Decimal::EncodedData::FormatClass lhsClass = m_lhs.value().formatClass();
|
|
|
|
const Decimal::EncodedData::FormatClass rhsClass = m_rhs.value().formatClass();
|
|
|
|
if (lhsClass == Decimal::EncodedData::ClassNaN) {
|
|
|
|
m_result = ResultIsLHS;
|
|
|
|
return EitherNaN;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rhsClass == Decimal::EncodedData::ClassNaN) {
|
|
|
|
m_result = ResultIsRHS;
|
|
|
|
return EitherNaN;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lhsClass == Decimal::EncodedData::ClassInfinity)
|
|
|
|
return rhsClass == Decimal::EncodedData::ClassInfinity ? BothInfinity : LHSIsInfinity;
|
|
|
|
|
|
|
|
if (rhsClass == Decimal::EncodedData::ClassInfinity)
|
|
|
|
return RHSIsInfinity;
|
|
|
|
|
|
|
|
ASSERT_NOT_REACHED();
|
|
|
|
return BothFinite;
|
|
|
|
}
|
|
|
|
|
|
|
|
Decimal SpecialValueHandler::value() const
|
|
|
|
{
|
|
|
|
switch (m_result) {
|
|
|
|
case ResultIsLHS:
|
|
|
|
return m_lhs;
|
|
|
|
case ResultIsRHS:
|
|
|
|
return m_rhs;
|
|
|
|
case ResultIsUnknown:
|
|
|
|
default:
|
|
|
|
ASSERT_NOT_REACHED();
|
|
|
|
return m_lhs;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// This class is used for 128 bit unsigned integer arithmetic.
|
|
|
|
class UInt128 {
|
|
|
|
public:
|
|
|
|
UInt128(uint64_t low, uint64_t high)
|
|
|
|
: m_high(high), m_low(low)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
UInt128& operator/=(uint32_t);
|
|
|
|
|
|
|
|
uint64_t high() const { return m_high; }
|
|
|
|
uint64_t low() const { return m_low; }
|
|
|
|
|
|
|
|
static UInt128 multiply(uint64_t u, uint64_t v) { return UInt128(u * v, multiplyHigh(u, v)); }
|
|
|
|
|
|
|
|
private:
|
|
|
|
static uint32_t highUInt32(uint64_t x) { return static_cast<uint32_t>(x >> 32); }
|
|
|
|
static uint32_t lowUInt32(uint64_t x) { return static_cast<uint32_t>(x & ((static_cast<uint64_t>(1) << 32) - 1)); }
|
|
|
|
bool isZero() const { return !m_low && !m_high; }
|
|
|
|
static uint64_t makeUInt64(uint32_t low, uint32_t high) { return low | (static_cast<uint64_t>(high) << 32); }
|
|
|
|
|
|
|
|
static uint64_t multiplyHigh(uint64_t, uint64_t);
|
|
|
|
|
|
|
|
uint64_t m_high;
|
|
|
|
uint64_t m_low;
|
|
|
|
};
|
|
|
|
|
|
|
|
UInt128& UInt128::operator/=(const uint32_t divisor)
|
|
|
|
{
|
|
|
|
ASSERT(divisor);
|
|
|
|
|
|
|
|
if (!m_high) {
|
|
|
|
m_low /= divisor;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t dividend[4];
|
|
|
|
dividend[0] = lowUInt32(m_low);
|
|
|
|
dividend[1] = highUInt32(m_low);
|
|
|
|
dividend[2] = lowUInt32(m_high);
|
|
|
|
dividend[3] = highUInt32(m_high);
|
|
|
|
|
|
|
|
uint32_t quotient[4];
|
|
|
|
uint32_t remainder = 0;
|
|
|
|
for (int i = 3; i >= 0; --i) {
|
|
|
|
const uint64_t work = makeUInt64(dividend[i], remainder);
|
|
|
|
remainder = static_cast<uint32_t>(work % divisor);
|
|
|
|
quotient[i] = static_cast<uint32_t>(work / divisor);
|
|
|
|
}
|
|
|
|
m_low = makeUInt64(quotient[0], quotient[1]);
|
|
|
|
m_high = makeUInt64(quotient[2], quotient[3]);
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns high 64bit of 128bit product.
|
|
|
|
uint64_t UInt128::multiplyHigh(uint64_t u, uint64_t v)
|
|
|
|
{
|
|
|
|
const uint64_t uLow = lowUInt32(u);
|
|
|
|
const uint64_t uHigh = highUInt32(u);
|
|
|
|
const uint64_t vLow = lowUInt32(v);
|
|
|
|
const uint64_t vHigh = highUInt32(v);
|
|
|
|
const uint64_t partialProduct = uHigh * vLow + highUInt32(uLow * vLow);
|
|
|
|
return uHigh * vHigh + highUInt32(partialProduct) + highUInt32(uLow * vHigh + lowUInt32(partialProduct));
|
|
|
|
}
|
|
|
|
|
|
|
|
static int countDigits(uint64_t x)
|
|
|
|
{
|
|
|
|
int numberOfDigits = 0;
|
|
|
|
for (uint64_t powerOfTen = 1; x >= powerOfTen; powerOfTen *= 10) {
|
|
|
|
++numberOfDigits;
|
|
|
|
if (powerOfTen >= std::numeric_limits<uint64_t>::max() / 10)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return numberOfDigits;
|
|
|
|
}
|
|
|
|
|
|
|
|
static uint64_t scaleDown(uint64_t x, int n)
|
|
|
|
{
|
|
|
|
ASSERT(n >= 0);
|
|
|
|
while (n > 0 && x) {
|
|
|
|
x /= 10;
|
|
|
|
--n;
|
|
|
|
}
|
|
|
|
return x;
|
|
|
|
}
|
|
|
|
|
|
|
|
static uint64_t scaleUp(uint64_t x, int n)
|
|
|
|
{
|
|
|
|
ASSERT(n >= 0);
|
|
|
|
ASSERT(n < Precision);
|
|
|
|
|
|
|
|
uint64_t y = 1;
|
|
|
|
uint64_t z = 10;
|
|
|
|
for (;;) {
|
|
|
|
if (n & 1)
|
|
|
|
y = y * z;
|
|
|
|
|
|
|
|
n >>= 1;
|
|
|
|
if (!n)
|
|
|
|
return x * y;
|
|
|
|
|
|
|
|
z = z * z;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace DecimalPrivate
|
|
|
|
|
|
|
|
using namespace DecimalPrivate;
|
|
|
|
|
|
|
|
Decimal::EncodedData::EncodedData(Sign sign, FormatClass formatClass)
|
|
|
|
: m_coefficient(0)
|
|
|
|
, m_exponent(0)
|
|
|
|
, m_formatClass(formatClass)
|
|
|
|
, m_sign(sign)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
Decimal::EncodedData::EncodedData(Sign sign, int exponent, uint64_t coefficient)
|
|
|
|
: m_formatClass(coefficient ? ClassNormal : ClassZero)
|
|
|
|
, m_sign(sign)
|
|
|
|
{
|
|
|
|
if (exponent >= ExponentMin && exponent <= ExponentMax) {
|
|
|
|
while (coefficient > MaxCoefficient) {
|
|
|
|
coefficient /= 10;
|
|
|
|
++exponent;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (exponent > ExponentMax) {
|
|
|
|
m_coefficient = 0;
|
|
|
|
m_exponent = 0;
|
|
|
|
m_formatClass = ClassInfinity;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (exponent < ExponentMin) {
|
|
|
|
m_coefficient = 0;
|
|
|
|
m_exponent = 0;
|
|
|
|
m_formatClass = ClassZero;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_coefficient = coefficient;
|
|
|
|
m_exponent = static_cast<int16_t>(exponent);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Decimal::EncodedData::operator==(const EncodedData& another) const
|
|
|
|
{
|
|
|
|
return m_sign == another.m_sign
|
|
|
|
&& m_formatClass == another.m_formatClass
|
|
|
|
&& m_exponent == another.m_exponent
|
|
|
|
&& m_coefficient == another.m_coefficient;
|
|
|
|
}
|
|
|
|
|
|
|
|
Decimal::Decimal(int32_t i32)
|
|
|
|
: m_data(i32 < 0 ? Negative : Positive, 0, i32 < 0 ? static_cast<uint64_t>(-static_cast<int64_t>(i32)) : static_cast<uint64_t>(i32))
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
Decimal::Decimal(Sign sign, int exponent, uint64_t coefficient)
|
2013-05-06 03:23:16 +04:00
|
|
|
: m_data(sign, coefficient ? exponent : 0, coefficient)
|
2013-05-06 03:23:15 +04:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
Decimal::Decimal(const EncodedData& data)
|
|
|
|
: m_data(data)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
Decimal::Decimal(const Decimal& other)
|
|
|
|
: m_data(other.m_data)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
Decimal& Decimal::operator=(const Decimal& other)
|
|
|
|
{
|
|
|
|
m_data = other.m_data;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
Decimal& Decimal::operator+=(const Decimal& other)
|
|
|
|
{
|
|
|
|
m_data = (*this + other).m_data;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
Decimal& Decimal::operator-=(const Decimal& other)
|
|
|
|
{
|
|
|
|
m_data = (*this - other).m_data;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
Decimal& Decimal::operator*=(const Decimal& other)
|
|
|
|
{
|
|
|
|
m_data = (*this * other).m_data;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
Decimal& Decimal::operator/=(const Decimal& other)
|
|
|
|
{
|
|
|
|
m_data = (*this / other).m_data;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
Decimal Decimal::operator-() const
|
|
|
|
{
|
|
|
|
if (isNaN())
|
|
|
|
return *this;
|
|
|
|
|
|
|
|
Decimal result(*this);
|
|
|
|
result.m_data.setSign(invertSign(m_data.sign()));
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
Decimal Decimal::operator+(const Decimal& rhs) const
|
|
|
|
{
|
|
|
|
const Decimal& lhs = *this;
|
|
|
|
const Sign lhsSign = lhs.sign();
|
|
|
|
const Sign rhsSign = rhs.sign();
|
|
|
|
|
|
|
|
SpecialValueHandler handler(lhs, rhs);
|
|
|
|
switch (handler.handle()) {
|
|
|
|
case SpecialValueHandler::BothFinite:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SpecialValueHandler::BothInfinity:
|
|
|
|
return lhsSign == rhsSign ? lhs : nan();
|
|
|
|
|
|
|
|
case SpecialValueHandler::EitherNaN:
|
|
|
|
return handler.value();
|
|
|
|
|
|
|
|
case SpecialValueHandler::LHSIsInfinity:
|
|
|
|
return lhs;
|
|
|
|
|
|
|
|
case SpecialValueHandler::RHSIsInfinity:
|
|
|
|
return rhs;
|
|
|
|
}
|
|
|
|
|
|
|
|
const AlignedOperands alignedOperands = alignOperands(lhs, rhs);
|
|
|
|
|
|
|
|
const uint64_t result = lhsSign == rhsSign
|
|
|
|
? alignedOperands.lhsCoefficient + alignedOperands.rhsCoefficient
|
|
|
|
: alignedOperands.lhsCoefficient - alignedOperands.rhsCoefficient;
|
|
|
|
|
|
|
|
if (lhsSign == Negative && rhsSign == Positive && !result)
|
|
|
|
return Decimal(Positive, alignedOperands.exponent, 0);
|
|
|
|
|
|
|
|
return static_cast<int64_t>(result) >= 0
|
|
|
|
? Decimal(lhsSign, alignedOperands.exponent, result)
|
|
|
|
: Decimal(invertSign(lhsSign), alignedOperands.exponent, -static_cast<int64_t>(result));
|
|
|
|
}
|
|
|
|
|
|
|
|
Decimal Decimal::operator-(const Decimal& rhs) const
|
|
|
|
{
|
|
|
|
const Decimal& lhs = *this;
|
|
|
|
const Sign lhsSign = lhs.sign();
|
|
|
|
const Sign rhsSign = rhs.sign();
|
|
|
|
|
|
|
|
SpecialValueHandler handler(lhs, rhs);
|
|
|
|
switch (handler.handle()) {
|
|
|
|
case SpecialValueHandler::BothFinite:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SpecialValueHandler::BothInfinity:
|
|
|
|
return lhsSign == rhsSign ? nan() : lhs;
|
|
|
|
|
|
|
|
case SpecialValueHandler::EitherNaN:
|
|
|
|
return handler.value();
|
|
|
|
|
|
|
|
case SpecialValueHandler::LHSIsInfinity:
|
|
|
|
return lhs;
|
|
|
|
|
|
|
|
case SpecialValueHandler::RHSIsInfinity:
|
|
|
|
return infinity(invertSign(rhsSign));
|
|
|
|
}
|
|
|
|
|
|
|
|
const AlignedOperands alignedOperands = alignOperands(lhs, rhs);
|
|
|
|
|
|
|
|
const uint64_t result = lhsSign == rhsSign
|
|
|
|
? alignedOperands.lhsCoefficient - alignedOperands.rhsCoefficient
|
|
|
|
: alignedOperands.lhsCoefficient + alignedOperands.rhsCoefficient;
|
|
|
|
|
|
|
|
if (lhsSign == Negative && rhsSign == Negative && !result)
|
|
|
|
return Decimal(Positive, alignedOperands.exponent, 0);
|
|
|
|
|
|
|
|
return static_cast<int64_t>(result) >= 0
|
|
|
|
? Decimal(lhsSign, alignedOperands.exponent, result)
|
|
|
|
: Decimal(invertSign(lhsSign), alignedOperands.exponent, -static_cast<int64_t>(result));
|
|
|
|
}
|
|
|
|
|
|
|
|
Decimal Decimal::operator*(const Decimal& rhs) const
|
|
|
|
{
|
|
|
|
const Decimal& lhs = *this;
|
|
|
|
const Sign lhsSign = lhs.sign();
|
|
|
|
const Sign rhsSign = rhs.sign();
|
|
|
|
const Sign resultSign = lhsSign == rhsSign ? Positive : Negative;
|
|
|
|
|
|
|
|
SpecialValueHandler handler(lhs, rhs);
|
|
|
|
switch (handler.handle()) {
|
|
|
|
case SpecialValueHandler::BothFinite: {
|
|
|
|
const uint64_t lhsCoefficient = lhs.m_data.coefficient();
|
|
|
|
const uint64_t rhsCoefficient = rhs.m_data.coefficient();
|
|
|
|
int resultExponent = lhs.exponent() + rhs.exponent();
|
|
|
|
UInt128 work(UInt128::multiply(lhsCoefficient, rhsCoefficient));
|
|
|
|
while (work.high()) {
|
|
|
|
work /= 10;
|
|
|
|
++resultExponent;
|
|
|
|
}
|
|
|
|
return Decimal(resultSign, resultExponent, work.low());
|
|
|
|
}
|
|
|
|
|
|
|
|
case SpecialValueHandler::BothInfinity:
|
|
|
|
return infinity(resultSign);
|
|
|
|
|
|
|
|
case SpecialValueHandler::EitherNaN:
|
|
|
|
return handler.value();
|
|
|
|
|
|
|
|
case SpecialValueHandler::LHSIsInfinity:
|
|
|
|
return rhs.isZero() ? nan() : infinity(resultSign);
|
|
|
|
|
|
|
|
case SpecialValueHandler::RHSIsInfinity:
|
|
|
|
return lhs.isZero() ? nan() : infinity(resultSign);
|
|
|
|
}
|
|
|
|
|
|
|
|
ASSERT_NOT_REACHED();
|
|
|
|
return nan();
|
|
|
|
}
|
|
|
|
|
|
|
|
Decimal Decimal::operator/(const Decimal& rhs) const
|
|
|
|
{
|
|
|
|
const Decimal& lhs = *this;
|
|
|
|
const Sign lhsSign = lhs.sign();
|
|
|
|
const Sign rhsSign = rhs.sign();
|
|
|
|
const Sign resultSign = lhsSign == rhsSign ? Positive : Negative;
|
|
|
|
|
|
|
|
SpecialValueHandler handler(lhs, rhs);
|
|
|
|
switch (handler.handle()) {
|
|
|
|
case SpecialValueHandler::BothFinite:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SpecialValueHandler::BothInfinity:
|
|
|
|
return nan();
|
|
|
|
|
|
|
|
case SpecialValueHandler::EitherNaN:
|
|
|
|
return handler.value();
|
|
|
|
|
|
|
|
case SpecialValueHandler::LHSIsInfinity:
|
|
|
|
return infinity(resultSign);
|
|
|
|
|
|
|
|
case SpecialValueHandler::RHSIsInfinity:
|
|
|
|
return zero(resultSign);
|
|
|
|
}
|
|
|
|
|
|
|
|
ASSERT(lhs.isFinite());
|
|
|
|
ASSERT(rhs.isFinite());
|
|
|
|
|
|
|
|
if (rhs.isZero())
|
|
|
|
return lhs.isZero() ? nan() : infinity(resultSign);
|
|
|
|
|
|
|
|
int resultExponent = lhs.exponent() - rhs.exponent();
|
|
|
|
|
|
|
|
if (lhs.isZero())
|
|
|
|
return Decimal(resultSign, resultExponent, 0);
|
|
|
|
|
|
|
|
uint64_t remainder = lhs.m_data.coefficient();
|
|
|
|
const uint64_t divisor = rhs.m_data.coefficient();
|
|
|
|
uint64_t result = 0;
|
|
|
|
while (result < MaxCoefficient / 100) {
|
|
|
|
while (remainder < divisor) {
|
|
|
|
remainder *= 10;
|
|
|
|
result *= 10;
|
|
|
|
--resultExponent;
|
|
|
|
}
|
|
|
|
result += remainder / divisor;
|
|
|
|
remainder %= divisor;
|
|
|
|
if (!remainder)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (remainder > divisor / 2)
|
|
|
|
++result;
|
|
|
|
|
|
|
|
return Decimal(resultSign, resultExponent, result);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Decimal::operator==(const Decimal& rhs) const
|
|
|
|
{
|
2013-05-06 03:23:17 +04:00
|
|
|
if (isNaN() || rhs.isNaN())
|
|
|
|
return false;
|
2013-05-06 03:23:15 +04:00
|
|
|
return m_data == rhs.m_data || compareTo(rhs).isZero();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Decimal::operator!=(const Decimal& rhs) const
|
|
|
|
{
|
2013-05-06 03:23:17 +04:00
|
|
|
if (isNaN() || rhs.isNaN())
|
|
|
|
return true;
|
2013-05-06 03:23:15 +04:00
|
|
|
if (m_data == rhs.m_data)
|
|
|
|
return false;
|
|
|
|
const Decimal result = compareTo(rhs);
|
|
|
|
if (result.isNaN())
|
|
|
|
return false;
|
|
|
|
return !result.isZero();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Decimal::operator<(const Decimal& rhs) const
|
|
|
|
{
|
|
|
|
const Decimal result = compareTo(rhs);
|
|
|
|
if (result.isNaN())
|
|
|
|
return false;
|
|
|
|
return !result.isZero() && result.isNegative();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Decimal::operator<=(const Decimal& rhs) const
|
|
|
|
{
|
2013-05-06 03:23:17 +04:00
|
|
|
if (isNaN() || rhs.isNaN())
|
|
|
|
return false;
|
2013-05-06 03:23:15 +04:00
|
|
|
if (m_data == rhs.m_data)
|
|
|
|
return true;
|
|
|
|
const Decimal result = compareTo(rhs);
|
|
|
|
if (result.isNaN())
|
|
|
|
return false;
|
|
|
|
return result.isZero() || result.isNegative();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Decimal::operator>(const Decimal& rhs) const
|
|
|
|
{
|
|
|
|
const Decimal result = compareTo(rhs);
|
|
|
|
if (result.isNaN())
|
|
|
|
return false;
|
|
|
|
return !result.isZero() && result.isPositive();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Decimal::operator>=(const Decimal& rhs) const
|
|
|
|
{
|
2013-05-06 03:23:17 +04:00
|
|
|
if (isNaN() || rhs.isNaN())
|
|
|
|
return false;
|
2013-05-06 03:23:15 +04:00
|
|
|
if (m_data == rhs.m_data)
|
|
|
|
return true;
|
|
|
|
const Decimal result = compareTo(rhs);
|
|
|
|
if (result.isNaN())
|
|
|
|
return false;
|
|
|
|
return result.isZero() || !result.isNegative();
|
|
|
|
}
|
|
|
|
|
|
|
|
Decimal Decimal::abs() const
|
|
|
|
{
|
|
|
|
Decimal result(*this);
|
|
|
|
result.m_data.setSign(Positive);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
Decimal::AlignedOperands Decimal::alignOperands(const Decimal& lhs, const Decimal& rhs)
|
|
|
|
{
|
|
|
|
ASSERT(lhs.isFinite());
|
|
|
|
ASSERT(rhs.isFinite());
|
|
|
|
|
|
|
|
const int lhsExponent = lhs.exponent();
|
|
|
|
const int rhsExponent = rhs.exponent();
|
|
|
|
int exponent = std::min(lhsExponent, rhsExponent);
|
|
|
|
uint64_t lhsCoefficient = lhs.m_data.coefficient();
|
|
|
|
uint64_t rhsCoefficient = rhs.m_data.coefficient();
|
|
|
|
|
|
|
|
if (lhsExponent > rhsExponent) {
|
|
|
|
const int numberOfLHSDigits = countDigits(lhsCoefficient);
|
|
|
|
if (numberOfLHSDigits) {
|
|
|
|
const int lhsShiftAmount = lhsExponent - rhsExponent;
|
|
|
|
const int overflow = numberOfLHSDigits + lhsShiftAmount - Precision;
|
|
|
|
if (overflow <= 0)
|
|
|
|
lhsCoefficient = scaleUp(lhsCoefficient, lhsShiftAmount);
|
|
|
|
else {
|
|
|
|
lhsCoefficient = scaleUp(lhsCoefficient, lhsShiftAmount - overflow);
|
|
|
|
rhsCoefficient = scaleDown(rhsCoefficient, overflow);
|
|
|
|
exponent += overflow;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if (lhsExponent < rhsExponent) {
|
|
|
|
const int numberOfRHSDigits = countDigits(rhsCoefficient);
|
|
|
|
if (numberOfRHSDigits) {
|
|
|
|
const int rhsShiftAmount = rhsExponent - lhsExponent;
|
|
|
|
const int overflow = numberOfRHSDigits + rhsShiftAmount - Precision;
|
|
|
|
if (overflow <= 0)
|
|
|
|
rhsCoefficient = scaleUp(rhsCoefficient, rhsShiftAmount);
|
|
|
|
else {
|
|
|
|
rhsCoefficient = scaleUp(rhsCoefficient, rhsShiftAmount - overflow);
|
|
|
|
lhsCoefficient = scaleDown(lhsCoefficient, overflow);
|
|
|
|
exponent += overflow;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
AlignedOperands alignedOperands;
|
|
|
|
alignedOperands.exponent = exponent;
|
|
|
|
alignedOperands.lhsCoefficient = lhsCoefficient;
|
|
|
|
alignedOperands.rhsCoefficient = rhsCoefficient;
|
|
|
|
return alignedOperands;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Round toward positive infinity.
|
|
|
|
// Note: Mac ports defines ceil(x) as wtf_ceil(x), so we can't use name "ceil" here.
|
|
|
|
Decimal Decimal::ceiling() const
|
|
|
|
{
|
|
|
|
if (isSpecial())
|
|
|
|
return *this;
|
|
|
|
|
|
|
|
if (exponent() >= 0)
|
|
|
|
return *this;
|
|
|
|
|
2013-05-06 03:23:16 +04:00
|
|
|
uint64_t coefficient = m_data.coefficient();
|
|
|
|
const int numberOfDigits = countDigits(coefficient);
|
2013-05-06 03:23:15 +04:00
|
|
|
const int numberOfDropDigits = -exponent();
|
|
|
|
if (numberOfDigits < numberOfDropDigits)
|
|
|
|
return isPositive() ? Decimal(1) : zero(Positive);
|
|
|
|
|
2013-05-06 03:23:16 +04:00
|
|
|
uint64_t result = scaleDown(coefficient, numberOfDropDigits);
|
|
|
|
uint64_t droppedDigits = coefficient - scaleUp(result, numberOfDropDigits);
|
|
|
|
if (droppedDigits && isPositive())
|
|
|
|
result += 1;
|
2013-05-06 03:23:15 +04:00
|
|
|
return Decimal(sign(), 0, result);
|
|
|
|
}
|
|
|
|
|
|
|
|
Decimal Decimal::compareTo(const Decimal& rhs) const
|
|
|
|
{
|
|
|
|
const Decimal result(*this - rhs);
|
|
|
|
switch (result.m_data.formatClass()) {
|
|
|
|
case EncodedData::ClassInfinity:
|
|
|
|
return result.isNegative() ? Decimal(-1) : Decimal(1);
|
|
|
|
|
|
|
|
case EncodedData::ClassNaN:
|
|
|
|
case EncodedData::ClassNormal:
|
|
|
|
return result;
|
|
|
|
|
|
|
|
case EncodedData::ClassZero:
|
|
|
|
return zero(Positive);
|
|
|
|
|
|
|
|
default:
|
|
|
|
ASSERT_NOT_REACHED();
|
|
|
|
return nan();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Round toward negative infinity.
|
|
|
|
Decimal Decimal::floor() const
|
|
|
|
{
|
|
|
|
if (isSpecial())
|
|
|
|
return *this;
|
|
|
|
|
|
|
|
if (exponent() >= 0)
|
|
|
|
return *this;
|
|
|
|
|
2013-05-06 03:23:16 +04:00
|
|
|
uint64_t coefficient = m_data.coefficient();
|
|
|
|
const int numberOfDigits = countDigits(coefficient);
|
2013-05-06 03:23:15 +04:00
|
|
|
const int numberOfDropDigits = -exponent();
|
|
|
|
if (numberOfDigits < numberOfDropDigits)
|
|
|
|
return isPositive() ? zero(Positive) : Decimal(-1);
|
|
|
|
|
2013-05-06 03:23:16 +04:00
|
|
|
uint64_t result = scaleDown(coefficient, numberOfDropDigits);
|
|
|
|
uint64_t droppedDigits = coefficient - scaleUp(result, numberOfDropDigits);
|
|
|
|
if (droppedDigits && isNegative()) {
|
|
|
|
result += 1;
|
|
|
|
}
|
2013-05-06 03:23:15 +04:00
|
|
|
return Decimal(sign(), 0, result);
|
|
|
|
}
|
|
|
|
|
|
|
|
Decimal Decimal::fromDouble(double doubleValue)
|
|
|
|
{
|
|
|
|
if (std::isfinite(doubleValue))
|
|
|
|
return fromString(String::numberToStringECMAScript(doubleValue));
|
|
|
|
|
|
|
|
if (std::isinf(doubleValue))
|
|
|
|
return infinity(doubleValue < 0 ? Negative : Positive);
|
|
|
|
|
|
|
|
return nan();
|
|
|
|
}
|
|
|
|
|
|
|
|
Decimal Decimal::fromString(const String& str)
|
|
|
|
{
|
|
|
|
int exponent = 0;
|
|
|
|
Sign exponentSign = Positive;
|
|
|
|
int numberOfDigits = 0;
|
|
|
|
int numberOfDigitsAfterDot = 0;
|
|
|
|
int numberOfExtraDigits = 0;
|
|
|
|
Sign sign = Positive;
|
|
|
|
|
|
|
|
enum {
|
|
|
|
StateDigit,
|
|
|
|
StateDot,
|
|
|
|
StateDotDigit,
|
|
|
|
StateE,
|
|
|
|
StateEDigit,
|
|
|
|
StateESign,
|
|
|
|
StateSign,
|
|
|
|
StateStart,
|
|
|
|
StateZero,
|
|
|
|
} state = StateStart;
|
|
|
|
|
|
|
|
#define HandleCharAndBreak(expected, nextState) \
|
|
|
|
if (ch == expected) { \
|
|
|
|
state = nextState; \
|
|
|
|
break; \
|
|
|
|
}
|
|
|
|
|
|
|
|
#define HandleTwoCharsAndBreak(expected1, expected2, nextState) \
|
|
|
|
if (ch == expected1 || ch == expected2) { \
|
|
|
|
state = nextState; \
|
|
|
|
break; \
|
|
|
|
}
|
|
|
|
|
|
|
|
uint64_t accumulator = 0;
|
|
|
|
for (unsigned index = 0; index < str.length(); ++index) {
|
|
|
|
const int ch = str[index];
|
|
|
|
switch (state) {
|
|
|
|
case StateDigit:
|
|
|
|
if (ch >= '0' && ch <= '9') {
|
|
|
|
if (numberOfDigits < Precision) {
|
|
|
|
++numberOfDigits;
|
|
|
|
accumulator *= 10;
|
|
|
|
accumulator += ch - '0';
|
|
|
|
} else
|
|
|
|
++numberOfExtraDigits;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
HandleCharAndBreak('.', StateDot);
|
|
|
|
HandleTwoCharsAndBreak('E', 'e', StateE);
|
|
|
|
return nan();
|
|
|
|
|
|
|
|
case StateDot:
|
|
|
|
if (ch >= '0' && ch <= '9') {
|
|
|
|
if (numberOfDigits < Precision) {
|
|
|
|
++numberOfDigits;
|
|
|
|
++numberOfDigitsAfterDot;
|
|
|
|
accumulator *= 10;
|
|
|
|
accumulator += ch - '0';
|
|
|
|
}
|
|
|
|
state = StateDotDigit;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case StateDotDigit:
|
|
|
|
if (ch >= '0' && ch <= '9') {
|
|
|
|
if (numberOfDigits < Precision) {
|
|
|
|
++numberOfDigits;
|
|
|
|
++numberOfDigitsAfterDot;
|
|
|
|
accumulator *= 10;
|
|
|
|
accumulator += ch - '0';
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
HandleTwoCharsAndBreak('E', 'e', StateE);
|
|
|
|
return nan();
|
|
|
|
|
|
|
|
case StateE:
|
|
|
|
if (ch == '+') {
|
|
|
|
exponentSign = Positive;
|
|
|
|
state = StateESign;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ch == '-') {
|
|
|
|
exponentSign = Negative;
|
|
|
|
state = StateESign;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ch >= '0' && ch <= '9') {
|
|
|
|
exponent = ch - '0';
|
|
|
|
state = StateEDigit;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return nan();
|
|
|
|
|
|
|
|
case StateEDigit:
|
|
|
|
if (ch >= '0' && ch <= '9') {
|
|
|
|
exponent *= 10;
|
|
|
|
exponent += ch - '0';
|
|
|
|
if (exponent > ExponentMax + Precision) {
|
|
|
|
if (accumulator)
|
|
|
|
return exponentSign == Negative ? zero(Positive) : infinity(sign);
|
|
|
|
return zero(sign);
|
|
|
|
}
|
|
|
|
state = StateEDigit;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return nan();
|
|
|
|
|
|
|
|
case StateESign:
|
|
|
|
if (ch >= '0' && ch <= '9') {
|
|
|
|
exponent = ch - '0';
|
|
|
|
state = StateEDigit;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return nan();
|
|
|
|
|
|
|
|
case StateSign:
|
|
|
|
if (ch >= '1' && ch <= '9') {
|
|
|
|
accumulator = ch - '0';
|
|
|
|
numberOfDigits = 1;
|
|
|
|
state = StateDigit;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
HandleCharAndBreak('0', StateZero);
|
|
|
|
return nan();
|
|
|
|
|
|
|
|
case StateStart:
|
|
|
|
if (ch >= '1' && ch <= '9') {
|
|
|
|
accumulator = ch - '0';
|
|
|
|
numberOfDigits = 1;
|
|
|
|
state = StateDigit;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ch == '-') {
|
|
|
|
sign = Negative;
|
|
|
|
state = StateSign;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ch == '+') {
|
|
|
|
sign = Positive;
|
|
|
|
state = StateSign;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
HandleCharAndBreak('0', StateZero);
|
|
|
|
HandleCharAndBreak('.', StateDot);
|
|
|
|
return nan();
|
|
|
|
|
|
|
|
case StateZero:
|
|
|
|
if (ch == '0')
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (ch >= '1' && ch <= '9') {
|
|
|
|
accumulator = ch - '0';
|
|
|
|
numberOfDigits = 1;
|
|
|
|
state = StateDigit;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
HandleCharAndBreak('.', StateDot);
|
|
|
|
HandleTwoCharsAndBreak('E', 'e', StateE);
|
|
|
|
return nan();
|
|
|
|
|
|
|
|
default:
|
|
|
|
ASSERT_NOT_REACHED();
|
|
|
|
return nan();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (state == StateZero)
|
|
|
|
return zero(sign);
|
|
|
|
|
|
|
|
if (state == StateDigit || state == StateEDigit || state == StateDotDigit) {
|
|
|
|
int resultExponent = exponent * (exponentSign == Negative ? -1 : 1) - numberOfDigitsAfterDot + numberOfExtraDigits;
|
|
|
|
if (resultExponent < ExponentMin)
|
|
|
|
return zero(Positive);
|
|
|
|
|
|
|
|
const int overflow = resultExponent - ExponentMax + 1;
|
|
|
|
if (overflow > 0) {
|
|
|
|
if (overflow + numberOfDigits - numberOfDigitsAfterDot > Precision)
|
|
|
|
return infinity(sign);
|
|
|
|
accumulator = scaleUp(accumulator, overflow);
|
|
|
|
resultExponent -= overflow;
|
|
|
|
}
|
|
|
|
|
|
|
|
return Decimal(sign, resultExponent, accumulator);
|
|
|
|
}
|
|
|
|
|
|
|
|
return nan();
|
|
|
|
}
|
|
|
|
|
|
|
|
Decimal Decimal::infinity(const Sign sign)
|
|
|
|
{
|
|
|
|
return Decimal(EncodedData(sign, EncodedData::ClassInfinity));
|
|
|
|
}
|
|
|
|
|
|
|
|
Decimal Decimal::nan()
|
|
|
|
{
|
|
|
|
return Decimal(EncodedData(Positive, EncodedData::ClassNaN));
|
|
|
|
}
|
|
|
|
|
|
|
|
Decimal Decimal::remainder(const Decimal& rhs) const
|
|
|
|
{
|
|
|
|
const Decimal quotient = *this / rhs;
|
|
|
|
return quotient.isSpecial() ? quotient : *this - (quotient.isNegative() ? quotient.ceiling() : quotient.floor()) * rhs;
|
|
|
|
}
|
|
|
|
|
|
|
|
Decimal Decimal::round() const
|
|
|
|
{
|
|
|
|
if (isSpecial())
|
|
|
|
return *this;
|
|
|
|
|
|
|
|
if (exponent() >= 0)
|
|
|
|
return *this;
|
|
|
|
|
|
|
|
uint64_t result = m_data.coefficient();
|
|
|
|
const int numberOfDigits = countDigits(result);
|
|
|
|
const int numberOfDropDigits = -exponent();
|
|
|
|
if (numberOfDigits < numberOfDropDigits)
|
|
|
|
return zero(Positive);
|
|
|
|
|
2013-05-06 03:23:16 +04:00
|
|
|
// We're implementing round-half-away-from-zero, so we only need the one
|
|
|
|
// (the most significant) fractional digit:
|
2013-05-06 03:23:15 +04:00
|
|
|
result = scaleDown(result, numberOfDropDigits - 1);
|
|
|
|
if (result % 10 >= 5)
|
|
|
|
result += 10;
|
|
|
|
result /= 10;
|
|
|
|
return Decimal(sign(), 0, result);
|
|
|
|
}
|
|
|
|
|
|
|
|
double Decimal::toDouble() const
|
|
|
|
{
|
|
|
|
if (isFinite()) {
|
|
|
|
bool valid;
|
|
|
|
const double doubleValue = toString().toDouble(&valid);
|
|
|
|
return valid ? doubleValue : std::numeric_limits<double>::quiet_NaN();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isInfinity())
|
|
|
|
return isNegative() ? -std::numeric_limits<double>::infinity() : std::numeric_limits<double>::infinity();
|
|
|
|
|
|
|
|
return std::numeric_limits<double>::quiet_NaN();
|
|
|
|
}
|
|
|
|
|
|
|
|
String Decimal::toString() const
|
|
|
|
{
|
|
|
|
switch (m_data.formatClass()) {
|
|
|
|
case EncodedData::ClassInfinity:
|
|
|
|
return sign() ? "-Infinity" : "Infinity";
|
|
|
|
|
|
|
|
case EncodedData::ClassNaN:
|
|
|
|
return "NaN";
|
|
|
|
|
|
|
|
case EncodedData::ClassNormal:
|
|
|
|
case EncodedData::ClassZero:
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
ASSERT_NOT_REACHED();
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
StringBuilder builder;
|
|
|
|
if (sign())
|
|
|
|
builder.append('-');
|
|
|
|
|
|
|
|
int originalExponent = exponent();
|
|
|
|
uint64_t coefficient = m_data.coefficient();
|
|
|
|
|
|
|
|
if (originalExponent < 0) {
|
|
|
|
const int maxDigits = DBL_DIG;
|
|
|
|
uint64_t lastDigit = 0;
|
|
|
|
while (countDigits(coefficient) > maxDigits) {
|
|
|
|
lastDigit = coefficient % 10;
|
|
|
|
coefficient /= 10;
|
|
|
|
++originalExponent;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lastDigit >= 5)
|
|
|
|
++coefficient;
|
|
|
|
|
|
|
|
while (originalExponent < 0 && coefficient && !(coefficient % 10)) {
|
|
|
|
coefficient /= 10;
|
|
|
|
++originalExponent;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const String digits = String::number(coefficient);
|
|
|
|
int coefficientLength = static_cast<int>(digits.length());
|
|
|
|
const int adjustedExponent = originalExponent + coefficientLength - 1;
|
|
|
|
if (originalExponent <= 0 && adjustedExponent >= -6) {
|
|
|
|
if (!originalExponent) {
|
|
|
|
builder.append(digits);
|
|
|
|
return builder.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (adjustedExponent >= 0) {
|
|
|
|
for (int i = 0; i < coefficientLength; ++i) {
|
|
|
|
builder.append(digits[i]);
|
|
|
|
if (i == adjustedExponent)
|
|
|
|
builder.append('.');
|
|
|
|
}
|
|
|
|
return builder.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
builder.appendLiteral("0.");
|
|
|
|
for (int i = adjustedExponent + 1; i < 0; ++i)
|
|
|
|
builder.append('0');
|
|
|
|
|
|
|
|
builder.append(digits);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
builder.append(digits[0]);
|
|
|
|
while (coefficientLength >= 2 && digits[coefficientLength - 1] == '0')
|
|
|
|
--coefficientLength;
|
|
|
|
if (coefficientLength >= 2) {
|
|
|
|
builder.append('.');
|
|
|
|
for (int i = 1; i < coefficientLength; ++i)
|
|
|
|
builder.append(digits[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (adjustedExponent) {
|
|
|
|
builder.append(adjustedExponent < 0 ? "e" : "e+");
|
|
|
|
builder.appendNumber(adjustedExponent);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return builder.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
Decimal Decimal::zero(Sign sign)
|
|
|
|
{
|
|
|
|
return Decimal(EncodedData(sign, EncodedData::ClassZero));
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace WebCore
|
|
|
|
|