Skip to content

Commit

Permalink
Refactor double to string conversion
Browse files Browse the repository at this point in the history
  • Loading branch information
adazem009 committed Jan 11, 2025
1 parent 62cc8ac commit a5f91f6
Show file tree
Hide file tree
Showing 3 changed files with 186 additions and 64 deletions.
114 changes: 95 additions & 19 deletions src/scratch/value_functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -242,16 +242,11 @@ extern "C"
}

/*! Writes the string representation of the given value to dst. */
void value_toString(const libscratchcpp::ValueData *v, std::string *dst)
void value_toString(const ValueData *v, std::string *dst)
{
if (v->type == ValueType::String)
dst->assign(v->stringValue);
else if (v->type == ValueType::Number)
value_doubleToString(v->numberValue, dst);
else if (v->type == ValueType::Bool)
dst->assign(v->boolValue ? "true" : "false");
else
dst->clear();
char *str = value_toCString(v);
dst->assign(str);
free(str);
}

/*!
Expand All @@ -260,11 +255,29 @@ extern "C"
*/
char *value_toCString(const ValueData *v)
{
std::string out;
value_toString(v, &out);
char *ret = (char *)malloc((out.size() + 1) * sizeof(char));
strncpy(ret, out.c_str(), out.size() + 1);
return ret;
if (v->type == ValueType::String) {
char *ret = (char *)malloc((strlen(v->stringValue) + 1) * sizeof(char));
strcpy(ret, v->stringValue);
return ret;
} else if (v->type == ValueType::Number)
return value_doubleToCString(v->numberValue);
else if (v->type == ValueType::Bool) {
char *ret;

if (v->boolValue) {
ret = (char *)malloc((4 + 1) * sizeof(char));
strcpy(ret, "true");
} else {
ret = (char *)malloc((5 + 1) * sizeof(char));
strcpy(ret, "false");
}

return ret;
} else {
char *ret = (char *)malloc(sizeof(char));
ret[0] = '\0';
return ret;
}
}

/*! Writes the UTF-16 representation of the given value to dst. */
Expand Down Expand Up @@ -292,11 +305,74 @@ extern "C"
*/
char *value_doubleToCString(double v)
{
std::string out;
value_doubleToString(v, &out);
char *ret = (char *)malloc((out.size() + 1) * sizeof(char));
strncpy(ret, out.c_str(), out.size() + 1);
return ret;
if (v == 0) {
char *ret = (char *)malloc((1 + 1) * sizeof(char));
strcpy(ret, "0");
return ret;
} else if (std::isinf(v)) {
if (v > 0) {
char *ret = (char *)malloc((8 + 1) * sizeof(char));
strcpy(ret, "Infinity");
return ret;
} else {
char *ret = (char *)malloc((9 + 1) * sizeof(char));
strcpy(ret, "-Infinity");
return ret;
}
} else if (std::isnan(v)) {
char *ret = (char *)malloc((3 + 1) * sizeof(char));
strcpy(ret, "NaN");
return ret;
}

const int maxlen = 26; // should be enough for any number
char *buffer = (char *)malloc((maxlen + 1) * sizeof(char));

// Constants for significant digits and thresholds
const int significantDigits = 17 - value_intDigitCount(std::floor(std::fabs(v)));
constexpr double scientificThreshold = 1e21;
constexpr double minScientificThreshold = 1e-6;

// Use scientific notation if the number is very large or very small
if (std::fabs(v) >= scientificThreshold || std::fabs(v) < minScientificThreshold) {
int ret = snprintf(buffer, maxlen, "%.*g", significantDigits - 1, v);
assert(ret >= 0);
} else {
snprintf(buffer, maxlen, "%.*f", significantDigits - 1, v);

// Remove trailing zeros from the fractional part
char *dot = std::strchr(buffer, '.');

if (dot) {
char *end = buffer + std::strlen(buffer) - 1;
while (end > dot && *end == '0') {
*end-- = '\0';
}
if (*end == '.') {
*end = '\0'; // Remove trailing dot
}
}
}

// Remove leading zeros after e+/e-
for (int i = 0; i < 2; i++) {
const char *target = (i == 0) ? "e+" : "e-";
char *index = strstr(buffer, target);

if (index != nullptr) {
char *ptr = index + 2;
while (*(ptr + 1) != '\0' && *ptr == '0') {
// Shift the characters left to erase '0'
char *shiftPtr = ptr;
do {
*shiftPtr = *(shiftPtr + 1);
shiftPtr++;
} while (*shiftPtr != '\0');
}
}
}

return buffer;
}

