Threadsafe locale-independent string to/from double conversions
- From: Alex Larsson <alexl redhat com>
- To: <gtk-devel-list gnome org>
- Subject: Threadsafe locale-independent string to/from double conversions
- Date: Wed, 3 Oct 2001 21:53:07 -0400 (EDT)
Here is a patch that adds/fixes locale-independent threadsafe g_strtod()
and g_dtostr() to glib. These are needed when reading config files etc.
Using setlocale (LC_NUMERIC, "C") is not threadsafe, so the old code was
broken.
/ Alex
Index: docs/reference/glib/glib-sections.txt
===================================================================
RCS file: /cvs/gnome/glib/docs/reference/glib/glib-sections.txt,v
retrieving revision 1.46
diff -u -p -r1.46 glib-sections.txt
--- docs/reference/glib/glib-sections.txt 2001/10/01 20:26:10 1.46
+++ docs/reference/glib/glib-sections.txt 2001/10/04 01:43:09
@@ -921,7 +921,11 @@ g_strncasecmp
<SUBSECTION>
g_strreverse
+
+<SUBSECTION>
+G_DTOSTR_BUF_SIZE
g_strtod
+g_dtostr
<SUBSECTION>
g_strchug
Index: docs/reference/glib/tmpl/string_utils.sgml
===================================================================
RCS file: /cvs/gnome/glib/docs/reference/glib/tmpl/string_utils.sgml,v
retrieving revision 1.16
diff -u -p -r1.16 string_utils.sgml
--- docs/reference/glib/tmpl/string_utils.sgml 2001/10/01 22:54:48 1.16
+++ docs/reference/glib/tmpl/string_utils.sgml 2001/10/04 01:43:09
@@ -562,19 +562,44 @@ For example, g_strreverse ("abcdef") wou
@Returns:
+<!-- ##### MACRO G_DTOSTR_BUF_SIZE ##### -->
+<para>
+A good size for a buffer to be passed into <function>g_dtostr</function>.
+It is guaranteed to be enough for all output of that function on systems
+with 64bit IEEE compatible doubles.
+</para>
+<para>
+The typical usage would be something like:
+</para>
+<para>
+<literal>
+ char buf[G_DTOSTR_BUF_SIZE];
+
+ fprintf (out, "value='%s'\n", g_dtostr (value, buf, sizeof (buf)));
+</literal>
+</para>
+
+
+
<!-- ##### FUNCTION g_strtod ##### -->
+<para>
+
+</para>
+
+ nptr:
+ endptr:
+ Returns:
+
+
+<!-- ##### FUNCTION g_dtostr ##### -->
<para>
-Converts a string to a gdouble value.
-It calls the standard <function>strtod()</function> function
-to handle the conversion, but if the string is not completely converted
-it attempts the conversion again in the "C" locale, and returns the best
-match.
+
</para>
- nptr: the string to convert to a numeric value.
- endptr: if non-NULL, it returns the character after the last character used
-in the conversion.
- Returns: the gdouble value.
+ d:
+ buffer:
+ len:
+ Returns:
<!-- ##### FUNCTION g_strchug ##### -->
Index: glib/gstrfuncs.c
===================================================================
RCS file: /cvs/gnome/glib/glib/gstrfuncs.c,v
retrieving revision 1.76
diff -u -p -r1.76 gstrfuncs.c
--- glib/gstrfuncs.c 2001/10/02 23:09:50 1.76
+++ glib/gstrfuncs.c 2001/10/04 01:43:09
@@ -253,46 +253,212 @@ g_strconcat (const gchar *string1, ...)
return concat;
}
+/**
+ * g_strtod:
+ * @nptr: the string to convert to a numeric value.
+ * @endptr: if non-NULL, it returns the character after
+ * the last character used in the conversion.
+ *
+ * Converts a string to a gdouble value.
+ * This function behaves like the standard strtod() function
+ * does in the C locale. It does this without actually
+ * changing the current locale, since that would not be
+ * thread-safe.
+ *
+ * This function is typically used when reading configuration
+ * files or other non-user input that should be locale dependent.
+ * To handle input from the user you should normally use the
+ * locale-sensitive system strtod function.
+ *
+ * To convert from a string to double in a locale-insensitive
+ * way, use @g_dtostr.
+ *
+ * Return value: the gdouble value.
+ **/
gdouble
g_strtod (const gchar *nptr,
- gchar **endptr)
+ gchar **endptr)
{
- gchar *fail_pos_1;
- gchar *fail_pos_2;
- gdouble val_1;
- gdouble val_2 = 0;
+ gchar *fail_pos;
+ gdouble val;
+ struct lconv *locale_data;
+ const char *decimal_point;
+ int decimal_point_len;
+ const char *p, *end, *decimal_point_pos;
g_return_val_if_fail (nptr != NULL, 0);
- fail_pos_1 = NULL;
- fail_pos_2 = NULL;
+ fail_pos = NULL;
- val_1 = strtod (nptr, &fail_pos_1);
-
- if (fail_pos_1 && fail_pos_1[0] != 0)
+ locale_data = localeconv ();
+ decimal_point = locale_data->decimal_point;
+ decimal_point_len = strlen (decimal_point);
+
+ g_assert (decimal_point_len != 0);
+
+ decimal_point_pos = NULL;
+ if (decimal_point[0] != '.' ||
+ decimal_point[1] != 0)
{
- gchar *old_locale;
-
- old_locale = g_strdup (setlocale (LC_NUMERIC, NULL));
- setlocale (LC_NUMERIC, "C");
- val_2 = strtod (nptr, &fail_pos_2);
- setlocale (LC_NUMERIC, old_locale);
- g_free (old_locale);
+ p = nptr;
+ /* Skip leading space */
+ while (isspace ((guchar)*p))
+ p++;
+
+ /* Skip leading optional sign */
+ if (*p == '+' || *p == '-')
+ p++;
+
+ if (p[0] == '0' &&
+ (p[1] == 'x' || p[1] == 'X'))
+ {
+ p += 2;
+ /* HEX - find the (optional) decimal point */
+
+ while (isxdigit ((guchar)*p))
+ p++;
+
+ if (*p == '.')
+ {
+ decimal_point_pos = p++;
+
+ while (isxdigit ((guchar)*p))
+ p++;
+
+ if (*p == 'p' || *p == 'P')
+ p++;
+ if (*p == '+' || *p == '-')
+ p++;
+ while (isdigit ((guchar)*p))
+ p++;
+ end = p;
+ }
+ }
+ else
+ {
+ while (isdigit ((guchar)*p))
+ p++;
+
+ if (*p == '.')
+ {
+ decimal_point_pos = p++;
+
+ while (isdigit ((guchar)*p))
+ p++;
+
+ if (*p == 'e' || *p == 'E')
+ p++;
+ if (*p == '+' || *p == '-')
+ p++;
+ while (isdigit ((guchar)*p))
+ p++;
+ end = p;
+ }
+ }
+ /* For the other cases, we need not convert the decimal point */
}
-
- if (!fail_pos_1 || fail_pos_1[0] == 0 || fail_pos_1 >= fail_pos_2)
+
+ if (decimal_point_pos)
{
- if (endptr)
- *endptr = fail_pos_1;
- return val_1;
+ char *copy, *c;
+
+ /* We need to convert the '.' to the locale specific decimal point */
+ copy = g_malloc (end - nptr + 1 + decimal_point_len);
+
+ c = copy;
+ memcpy (c, nptr, decimal_point_pos - nptr);
+ c += decimal_point_pos - nptr;
+ memcpy (c, decimal_point, decimal_point_len);
+ c += decimal_point_len;
+ memcpy (c, decimal_point_pos + 1, end - (decimal_point_pos + 1));
+ c += end - (decimal_point_pos + 1);
+ *c = 0;
+
+ val = strtod (copy, &fail_pos);
+
+ fail_pos = (char *)nptr + (fail_pos - copy);
+
+ g_free (copy);
+
}
else
+ val = strtod (nptr, &fail_pos);
+
+ if (endptr)
+ *endptr = fail_pos;
+
+ return val;
+}
+
+/**
+ * g_dtostr:
+ * @d: The double to convert
+ * @buffer: A buffer to place the resulting string in
+ * @len: The length of the buffer.
+ *
+ * Converts a double to a string, using the format of
+ * %g in the C locale.
+ *
+ * It generates enough precision that converting the string
+ * back using @g_strtod gives the same machine-number, at
+ * least on machines with IEEE compatible 64bit doubles.
+ * (This does depend on the quality of the system strtod and
+ * printf implementations.)
+ *
+ * A good size to use for the buffer is @G_DTOSTR_BUF_SIZE.
+ * The result will never be larger than that.
+ *
+ * Return value: The pointer to the buffer with the converted string.
+ **/
+gchar *
+g_dtostr (gdouble d,
+ gchar *buffer,
+ gint len)
+{
+ struct lconv *locale_data;
+ const char *decimal_point;
+ int decimal_point_len;
+ gchar *p;
+ int rest_len;
+
+ g_return_val_if_fail (buffer != NULL, NULL);
+
+ g_snprintf (buffer, len, "%.17g", d);
+
+ locale_data = localeconv ();
+ decimal_point = locale_data->decimal_point;
+ decimal_point_len = strlen (decimal_point);
+
+ g_assert (decimal_point_len != 0);
+
+ if (decimal_point[0] != '.' ||
+ decimal_point[1] != 0)
{
- if (endptr)
- *endptr = fail_pos_2;
- return val_2;
+ p = buffer;
+
+ if (*p == '+' || *p == '-')
+ p++;
+
+ while (isdigit ((guchar)*p))
+ p++;
+
+ if (strncmp (p, decimal_point, decimal_point_len) == 0)
+ {
+ *p = '.';
+ p++;
+ if (decimal_point_len > 1) {
+ rest_len = strlen (p + (decimal_point_len-1));
+ memmove (p, p + (decimal_point_len-1),
+ rest_len);
+ p[rest_len] = 0;
+
+ }
+ }
}
+
+ return buffer;
}
+
G_CONST_RETURN gchar*
g_strerror (gint errnum)
Index: glib/gstrfuncs.h
===================================================================
RCS file: /cvs/gnome/glib/glib/gstrfuncs.h,v
retrieving revision 1.16
diff -u -p -r1.16 gstrfuncs.h
--- glib/gstrfuncs.h 2001/09/29 09:42:19 1.16
+++ glib/gstrfuncs.h 2001/10/04 01:43:09
@@ -93,13 +93,11 @@ gint g_ascii_xdigit_val
*/
#define G_STR_DELIMITERS "_-|> <."
gchar* g_strdelimit (gchar *string,
- const gchar *delimiters,
+ const gchar *delimiters,
gchar new_delimiter);
-gchar* g_strcanon (gchar *string,
- const gchar *valid_chars,
- gchar substitutor);
-gdouble g_strtod (const gchar *nptr,
- gchar **endptr);
+gchar* g_strcanon (gchar *string,
+ const gchar *valid_chars,
+ gchar substitutor);
G_CONST_RETURN gchar* g_strerror (gint errnum) G_GNUC_CONST;
G_CONST_RETURN gchar* g_strsignal (gint signum) G_GNUC_CONST;
gchar* g_strreverse (gchar *string);
@@ -117,6 +115,18 @@ gchar * g_strrstr (
gchar * g_strrstr_len (const gchar *haystack,
gssize haystack_len,
const gchar *needle);
+
+/* String to/from double conversion functions */
+
+/* 29 bytes should enough for all possible values that
+ * g_dtostr can produce. Then add 10 for good measure */
+#define G_DTOSTR_BUF_SIZE (29 + 10)
+gdouble g_strtod (const gchar *nptr,
+ gchar **endptr);
+gchar * g_dtostr (gdouble d,
+ gchar *buffer,
+ gint len);
+
/* removes leading spaces */
gchar* g_strchug (gchar *string);
Index: tests/Makefile.am
===================================================================
RCS file: /cvs/gnome/glib/tests/Makefile.am,v
retrieving revision 1.43
diff -u -p -r1.43 Makefile.am
--- tests/Makefile.am 2001/09/03 22:13:16 1.43
+++ tests/Makefile.am 2001/10/04 01:43:09
@@ -71,6 +71,7 @@ test_programs = \
spawn-test \
strfunc-test \
string-test \
+ strtod-test \
thread-test \
threadpool-test \
tree-test \
@@ -113,6 +114,7 @@ slist_test_LDADD = $(progs_LDADD)
spawn_test_LDADD = $(progs_LDADD)
strfunc_test_LDADD = $(progs_LDADD)
string_test_LDADD = $(progs_LDADD)
+strtod_test_LDADD = $(progs_LDADD) -lm
thread_test_LDADD = $(thread_LDADD)
threadpool_test_LDADD = $(thread_LDADD)
tree_test_LDADD = $(progs_LDADD)
--- /dev/null Thu Aug 30 16:30:55 2001
+++ tests/strtod-test.c Wed Oct 3 21:36:55 2001
@@ -0,0 +1,50 @@
+#include <glib.h>
+#include <locale.h>
+#include <string.h>
+#include <math.h>
+
+void
+test_string (char *number, double res)
+{
+ gdouble d;
+ char *locales[] = {"sv_SE", "en_US", "fa_IR", "C"};
+ int l;
+
+ for (l = 0; l < G_N_ELEMENTS (locales); l++)
+ {
+ setlocale (LC_NUMERIC, locales[l]);
+ d = g_strtod (number, NULL);
+ if (d != res)
+ g_print ("g_strtod for locale %s failed\n", locales[l]);
+ }
+}
+
+
+int
+main ()
+{
+ gdouble d;
+ char buffer[G_DTOSTR_BUF_SIZE];
+
+ test_string ("123.123", 123.123);
+ test_string ("123.123e2", 123.123e2);
+ test_string ("123.123e-2", 123.123e-2);
+ test_string ("-123.123", -123.123);
+ test_string ("-123.123e2", -123.123e2);
+ test_string ("-123.123e-2", -123.123e-2);
+
+ d = 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0;
+ g_assert (d == g_strtod (g_dtostr (d, buffer, sizeof (buffer)), NULL));
+
+ d = -179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0;
+ g_assert (d == g_strtod (g_dtostr (d, buffer, sizeof (buffer)), NULL));
+
+ d = pow (2.0, -1024.1);
+ g_assert (d == g_strtod (g_dtostr (d, buffer, sizeof (buffer)), NULL));
+
+ d = -pow (2.0, -1024.1);
+ g_assert (d == g_strtod (g_dtostr (d, buffer, sizeof (buffer)), NULL));
+
+
+ return 0;
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]