New g_ascii_strtod/g_ascii_dtostr() patch



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]