/*!
Expand Down
58 changes: 13 additions & 45 deletions src/scratch/value_functions_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,19 @@
namespace libscratchcpp
{

template<typename T>
inline unsigned int value_intDigitCount(T v)
{
unsigned int i = 0;

while (v >= 1) {
v /= 10;
i++;
}

return i;
}

template<typename T>
inline unsigned int value_digitCount(T v)
{
Expand Down Expand Up @@ -407,51 +420,6 @@ extern "C"
}
}

inline void value_doubleToString(double v, std::string *dst)
{
if (v == 0) {
dst->assign("0");
return;
} else if (std::isinf(v)) {
if (v > 0)
dst->assign("Infinity");
else
dst->assign("-Infinity");

return;
} else if (std::isnan(v)) {
dst->assign("NaN");
return;
}

std::stringstream stream;

if (v != 0) {
const int exponent = std::log10(std::abs(v));

if (exponent > 20)
stream << std::scientific << std::setprecision(value_digitCount(v / std::pow(10, exponent + 1)) - 1) << v;
else
stream << std::setprecision(std::max(16u, value_digitCount(v))) << v;
} else
stream << std::setprecision(std::max(16u, value_digitCount(v))) << v;

dst->assign(stream.str());
std::size_t index;

for (int i = 0; i < 2; i++) {
if (i == 0)
index = dst->find("e+");
else
index = dst->find("e-");

if (index != std::string::npos) {
while ((dst->size() >= index + 3) && ((*dst)[index + 2] == '0'))
dst->erase(index + 2, 1);
}
}
}

extern "C"
{
inline double value_floatToDouble(float v)
Expand Down
78 changes: 78 additions & 0 deletions test/scratch_classes/value_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1401,6 +1401,30 @@ TEST(ValueTest, ToString)
ASSERT_EQ(utf8::utf16to8(v.toUtf16()), v.toString());
ASSERT_EQ(std::string(cStrings.back()), v.toString());

v = 59.8;
cStrings.push_back(value_toCString(&v.data()));
ASSERT_EQ(v.toString(), "59.8");
ASSERT_EQ(utf8::utf16to8(v.toUtf16()), v.toString());
ASSERT_EQ(std::string(cStrings.back()), v.toString());

v = -59.8;
cStrings.push_back(value_toCString(&v.data()));
ASSERT_EQ(v.toString(), "-59.8");
ASSERT_EQ(utf8::utf16to8(v.toUtf16()), v.toString());
ASSERT_EQ(std::string(cStrings.back()), v.toString());

v = 5.3;
cStrings.push_back(value_toCString(&v.data()));
ASSERT_EQ(v.toString(), "5.3");
ASSERT_EQ(utf8::utf16to8(v.toUtf16()), v.toString());
ASSERT_EQ(std::string(cStrings.back()), v.toString());

v = -5.3;
cStrings.push_back(value_toCString(&v.data()));
ASSERT_EQ(v.toString(), "-5.3");
ASSERT_EQ(utf8::utf16to8(v.toUtf16()), v.toString());
ASSERT_EQ(std::string(cStrings.back()), v.toString());

v = 2550.625021000115;
cStrings.push_back(value_toCString(&v.data()));
ASSERT_EQ(v.toString(), "2550.625021000115");
Expand Down Expand Up @@ -1448,6 +1472,28 @@ TEST(ValueTest, ToString)
ASSERT_EQ(utf8::utf16to8(v.toUtf16()), v.toString());
ASSERT_EQ(std::string(cStrings.back()), v.toString());

v = 0.000001;
cStrings.push_back(value_toCString(&v.data()));
ASSERT_EQ(v.toString(), "0.000001");
ASSERT_EQ(utf8::utf16to8(v.toUtf16()), v.toString());
ASSERT_EQ(std::string(cStrings.back()), v.toString());
v = -0.000001;
cStrings.push_back(value_toCString(&v.data()));
ASSERT_EQ(v.toString(), "-0.000001");
ASSERT_EQ(utf8::utf16to8(v.toUtf16()), v.toString());
ASSERT_EQ(std::string(cStrings.back()), v.toString());

v = 0.0000001;
cStrings.push_back(value_toCString(&v.data()));
ASSERT_EQ(v.toString(), "1e-7");
ASSERT_EQ(utf8::utf16to8(v.toUtf16()), v.toString());
ASSERT_EQ(std::string(cStrings.back()), v.toString());
v = -0.0000001;
cStrings.push_back(value_toCString(&v.data()));
ASSERT_EQ(v.toString(), "-1e-7");
ASSERT_EQ(utf8::utf16to8(v.toUtf16()), v.toString());
ASSERT_EQ(std::string(cStrings.back()), v.toString());

v = false;
cStrings.push_back(value_toCString(&v.data()));
ASSERT_EQ(v.toString(), "false");
Expand Down Expand Up @@ -2846,6 +2892,22 @@ TEST(ValueTest, DoubleToCString)
ASSERT_EQ(strcmp(ret, "-2.54"), 0);
free(ret);

ret = value_doubleToCString(59.8);
ASSERT_EQ(strcmp(ret, "59.8"), 0);
free(ret);

ret = value_doubleToCString(-59.8);
ASSERT_EQ(strcmp(ret, "-59.8"), 0);
free(ret);

ret = value_doubleToCString(5.3);
ASSERT_EQ(strcmp(ret, "5.3"), 0);
free(ret);

ret = value_doubleToCString(-5.3);
ASSERT_EQ(strcmp(ret, "-5.3"), 0);
free(ret);

ret = value_doubleToCString(2550.625021000115);
ASSERT_EQ(strcmp(ret, "2550.625021000115"), 0);
free(ret);
Expand Down Expand Up @@ -2878,6 +2940,22 @@ TEST(ValueTest, DoubleToCString)
ASSERT_EQ(strcmp(ret, "-0.001"), 0);
free(ret);

ret = value_doubleToCString(0.000001);
ASSERT_EQ(strcmp(ret, "0.000001"), 0);
free(ret);

ret = value_doubleToCString(-0.000001);
ASSERT_EQ(strcmp(ret, "-0.000001"), 0);
free(ret);

ret = value_doubleToCString(0.0000001);
ASSERT_EQ(strcmp(ret, "1e-7"), 0);
free(ret);

ret = value_doubleToCString(-0.0000001);
ASSERT_EQ(strcmp(ret, "-1e-7"), 0);
free(ret);

ret = value_doubleToCString(std::numeric_limits<double>::infinity());
ASSERT_EQ(strcmp(ret, "Infinity"), 0);
free(ret);
Expand Down

0 comments on commit a5f91f6

Please sign in to comment.