Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,64 @@

#include "DoubleConversions.h"

#include <double-conversion/double-conversion.h>
#include <array>
#include <cmath>
#include <cstdio>

namespace facebook::react {

std::string toString(double doubleValue, char suffix) {
// Format taken from folly's toString
static double_conversion::DoubleToStringConverter conv(
0,
nullptr,
nullptr,
'E',
-6, // detail::kConvMaxDecimalInShortestLow,
21, // detail::kConvMaxDecimalInShortestHigh,
6, // max leading padding zeros
1); // max trailing padding zeros
std::array<char, 256> buffer{};
double_conversion::StringBuilder builder(buffer.data(), buffer.size());
if (!conv.ToShortest(doubleValue, &builder)) {
int len = 0;
bool stripZeros = false;

if (!std::isfinite(doubleValue)) {
// Serialize infinite and NaN as 0
builder.AddCharacter('0');
buffer[0] = '0';
len = 1;
} else {
double absValue = std::abs(doubleValue);

// Use fixed notation for values in [1e-6, 1e21), scientific notation
// with uppercase E otherwise. This approximates JavaScript's
// Number.toString() behavior, though %g's default precision of 6
// significant digits may lose precision for values with more digits.
if (absValue != 0.0 && (absValue < 1e-6 || absValue >= 1e21)) {
// %G is like %g but uses uppercase E
len = std::snprintf(buffer.data(), buffer.size(), "%G", doubleValue);
} else if (
(absValue >= 1e-6 && absValue < 1e-4) ||
(absValue >= 1e6 && absValue < 1e21)) {
// %g switches to scientific notation for exponents < -4 or >= precision
// (default 6), so we use %f for [1e-6, 1e-4) and [1e6, 1e21).
len = std::snprintf(buffer.data(), buffer.size(), "%.20f", doubleValue);
stripZeros = true;
} else {
len = std::snprintf(buffer.data(), buffer.size(), "%g", doubleValue);
}

if (len <= 0 || static_cast<size_t>(len) >= buffer.size()) {
buffer[0] = '0';
len = 1;
} else if (stripZeros) {
// Strip trailing zeros and unnecessary decimal point
auto end = static_cast<size_t>(len);
while (end > 0 && buffer[end - 1] == '0') {
--end;
}
if (end > 0 && buffer[end - 1] == '.') {
--end;
}
len = static_cast<int>(end);
}
}

auto resultLen = static_cast<size_t>(len);
if (suffix != '\0') {
builder.AddCharacter(suffix);
buffer[resultLen] = suffix;
++resultLen;
}
return builder.Finalize();
return std::string(buffer.data(), resultLen);
}

} // namespace facebook::react
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#include <react/renderer/graphics/DoubleConversions.h>

#include <gtest/gtest.h>
#include <cmath>
#include <limits>

using namespace facebook::react;

TEST(DoubleConversionsTest, zeroValue) {
EXPECT_EQ(toString(0.0, '\0'), "0");
}

TEST(DoubleConversionsTest, negativeZero) {
EXPECT_EQ(toString(-0.0, '\0'), "-0");
}

TEST(DoubleConversionsTest, positiveIntegers) {
EXPECT_EQ(toString(1.0, '\0'), "1");
EXPECT_EQ(toString(42.0, '\0'), "42");
EXPECT_EQ(toString(1000.0, '\0'), "1000");
}

TEST(DoubleConversionsTest, negativeIntegers) {
EXPECT_EQ(toString(-1.0, '\0'), "-1");
EXPECT_EQ(toString(-42.0, '\0'), "-42");
}

TEST(DoubleConversionsTest, fractionalValues) {
EXPECT_EQ(toString(3.14, '\0'), "3.14");
EXPECT_EQ(toString(0.5, '\0'), "0.5");
EXPECT_EQ(toString(-2.75, '\0'), "-2.75");
}

TEST(DoubleConversionsTest, verySmallValues) {
EXPECT_EQ(toString(0.000001, '\0'), "0.000001");
}

TEST(DoubleConversionsTest, veryLargeValues) {
EXPECT_EQ(toString(1e20, '\0'), "100000000000000000000");
}

TEST(DoubleConversionsTest, scientificNotation) {
// Values below 1e-6 should use scientific notation
auto result = toString(1e-7, '\0');
EXPECT_NE(result.find('E'), std::string::npos);
}

TEST(DoubleConversionsTest, infinitySerializedAsZero) {
EXPECT_EQ(toString(std::numeric_limits<double>::infinity(), '\0'), "0");
EXPECT_EQ(toString(-std::numeric_limits<double>::infinity(), '\0'), "0");
}

TEST(DoubleConversionsTest, nanSerializedAsZero) {
EXPECT_EQ(toString(std::numeric_limits<double>::quiet_NaN(), '\0'), "0");
}

TEST(DoubleConversionsTest, suffixAppended) {
EXPECT_EQ(toString(3.14, '%'), "3.14%");
EXPECT_EQ(toString(100.0, 'x'), "100x");
}

TEST(DoubleConversionsTest, nullSuffixNotAppended) {
auto result = toString(42.0, '\0');
EXPECT_EQ(result, "42");
EXPECT_EQ(result.back(), '2');
}

TEST(DoubleConversionsTest, suffixWithSpecialValues) {
EXPECT_EQ(toString(std::numeric_limits<double>::infinity(), '%'), "0%");
EXPECT_EQ(toString(std::numeric_limits<double>::quiet_NaN(), 'x'), "0x");
}
Loading