blob: 5d889c6e46cbf8595ccf93d8cb4e6802da99e8d4 [file] [log] [blame] [edit]
/*
* Copyright (C) 2013-2023 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 <charconv>
#include <cstddef>
#include <cstdio>
#include <random>
#include <wtf/DataLog.h>
#include <wtf/MonotonicTime.h>
#include <wtf/WeakRandom.h>
#include <wtf/dragonbox/dragonbox_to_chars.h>
#include <wtf/dtoa.h>
#include <wtf/dtoa/utils.h>
namespace TestWebKitAPI {
using DoubleToStringConverter = WTF::double_conversion::DoubleToStringConverter;
typedef WTF::double_conversion::StringBuilder DoubleConversionStringBuilder;
class DragonBoxTest {
public:
DragonBoxTest()
: builder1(std::span<char> { buffer1 })
, builder2(std::span<char> { buffer2 })
, converter(DoubleToStringConverter::EcmaScriptConverter())
{
}
float randomFloat()
{
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<float> dis(0.0f, 1.0f);
return dis(gen);
}
double randomDouble()
{
return weakRandom.get();
}
char* dragonBoxToString(double x)
{
builder1.Reset();
WTF::dragonbox::ToShortest(x, &builder1);
auto span = builder1.Finalize();
EXPECT_EQ(span.size(), strlen(span.data()));
return span.data();
}
char* dragonBoxToString(float x)
{
builder1.Reset();
WTF::dragonbox::ToShortest(x, &builder1);
auto span = builder1.Finalize();
EXPECT_EQ(span.size(), strlen(span.data()));
return span.data();
}
char* dragonBoxToExponential(double x)
{
builder1.Reset();
WTF::dragonbox::ToExponential(x, &builder1);
auto span = builder1.Finalize();
EXPECT_EQ(span.size(), strlen(span.data()));
return span.data();
}
char* doubleConversionToString(double x)
{
builder2.Reset();
converter.ToShortest(x, &builder2);
auto span = builder2.Finalize();
EXPECT_EQ(span.size(), strlen(span.data()));
return span.data();
}
char* doubleConversionToString(float x)
{
builder2.Reset();
converter.ToShortestSingle(x, &builder2);
auto span = builder2.Finalize();
EXPECT_EQ(span.size(), strlen(span.data()));
return span.data();
}
char* doubleConversionToExponential(double x, int requested_decimal_digits = -1)
{
builder2.Reset();
converter.ToExponential(x, requested_decimal_digits, &builder2);
auto span = builder2.Finalize();
EXPECT_EQ(span.size(), strlen(span.data()));
return span.data();
}
NumberToStringBuffer buffer1 { 0 };
NumberToStringBuffer buffer2 { 0 };
DoubleConversionStringBuilder builder1;
DoubleConversionStringBuilder builder2;
const DoubleToStringConverter& converter;
WeakRandom weakRandom;
static constexpr bool verbose = false;
};
TEST(WTF, DragonBox)
{
DragonBoxTest t;
{
auto toExponentialTest = [&](double x) {
std::string str1(t.dragonBoxToExponential(x));
std::string str2(t.doubleConversionToExponential(x));
if (str1 != str2)
printf("ToExponential double x=%.100f, str1=%s, str2=%s\n", x, str1.c_str(), str2.c_str());
EXPECT_EQ(str1, str2);
};
auto toStringTest = [&](double x) {
std::string str1(t.dragonBoxToString(x));
std::string str2(t.doubleConversionToString(x));
if (str1 != str2)
printf("ToString double x=%.100f, str1=%s, str2=%s\n", x, str1.c_str(), str2.c_str());
EXPECT_EQ(str1, str2);
};
unsigned testCount = 1e4;
double bases[] = { -1e10, -1e5, 0, 10, 1e5, 1e10 };
for (uint64_t base : bases) {
for (unsigned i = 0; i < testCount; ++i) {
double x = t.randomDouble() * base;
toExponentialTest(x);
toStringTest(x);
}
}
double nums[] = { 0, -0 };
for (double x : nums) {
toExponentialTest(x);
toStringTest(x);
}
}
{
auto toStringTest = [&](float x) {
std::string str1(t.dragonBoxToString(x));
std::string str2(t.doubleConversionToString(x));
if (str1 != str2)
printf("ToString float x=%.100f, str1=%s, str2=%s\n", x, str1.c_str(), str2.c_str());
EXPECT_EQ(str1, str2);
};
unsigned testCount = 1e4;
float bases[] = { -1e6, -1e5, -1e4, -1e3, -1e2, -10, 0, 10, 1e2, 1e3, 1e4, 1e5, 1e6, };
for (uint64_t base : bases) {
for (unsigned i = 0; i < testCount; ++i) {
float x = t.randomFloat() * base;
toStringTest(x);
}
}
float nums[] = { 0, -0 };
for (float x : nums)
toStringTest(x);
}
}
TEST(WTF, DragonBox_perf)
{
DragonBoxTest t;
constexpr size_t size = 1e4;
double nums1[size];
double nums2[size];
double nums3[size];
double nums4[size];
for (size_t i = 0; i < size; ++i) {
double num1 = t.randomDouble();
double num2 = num1 * 10;
double num3 = num1 * 100;
double num4 = num1 * 1000;
nums1[i] = num1;
nums2[i] = num2;
nums3[i] = num3;
nums4[i] = num4;
}
auto start = MonotonicTime::now();
for (size_t i = 0; i < size; ++i) {
t.dragonBoxToExponential(nums1[i]);
t.dragonBoxToExponential(nums2[i]);
t.dragonBoxToExponential(nums3[i]);
t.dragonBoxToExponential(nums4[i]);
}
dataLogLnIf(DragonBoxTest::verbose, "dragonBoxToExponential -> ", MonotonicTime::now() - start);
start = MonotonicTime::now();
for (size_t i = 0; i < size; ++i) {
t.doubleConversionToExponential(nums1[i]);
t.doubleConversionToExponential(nums2[i]);
t.doubleConversionToExponential(nums3[i]);
t.doubleConversionToExponential(nums4[i]);
}
dataLogLnIf(DragonBoxTest::verbose, "doubleConversionToExponential -> ", MonotonicTime::now() - start);
start = MonotonicTime::now();
for (size_t i = 0; i < size; ++i)
t.dragonBoxToString(nums1[i]);
dataLogLnIf(DragonBoxTest::verbose, "dragonBoxToString -> ", MonotonicTime::now() - start, " base 0");
start = MonotonicTime::now();
for (size_t i = 0; i < size; ++i)
t.doubleConversionToString(nums1[i]);
dataLogLnIf(DragonBoxTest::verbose, "doubleConversionToString -> ", MonotonicTime::now() - start, " base 0");
start = MonotonicTime::now();
for (size_t i = 0; i < size; ++i)
t.dragonBoxToString(nums2[i]);
dataLogLnIf(DragonBoxTest::verbose, "dragonBoxToString -> ", MonotonicTime::now() - start, " base 10");
start = MonotonicTime::now();
for (size_t i = 0; i < size; ++i)
t.doubleConversionToString(nums2[i]);
dataLogLnIf(DragonBoxTest::verbose, "doubleConversionToString -> ", MonotonicTime::now() - start, " base 10");
start = MonotonicTime::now();
for (size_t i = 0; i < size; ++i)
t.dragonBoxToString(nums3[i]);
dataLogLnIf(DragonBoxTest::verbose, "dragonBoxToString -> ", MonotonicTime::now() - start, " base 100");
start = MonotonicTime::now();
for (size_t i = 0; i < size; ++i)
t.doubleConversionToString(nums3[i]);
dataLogLnIf(DragonBoxTest::verbose, "doubleConversionToString -> ", MonotonicTime::now() - start, " base 100");
start = MonotonicTime::now();
for (size_t i = 0; i < size; ++i)
t.dragonBoxToString(nums4[i]);
dataLogLnIf(DragonBoxTest::verbose, "dragonBoxToString -> ", MonotonicTime::now() - start, " base 1000");
start = MonotonicTime::now();
for (size_t i = 0; i < size; ++i)
t.doubleConversionToString(nums4[i]);
dataLogLnIf(DragonBoxTest::verbose, "doubleConversionToString -> ", MonotonicTime::now() - start, " base 1000");
}
} // namespace TestWebKitAPI