Piotr Caban : ucrtbase: Fix hexadecimal floats parsing in strtod.
Alexandre Julliard
julliard at winehq.org
Fri Nov 22 16:17:26 CST 2019
Module: wine
Branch: master
Commit: c953c36d11ee6f83b8b33e9ec1d7664ff5e680ed
URL: https://source.winehq.org/git/wine.git/?a=commit;h=c953c36d11ee6f83b8b33e9ec1d7664ff5e680ed
Author: Piotr Caban <piotr at codeweavers.com>
Date: Fri Nov 22 16:41:30 2019 +0100
ucrtbase: Fix hexadecimal floats parsing in strtod.
Signed-off-by: Piotr Caban <piotr at codeweavers.com>
Signed-off-by: Alexandre Julliard <julliard at winehq.org>
---
dlls/msvcrt/string.c | 263 +++++++++++++++++++++++++++++++++++++------
dlls/ucrtbase/tests/string.c | 15 ++-
2 files changed, 239 insertions(+), 39 deletions(-)
diff --git a/dlls/msvcrt/string.c b/dlls/msvcrt/string.c
index 10249592bb..fa5f7022c1 100644
--- a/dlls/msvcrt/string.c
+++ b/dlls/msvcrt/string.c
@@ -325,6 +325,221 @@ void CDECL MSVCRT__swab(char* src, char* dst, int len)
}
}
+#if _MSVCR_VER >= 140
+
+enum round {
+ ROUND_ZERO, /* only used when dropped part contains only zeros */
+ ROUND_DOWN,
+ ROUND_EVEN,
+ ROUND_UP
+};
+
+static double make_double(int sign, int exp, ULONGLONG m, enum round round, int *err)
+{
+#define EXP_BITS 11
+#define MANT_BITS 53
+ ULONGLONG bits = 0;
+
+ TRACE("%c %s *2^%d (round %d)\n", sign == -1 ? '-' : '+', wine_dbgstr_longlong(m), exp, round);
+ if (!m) return sign * 0.0;
+
+ /* make sure that we don't overflow modifying exponent */
+ if (exp > 1<<EXP_BITS)
+ {
+ if (err) *err = MSVCRT_ERANGE;
+ return sign * INFINITY;
+ }
+ if (exp < -(1<<EXP_BITS))
+ {
+ if (err) *err = MSVCRT_ERANGE;
+ return sign * 0.0;
+ }
+ exp += MANT_BITS - 1;
+
+ /* normalize mantissa */
+ while(m < (ULONGLONG)1 << (MANT_BITS-1))
+ {
+ m <<= 1;
+ exp--;
+ }
+ while(m >= (ULONGLONG)1 << MANT_BITS)
+ {
+ if (m & 1 || round != ROUND_ZERO)
+ {
+ if (!(m & 1)) round = ROUND_DOWN;
+ else if(round == ROUND_ZERO) round = ROUND_EVEN;
+ else round = ROUND_UP;
+ }
+ m >>= 1;
+ exp++;
+ }
+
+ /* handle subnormal that falls into regular range due to rounding */
+ exp += (1 << (EXP_BITS-1)) - 1;
+ if (!exp && (round == ROUND_UP || (round == ROUND_EVEN && m & 1)))
+ {
+ if (m + 1 >= (ULONGLONG)1 << MANT_BITS)
+ {
+ m++;
+ m >>= 1;
+ exp++;
+ round = ROUND_DOWN;
+ }
+ }
+
+ /* handle subnormals */
+ if (exp <= 0)
+ m >>= 1;
+ while(m && exp<0)
+ {
+ m >>= 1;
+ exp++;
+ }
+
+ /* round mantissa */
+ if (round == ROUND_UP || (round == ROUND_EVEN && m & 1))
+ {
+ m++;
+ if (m >= (ULONGLONG)1 << MANT_BITS)
+ {
+ exp++;
+ m >>= 1;
+ }
+ }
+
+ if (exp >= 1<<EXP_BITS)
+ {
+ if (err) *err = MSVCRT_ERANGE;
+ return sign * INFINITY;
+ }
+ if (!m || exp < 0)
+ {
+ if (err) *err = MSVCRT_ERANGE;
+ return sign * 0.0;
+ }
+
+ if (sign == -1) bits |= (ULONGLONG)1 << (MANT_BITS + EXP_BITS - 1);
+ bits |= (ULONGLONG)exp << (MANT_BITS - 1);
+ bits |= m & (((ULONGLONG)1 << (MANT_BITS - 1)) - 1);
+
+ TRACE("returning %s\n", wine_dbgstr_longlong(bits));
+ return *((double*)&bits);
+}
+
+static inline int hex2int(char c)
+{
+ if (c >= '0' && c <= '9')
+ return c - '0';
+ else if (c >= 'a' && c <= 'f')
+ return c - 'a' + 10;
+ else if (c >= 'A' && c <= 'F')
+ return c - 'A' + 10;
+ return -1;
+}
+
+static double strtod16(int sign, const char *p, char **end,
+ MSVCRT_pthreadlocinfo locinfo, int *err)
+{
+ enum round round = ROUND_ZERO;
+ BOOL found_digit = FALSE;
+ ULONGLONG m = 0;
+ int val, exp = 0;
+
+ while(m < MSVCRT_UI64_MAX/16)
+ {
+ val = hex2int(*p);
+ if (val == -1) break;
+ found_digit = TRUE;
+ p++;
+
+ m = m*16 + val;
+ }
+ while(1)
+ {
+ val = hex2int(*p);
+ if (val == -1) break;
+ p++;
+ exp += 4;
+
+ if (val || round != ROUND_ZERO)
+ {
+ if (val < 8) round = ROUND_DOWN;
+ else if (val == 8 && round == ROUND_ZERO) round = ROUND_EVEN;
+ else round = ROUND_UP;
+ }
+ }
+
+ if(*p == *locinfo->lconv->decimal_point)
+ p++;
+ else if (!found_digit)
+ {
+ if(end) *end = (char*)p - 1;
+ return 0.0;
+ }
+
+ while(m <= MSVCRT_UI64_MAX/16)
+ {
+ val = hex2int(*p);
+ if (val == -1) break;
+ found_digit = TRUE;
+ p++;
+
+ m = m*16 + val;
+ exp -= 4;
+ }
+ while(1)
+ {
+ val = hex2int(*p);
+ if (val == -1) break;
+ p++;
+
+ if (val || round != ROUND_ZERO)
+ {
+ if (val < 8) round = ROUND_DOWN;
+ else if (val == 8 && round == ROUND_ZERO) round = ROUND_EVEN;
+ else round = ROUND_UP;
+ }
+ }
+
+ if (!found_digit)
+ {
+ if(end) *end = (char*)p - 2;
+ return 0.0;
+ }
+
+ if(*p=='p' || *p=='P') {
+ int e=0, s=1;
+
+ p++;
+ if(*p == '-') {
+ s = -1;
+ p++;
+ } else if(*p == '+')
+ p++;
+
+ if(*p>='0' && *p<='9') {
+ while(*p>='0' && *p<='9') {
+ if(e>INT_MAX/10 || (e=e*10+*p-'0')<0)
+ e = INT_MAX;
+ p++;
+ }
+ e *= s;
+
+ if(exp<0 && e<0 && exp+e>=0) exp = INT_MIN;
+ else if(exp>0 && e>0 && exp+e<0) exp = INT_MAX;
+ else exp += e;
+ } else {
+ if(*p=='-' || *p=='+')
+ p--;
+ p--;
+ }
+ }
+
+ if (end) *end = (char*)p;
+ return make_double(sign, exp, m, round, err);
+}
+#endif
+
static double strtod_helper(const char *str, char **end, MSVCRT__locale_t locale, int *err)
{
MSVCRT_pthreadlocinfo locinfo;
@@ -335,7 +550,6 @@ static double strtod_helper(const char *str, char **end, MSVCRT__locale_t locale
double ret;
long double lret=1, expcnt = 10;
BOOL found_digit = FALSE, negexp;
- int base = 10;
if(err)
*err = 0;
@@ -379,33 +593,22 @@ static double strtod_helper(const char *str, char **end, MSVCRT__locale_t locale
return NAN;
}
- if(p[0] == '0' && MSVCRT__tolower_l(p[1], locale) == 'x') {
- base = 16;
- expcnt = 2;
+ if(p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) {
p += 2;
+ return strtod16(sign, p, end, locinfo, err);
}
#endif
- while((*p>='0' && *p<='9') ||
- (base == 16 && ((*p >= 'a' && *p <= 'f') || (*p >= 'A' && *p <= 'F')))) {
- char c = *p++;
- int val;
+ while(*p>='0' && *p<='9') {
found_digit = TRUE;
- if (c>='0' && c<='9')
- val = c - '0';
- else if (c >= 'a' && c <= 'f')
- val = 10 + c - 'a';
- else
- val = 10 + c - 'A';
- hlp = d*base+val;
- if(d>MSVCRT_UI64_MAX/base || hlp<d) {
+ hlp = d * 10 + *p++ - '0';
+ if(d>MSVCRT_UI64_MAX/10 || hlp<d) {
exp++;
break;
} else
d = hlp;
}
- while((*p>='0' && *p<='9') ||
- (base == 16 && ((*p >= 'a' && *p <= 'f') || (*p >= 'A' && *p <= 'F')))) {
+ while(*p>='0' && *p<='9') {
exp++;
p++;
}
@@ -413,25 +616,15 @@ static double strtod_helper(const char *str, char **end, MSVCRT__locale_t locale
if(*p == *locinfo->lconv->decimal_point)
p++;
- while((*p>='0' && *p<='9') ||
- (base == 16 && ((*p >= 'a' && *p <= 'f') || (*p >= 'A' && *p <= 'F')))) {
- char c = *p++;
- int val;
+ while(*p>='0' && *p<='9') {
found_digit = TRUE;
- if (c>='0' && c<='9')
- val = c - '0';
- else if (c >= 'a' && c <= 'f')
- val = 10 + c - 'a';
- else
- val = 10 + c - 'A';
- hlp = d*base+val;
- if(d>MSVCRT_UI64_MAX/base || hlp<d)
+ hlp = d * 10 + *p++ - '0';
+ if(d>MSVCRT_UI64_MAX/10 || hlp<d)
break;
d = hlp;
exp--;
}
- while((*p>='0' && *p<='9') ||
- (base == 16 && ((*p >= 'a' && *p <= 'f') || (*p >= 'A' && *p <= 'F'))))
+ while(*p>='0' && *p<='9')
p++;
if(!found_digit) {
@@ -440,11 +633,7 @@ static double strtod_helper(const char *str, char **end, MSVCRT__locale_t locale
return 0.0;
}
- if(base == 16)
- exp *= 4;
-
- if((base == 10 && (*p=='e' || *p=='E' || *p=='d' || *p=='D')) ||
- (base == 16 && (*p=='p' || *p=='P'))) {
+ if(*p=='e' || *p=='E' || *p=='d' || *p=='D') {
int e=0, s=1;
p++;
diff --git a/dlls/ucrtbase/tests/string.c b/dlls/ucrtbase/tests/string.c
index e4146c6fc4..bfa602c538 100644
--- a/dlls/ucrtbase/tests/string.c
+++ b/dlls/ucrtbase/tests/string.c
@@ -125,9 +125,9 @@ static void _test_strtod_str(int line, const char* string, double value, int len
double d;
d = p_strtod(string, &end);
if (local_isnan(value))
- ok_(__FILE__, line)(local_isnan(d), "d = %lf (\"%s\")\n", d, string);
+ ok_(__FILE__, line)(local_isnan(d), "d = %.16le (\"%s\")\n", d, string);
else
- ok_(__FILE__, line)(d == value, "d = %lf (\"%s\")\n", d, string);
+ ok_(__FILE__, line)(d == value, "d = %.16le (\"%s\")\n", d, string);
ok_(__FILE__, line)(end == string + length, "incorrect end (%d, \"%s\")\n", (int)(end - string), string);
}
@@ -162,6 +162,17 @@ static void test_strtod(void)
test_strtod_str("0x1.1p1", 2.125, 7);
test_strtod_str("0x1.A", 1.625, 5);
test_strtod_str("0x1p1a", 2, 5);
+ test_strtod_str("0xp3", 0, 1);
+ test_strtod_str("0x.", 0, 1);
+ test_strtod_str("0x.8", 0.5, 4);
+ test_strtod_str("0x.8p", 0.5, 4);
+ test_strtod_str("0x0p10000000000000000000000000", 0, 30);
+ test_strtod_str("0x1p-1026", 1.3906711615670009e-309, 9);
+
+ test_strtod_str("0x1ffffffffffffe.80000000000000000000", 9007199254740990.0, 37);
+ test_strtod_str("0x1ffffffffffffe.80000000000000000001", 9007199254740991.0, 37);
+ test_strtod_str("0x1fffffffffffff.80000000000000000000", 9007199254740992.0, 37);
+ test_strtod_str("0x1fffffffffffff.80000000000000000001", 9007199254740992.0, 37);
}
static void test__memicmp(void)
More information about the wine-cvs
mailing list