diff --git a/src/scratch/value_functions.cpp b/src/scratch/value_functions.cpp index d0c85537..0e157b65 100644 --- a/src/scratch/value_functions.cpp +++ b/src/scratch/value_functions.cpp @@ -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); } /*! @@ -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. */ @@ -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; } /*! diff --git a/src/scratch/value_functions_p.h b/src/scratch/value_functions_p.h index 01958ab5..05ec0b88 100644 --- a/src/scratch/value_functions_p.h +++ b/src/scratch/value_functions_p.h @@ -18,6 +18,19 @@ namespace libscratchcpp { +template +inline unsigned int value_intDigitCount(T v) +{ + unsigned int i = 0; + + while (v >= 1) { + v /= 10; + i++; + } + + return i; +} + template inline unsigned int value_digitCount(T v) { @@ -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) diff --git a/test/scratch_classes/value_test.cpp b/test/scratch_classes/value_test.cpp index 901d44ad..71e31bfd 100644 --- a/test/scratch_classes/value_test.cpp +++ b/test/scratch_classes/value_test.cpp @@ -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"); @@ -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"); @@ -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); @@ -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::infinity()); ASSERT_EQ(strcmp(ret, "Infinity"), 0); free(ret);