New g_ascii_strtod/g_ascii_dtostr() patch
- From: Alex Larsson <alexl redhat com>
- To: <gtk-devel-list gnome org>
- Cc: <timj gtk org>
- Subject: New g_ascii_strtod/g_ascii_dtostr() patch
- Date: Thu, 4 Oct 2001 13:23:05 -0400 (EDT)
After a discussion with tim on irc yesterday i made some changes to the
patch. Here is a new version that keeps the old g_strtod() behaviour, and
adds the new g_ascii_ versions. I also changed the g_ascii_dtostr() API.
Now you can formate it differently if you want.
Jody told me on irc that gnumeric has had a lot of problems because
strtod() does not clear errno, so if you do stuff like
if (strtol ())
strtod()
an ERANGE set by strtol is left even if strtod() succeeded.
He proposed that we should set errno to 0 in g_ascii_strtod(). What do
people think about this? Personally i don't like doing magic that makes
it behave in a way the standard call does.
/ 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 17:04:32
@@ -921,6 +921,11 @@ g_strncasecmp
<SUBSECTION>
g_strreverse
+
+<SUBSECTION>
+G_ASCII_DTOSTR_BUF_SIZE
+g_ascii_strtod
+g_ascii_dtostr
g_strtod
<SUBSECTION>
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 17:04:32
@@ -562,19 +562,45 @@ For example, g_strreverse ("abcdef") wou
@Returns:
-<!-- ##### FUNCTION g_strtod ##### -->
+<!-- ##### MACRO G_ASCII_DTOSTR_BUF_SIZE ##### -->
<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.
+A good size for a buffer to be passed into <function>g_ascii_dtostr</function>
+if you use the "%.17g" format. 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_ASCII_DTOSTR_BUF_SIZE];
+
+ fprintf (out, "value=%s\n", g_ascii_dtostr (buf, sizeof (buf), "%.17g", value));
+</literal>
+</para>
+
+
+
+<!-- ##### FUNCTION g_ascii_strtod ##### -->
+<para>
+
+</para>
+
+ nptr:
+ endptr:
+ Returns:
+
+
+<!-- ##### FUNCTION g_ascii_dtostr ##### -->
+<para>
+
+</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.
+ buffer:
+ buf_len:
+ format:
+ d:
+ Returns:
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 17:04:32
@@ -253,9 +253,22 @@ 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.
+ * It calls the standard strtod() function to handle the conversion, but
+ * if the string is not completely converted it attempts the conversion
+ * again with @g_ascii_strtod, and returns the best match.
+ *
+ * Return value: the gdouble value.
+ **/
gdouble
g_strtod (const gchar *nptr,
- gchar **endptr)
+ gchar **endptr)
{
gchar *fail_pos_1;
gchar *fail_pos_2;
@@ -270,15 +283,7 @@ g_strtod (const gchar *nptr,
val_1 = strtod (nptr, &fail_pos_1);
if (fail_pos_1 && fail_pos_1[0] != 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);
- }
+ val_2 = g_ascii_strtod (nptr, &fail_pos_2);
if (!fail_pos_1 || fail_pos_1[0] == 0 || fail_pos_1 >= fail_pos_2)
{
@@ -293,6 +298,246 @@ g_strtod (const gchar *nptr,
return val_2;
}
}
+
+
+/**
+ * g_ascii_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_ascii_dtostr.
+ *
+ * Return value: the gdouble value.
+ **/
+gdouble
+g_ascii_strtod (const gchar *nptr,
+ gchar **endptr)
+{
+ gchar *fail_pos;
+ gdouble val;
+ struct lconv *locale_data;
+ const char *decimal_point;
+ int decimal_point_len;
+ const char *p, *decimal_point_pos;
+ const char *end = NULL; /* Silence gcc */
+
+ g_return_val_if_fail (nptr != NULL, 0);
+
+ fail_pos = NULL;
+
+ 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)
+ {
+ 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 (decimal_point_pos)
+ {
+ 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);
+
+ if (fail_pos)
+ {
+ if (fail_pos > decimal_point_pos)
+ fail_pos = (char *)nptr + (fail_pos - copy) - (decimal_point_len - 1);
+ else
+ fail_pos = (char *)nptr + (fail_pos - copy);
+ }
+
+ g_free (copy);
+
+ }
+ else
+ val = strtod (nptr, &fail_pos);
+
+ if (endptr)
+ *endptr = fail_pos;
+
+ return val;
+}
+
+/**
+ * g_ascii_dtostr:
+ * @buffer: A buffer to place the resulting string in
+ * @buf_len: The length of the buffer.
+ * @format: The printf-style format to use for the
+ * code to use for converting.
+ * @d: The double to convert
+ *
+ * Converts a double to a string, using the '.' as
+ * decimal_point. To format the number you pass in
+ * a printf-style formating string. Allowed conversion
+ * specifiers are eEfFgG.
+ *
+ * If you want to generates enough precision that converting
+ * the string back using @g_strtod gives the same machine-number
+ * (on machines with IEEE compatible 64bit doubles) use the format
+ * string "%.17g". If you do this it is guaranteed that the size
+ * of the resulting string will never be larger than
+ * @G_ASCII_DTOSTR_BUF_SIZE bytes.
+ *
+ * Return value: The pointer to the buffer with the converted string.
+ **/
+gchar *
+g_ascii_dtostr (gchar *buffer,
+ gint buf_len,
+ const gchar *format,
+ gdouble d)
+{
+ struct lconv *locale_data;
+ const char *decimal_point;
+ int decimal_point_len;
+ gchar *p;
+ int rest_len;
+ gchar format_char;
+
+ g_return_val_if_fail (buffer != NULL, NULL);
+ g_return_val_if_fail (format[0] == '%', NULL);
+ g_return_val_if_fail (strpbrk (format + 1, "'l%") == NULL, NULL);
+
+ format_char = format[strlen (format) - 1];
+
+ g_return_val_if_fail (format_char == 'e' || format_char == 'E' ||
+ format_char == 'f' || format_char == 'F' ||
+ format_char == 'g' || format_char == 'G',
+ NULL);
+
+ if (format[0] != '%')
+ return NULL;
+
+ if (strpbrk (format + 1, "'l%"))
+ return NULL;
+
+ if (!(format_char == 'e' || format_char == 'E' ||
+ format_char == 'f' || format_char == 'F' ||
+ format_char == 'g' || format_char == 'G'))
+ return NULL;
+
+
+ g_snprintf (buffer, buf_len, format, 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)
+ {
+ 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 17:04:32
@@ -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,21 @@ gchar * g_strrstr (
gchar * g_strrstr_len (const gchar *haystack,
gssize haystack_len,
const gchar *needle);
+
+/* String to/from double conversion functions */
+
+gdouble g_strtod (const gchar *nptr,
+ gchar **endptr);
+gdouble g_ascii_strtod (const gchar *nptr,
+ gchar **endptr);
+/* 29 bytes should enough for all possible values that
+ * g_ascii_dtostr can produce with the %.17g format.
+ * Then add 10 for good measure */
+#define G_ASCII_DTOSTR_BUF_SIZE (29 + 10)
+gchar * g_ascii_dtostr (gchar *buffer,
+ gint buf_len,
+ const gchar *format,
+ gdouble d);
/* 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 17:04:32
@@ -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)
Index: tests/strtod-test.c
===================================================================
RCS file: /cvs/gnome/glib/tests/strtod-test.c,v
retrieving revision 1.0
diff -u -p -r1.0 strtod-test.c
--- /dev/null Thu Aug 30 16:30:55 2001
+++ tests/strtod-test.c Thu Oct 4 12:19:56 2001
@@ -0,0 +1,53 @@
+#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;
+ char *end;
+
+ for (l = 0; l < G_N_ELEMENTS (locales); l++)
+ {
+ setlocale (LC_NUMERIC, locales[l]);
+ d = g_ascii_strtod (number, &end);
+ if (d != res)
+ g_print ("g_ascii_strtod for locale %s failed\n", locales[l]);
+ if (*end != 0)
+ g_print ("g_ascii_strtod for locale %s endptr was wrong\n", locales[l]);
+ }
+}
+
+
+int
+main ()
+{
+ gdouble d;
+ char buffer[G_ASCII_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_ascii_strtod (g_ascii_dtostr (buffer, sizeof (buffer), "%.17g", d), NULL));
+
+ d = -179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0;
+ g_assert (d == g_ascii_strtod (g_ascii_dtostr (buffer, sizeof (buffer), "%.17g", d), NULL));
+
+ d = pow (2.0, -1024.1);
+ g_assert (d == g_ascii_strtod (g_ascii_dtostr (buffer, sizeof (buffer), "%.17g", d), NULL));
+
+ d = -pow (2.0, -1024.1);
+ g_assert (d == g_ascii_strtod (g_ascii_dtostr (buffer, sizeof (buffer), "%.17g", d), NULL));
+
+
+ return 0;
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]