blob: 96b45985ae23e64d3e40f60eea014be856be1957 [file] [edit]
/*
* Copyright (C) 2013-2026 Apple 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:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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 <array>
#include <bit>
#include <cmath>
#include <limits>
#include <string>
#include <wtf/DataLog.h>
#include <wtf/MonotonicTime.h>
#include <wtf/Vector.h>
#include <wtf/WeakRandom.h>
#include <wtf/dtoa.h>
#include <wtf/dtoa/double-conversion.h>
namespace TestWebKitAPI {
using DoubleToStringConverter = WTF::double_conversion::DoubleToStringConverter;
using DoubleConversionStringBuilder = WTF::double_conversion::StringBuilder;
static std::string zmijToString(double x)
{
NumberToStringBuffer buffer;
auto span = numberToStringAndSize(x, buffer);
std::array<char, numberToShortestStringBufferSize> shortestBuffer;
size_t length = numberToShortestString(x, shortestBuffer);
EXPECT_EQ(std::string(span.data(), span.size()), std::string(shortestBuffer.data(), length));
return std::string(span.data(), span.size());
}
static std::string zmijToString(float x)
{
NumberToStringBuffer buffer;
auto span = numberToStringAndSize(x, buffer);
return std::string(span.data(), span.size());
}
static std::string zmijToExponential(double x)
{
NumberToStringBuffer buffer;
auto span = numberToShortestExponentialString(x, buffer);
return std::string(span.data(), span.size());
}
static std::string doubleConversionToString(double x)
{
NumberToStringBuffer buffer;
DoubleConversionStringBuilder builder { std::span<char> { buffer } };
DoubleToStringConverter::EcmaScriptConverter().ToShortest(x, &builder);
auto span = builder.Finalize();
return std::string(span.data(), span.size());
}
static std::string doubleConversionToString(float x)
{
NumberToStringBuffer buffer;
DoubleConversionStringBuilder builder { std::span<char> { buffer } };
DoubleToStringConverter::EcmaScriptConverter().ToShortestSingle(x, &builder);
auto span = builder.Finalize();
return std::string(span.data(), span.size());
}
static std::string doubleConversionToExponential(double x)
{
NumberToStringBuffer buffer;
DoubleConversionStringBuilder builder { std::span<char> { buffer } };
DoubleToStringConverter::EcmaScriptConverter().ToExponential(x, -1, &builder);
auto span = builder.Finalize();
return std::string(span.data(), span.size());
}
static void checkDouble(double x)
{
auto expected = doubleConversionToString(x);
auto actual = zmijToString(x);
EXPECT_EQ(actual, expected) << "ToString bits=" << std::bit_cast<uint64_t>(x);
EXPECT_LE(actual.size(), 25u);
if (std::isfinite(x))
EXPECT_EQ(zmijToExponential(x), doubleConversionToExponential(x)) << "ToExponential bits=" << std::bit_cast<uint64_t>(x);
}
static void checkFloat(float x)
{
EXPECT_EQ(zmijToString(x), doubleConversionToString(x)) << "ToString float bits=" << std::bit_cast<uint32_t>(x);
}
TEST(WTF, ZmijSpecialValues)
{
double doubles[] = {
0, -0.0, 1, -1, 0.1, 0.5, 1.5, 100, 123.456, 1e21, 1e20, 123456789012345680000.0, 1e-6, 1e-7, 1.5e-7, 0.000001234,
9007199254740992.0, 9007199254740993.0, 4294967295.0, 4294967296.0, 1e22, 1e23, 5e-324, 2.2250738585072014e-308,
2.2250738585072009e-308, 1.7976931348623157e308, 1e100, 1e-100, 1e308, 1e-308, 123e-20, 0.3, 2.0 / 3.0,
std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity(), std::numeric_limits<double>::quiet_NaN(),
};
for (double x : doubles) {
checkDouble(x);
checkDouble(-x);
}
for (int exponent = -330; exponent <= 310; ++exponent) {
double power = std::pow(10.0, exponent);
checkDouble(power);
checkDouble(std::nextafter(power, 0.0));
checkDouble(std::nextafter(power, std::numeric_limits<double>::infinity()));
}
for (int binaryExponent : { -31, -30, -13, -12, 52, 53, 70, 71 }) {
double power = std::ldexp(1.0, binaryExponent);
for (double x = power, i = 0; i < 64; ++i, x = std::nextafter(x, 0.0))
checkDouble(x);
for (double x = power, i = 0; i < 64; ++i, x = std::nextafter(x, std::numeric_limits<double>::infinity()))
checkDouble(x);
float floatPower = std::ldexp(1.0f, binaryExponent);
for (float x = floatPower, i = 0; i < 64; ++i, x = std::nextafter(x, 0.0f))
checkFloat(x);
for (float x = floatPower, i = 0; i < 64; ++i, x = std::nextafter(x, std::numeric_limits<float>::infinity()))
checkFloat(x);
}
float floats[] = {
0, -0.0f, 1, 0.1f, 1e-7f, 1e-6f, 1e20f, 1e21f, 1.17549435e-38f, 1.4e-45f, 3.40282347e38f, 16777216.0f, 16777217.0f,
std::numeric_limits<float>::infinity(), -std::numeric_limits<float>::infinity(), std::numeric_limits<float>::quiet_NaN(),
};
for (float x : floats) {
checkFloat(x);
checkFloat(-x);
}
}
TEST(WTF, ZmijRandom)
{
WeakRandom random;
constexpr unsigned testCount = 100000;
for (unsigned i = 0; i < testCount; ++i) {
uint64_t bits = (static_cast<uint64_t>(random.getUint32()) << 32) | random.getUint32();
checkDouble(std::bit_cast<double>(bits));
checkFloat(std::bit_cast<float>(random.getUint32()));
}
double bases[] = { -1e10, -1e5, 10, 1e5, 1e10 };
for (double base : bases) {
for (unsigned i = 0; i < testCount / 10; ++i)
checkDouble(random.get() * base);
}
// Subnormals.
for (unsigned i = 0; i < testCount / 10; ++i) {
checkDouble(std::bit_cast<double>((static_cast<uint64_t>(random.getUint32() & 0xfffff) << 32) | random.getUint32()));
checkFloat(std::bit_cast<float>(random.getUint32() & 0x7fffff));
}
}
TEST(WTF, Zmij_perf)
{
static constexpr bool verbose = false;
static constexpr size_t size = 10'000;
WeakRandom random;
auto makeNumbers = [&](double scale) {
Vector<double> numbers(size, [&](size_t) {
return random.get() * scale;
});
return numbers;
};
auto measure = [&](ASCIILiteral name, ASCIILiteral label, const Vector<double>& numbers, auto&& convert) {
NumberToStringBuffer buffer;
size_t totalLength = 0;
auto start = MonotonicTime::now();
for (double number : numbers)
totalLength += convert(number, buffer);
dataLogLnIf(verbose, name, " -> ", MonotonicTime::now() - start, " ", label, " (", totalLength, " chars)");
};
auto zmijToStringLength = [](double number, NumberToStringBuffer& buffer) {
return numberToStringAndSize(number, buffer).size();
};
auto zmijToExponentialLength = [](double number, NumberToStringBuffer& buffer) {
return numberToShortestExponentialString(number, buffer).size();
};
auto doubleConversionToStringLength = [](double number, NumberToStringBuffer& buffer) {
DoubleConversionStringBuilder builder { std::span<char> { buffer } };
DoubleToStringConverter::EcmaScriptConverter().ToShortest(number, &builder);
return builder.Finalize().size();
};
auto doubleConversionToExponentialLength = [](double number, NumberToStringBuffer& buffer) {
DoubleConversionStringBuilder builder { std::span<char> { buffer } };
DoubleToStringConverter::EcmaScriptConverter().ToExponential(number, -1, &builder);
return builder.Finalize().size();
};
std::pair<ASCIILiteral, Vector<double>> inputs[] = {
{ "base 1"_s, makeNumbers(1) },
{ "base 10"_s, makeNumbers(10) },
{ "base 100"_s, makeNumbers(100) },
{ "base 1000"_s, makeNumbers(1000) },
{ "base 2^-13"_s, makeNumbers(std::ldexp(1.0, -13)) },
{ "base 2^70"_s, makeNumbers(std::ldexp(1.0, 70)) },
};
for (auto& [label, numbers] : inputs) {
measure("zmijToString "_s, label, numbers, zmijToStringLength);
measure("doubleConversionToString "_s, label, numbers, doubleConversionToStringLength);
measure("zmijToExponential "_s, label, numbers, zmijToExponentialLength);
measure("doubleConversionToExponential"_s, label, numbers, doubleConversionToExponentialLength);
}
}
} // namespace TestWebKitAPI