SDL: stdlib: Improve SDL_strtod

From 8092e35287edd9ae9f28b9b5f688707cb68e3631 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Carl=20=C3=85stholm?= <[EMAIL REDACTED]>
Date: Thu, 12 Sep 2024 01:23:56 +0200
Subject: [PATCH] stdlib: Improve SDL_strtod

- Handle leading whitespace
- Handle positive sign
- Parse integer part as unsigned long long
- Handle signed zero (this also applies to printf)
---
 src/stdlib/SDL_string.c | 55 ++++++++++++++++++++++-------------------
 1 file changed, 29 insertions(+), 26 deletions(-)

diff --git a/src/stdlib/SDL_string.c b/src/stdlib/SDL_string.c
index 07a58609b61a0..9b086357f4998 100644
--- a/src/stdlib/SDL_string.c
+++ b/src/stdlib/SDL_string.c
@@ -540,7 +540,7 @@ static size_t SDL_ScanLongW(const wchar_t *text, int count, int radix, long *val
 }
 #endif
 
-#if !defined(HAVE_VSSCANF) || !defined(HAVE_STRTOUL) || !defined(HAVE_STRTOD)
+#if !defined(HAVE_VSSCANF) || !defined(HAVE_STRTOUL)
 static size_t SDL_ScanUnsignedLong(const char *text, int count, int radix, unsigned long *valuep)
 {
     const unsigned long ulong_max = ~0UL;
@@ -608,7 +608,7 @@ static size_t SDL_ScanLongLong(const char *text, int count, int radix, long long
 }
 #endif
 
-#if !defined(HAVE_VSSCANF) || !defined(HAVE_STRTOULL)
+#if !defined(HAVE_VSSCANF) || !defined(HAVE_STRTOULL) || !defined(HAVE_STRTOD)
 static size_t SDL_ScanUnsignedLongLong(const char *text, int count, int radix, unsigned long long *valuep)
 {
     const unsigned long long ullong_max = ~0ULL;
@@ -628,35 +628,40 @@ static size_t SDL_ScanUnsignedLongLong(const char *text, int count, int radix, u
 #if !defined(HAVE_VSSCANF) || !defined(HAVE_STRTOD)
 static size_t SDL_ScanFloat(const char *text, double *valuep)
 {
-    const char *textstart = text;
-    unsigned long lvalue = 0;
+    const char *text_start = text;
+    const char *number_start = text_start;
     double value = 0.0;
     bool negative = false;
 
-    if (*text == '-') {
-        negative = true;
+    while (SDL_isspace(*text)) {
         ++text;
     }
-    text += SDL_ScanUnsignedLong(text, 0, 10, &lvalue);
-    value += lvalue;
-    if (*text == '.') {
-        int mult = 10;
+    if (*text == '-' || *text == '+') {
+        negative = *text == '-';
         ++text;
-        while (SDL_isdigit((unsigned char)*text)) {
-            lvalue = *text - '0';
-            value += (double)lvalue / mult;
-            mult *= 10;
+    }
+    number_start = text;
+    if (SDL_isdigit(*text)) {
+        value += SDL_strtoull(text, (char **)(&text), 10);
+        if (*text == '.') {
+            double denom = 10;
             ++text;
+            while (SDL_isdigit(*text)) {
+                value += (double)(*text - '0') / denom;
+                denom *= 10;
+                ++text;
+            }
         }
     }
-    if (valuep && text > textstart) {
-        if (negative && value != 0.0) {
-            *valuep = -value;
-        } else {
-            *valuep = value;
-        }
+    if (text == number_start) {
+        // no number was parsed, and thus no characters were consumed
+        text = text_start;
+    }
+    if (negative) {
+        value = -value;
     }
-    return text - textstart;
+    *valuep = value;
+    return text - text_start;
 }
 #endif
 
@@ -1302,10 +1307,8 @@ double SDL_strtod(const char *string, char **endp)
 #ifdef HAVE_STRTOD
     return strtod(string, endp);
 #else
-    size_t len;
-    double value = 0.0;
-
-    len = SDL_ScanFloat(string, &value);
+    double value;
+    size_t len = SDL_ScanFloat(string, &value);
     if (endp) {
         *endp = (char *)string + len;
     }
@@ -1977,7 +1980,7 @@ static size_t SDL_PrintFloat(char *text, size_t maxlen, SDL_FormatInfo *info, do
     // This isn't especially accurate, but hey, it's easy. :)
     unsigned long long value;
 
-    if (arg < 0) {
+    if (arg < 0.0 || (arg == 0.0 && 1.0 / arg < 0.0)) { // additional check for signed zero
         num[length++] = '-';
         arg = -arg;
     } else if (info->force_sign) {