From: Alex Larsson Date: Wed, 24 Oct 2001 18:00:11 +0000 (+0000) Subject: Add g_strtod & co. X-Git-Tag: GLIB_1_3_10~7 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=3c39c8fcd01dd9929477488ac9dd713fd0311e9b;p=platform%2Fupstream%2Fglib.git Add g_strtod & co. 2001-10-24 Alex Larsson * docs/reference/glib/glib-sections.txt: Add g_strtod & co. * docs/reference/glib/tmpl/string_utils.sgml: Add docs for G_ASCII_DTOSTR_BUF_SIZE. * glib/gstrfuncs.[ch]: Added g_ascii_strtod, g_ascii_dtostr and g_ascii_formatd. * tests/Makefile.am: * tests/strtod-test.c: Add tests for g_ascii_strtod & co. --- diff --git a/ChangeLog b/ChangeLog index 97a5475..0817d37 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,18 @@ +2001-10-24 Alex Larsson + + * docs/reference/glib/glib-sections.txt: + Add g_strtod & co. + + * docs/reference/glib/tmpl/string_utils.sgml: + Add docs for G_ASCII_DTOSTR_BUF_SIZE. + + * glib/gstrfuncs.[ch]: + Added g_ascii_strtod, g_ascii_dtostr and g_ascii_formatd. + + * tests/Makefile.am: + * tests/strtod-test.c: + Add tests for g_ascii_strtod & co. + 2001-10-23 Tor Lillqvist * config.h.win32.in: Typo: GLIB_MICRO_VERSION and diff --git a/ChangeLog.pre-2-0 b/ChangeLog.pre-2-0 index 97a5475..0817d37 100644 --- a/ChangeLog.pre-2-0 +++ b/ChangeLog.pre-2-0 @@ -1,3 +1,18 @@ +2001-10-24 Alex Larsson + + * docs/reference/glib/glib-sections.txt: + Add g_strtod & co. + + * docs/reference/glib/tmpl/string_utils.sgml: + Add docs for G_ASCII_DTOSTR_BUF_SIZE. + + * glib/gstrfuncs.[ch]: + Added g_ascii_strtod, g_ascii_dtostr and g_ascii_formatd. + + * tests/Makefile.am: + * tests/strtod-test.c: + Add tests for g_ascii_strtod & co. + 2001-10-23 Tor Lillqvist * config.h.win32.in: Typo: GLIB_MICRO_VERSION and diff --git a/ChangeLog.pre-2-10 b/ChangeLog.pre-2-10 index 97a5475..0817d37 100644 --- a/ChangeLog.pre-2-10 +++ b/ChangeLog.pre-2-10 @@ -1,3 +1,18 @@ +2001-10-24 Alex Larsson + + * docs/reference/glib/glib-sections.txt: + Add g_strtod & co. + + * docs/reference/glib/tmpl/string_utils.sgml: + Add docs for G_ASCII_DTOSTR_BUF_SIZE. + + * glib/gstrfuncs.[ch]: + Added g_ascii_strtod, g_ascii_dtostr and g_ascii_formatd. + + * tests/Makefile.am: + * tests/strtod-test.c: + Add tests for g_ascii_strtod & co. + 2001-10-23 Tor Lillqvist * config.h.win32.in: Typo: GLIB_MICRO_VERSION and diff --git a/ChangeLog.pre-2-12 b/ChangeLog.pre-2-12 index 97a5475..0817d37 100644 --- a/ChangeLog.pre-2-12 +++ b/ChangeLog.pre-2-12 @@ -1,3 +1,18 @@ +2001-10-24 Alex Larsson + + * docs/reference/glib/glib-sections.txt: + Add g_strtod & co. + + * docs/reference/glib/tmpl/string_utils.sgml: + Add docs for G_ASCII_DTOSTR_BUF_SIZE. + + * glib/gstrfuncs.[ch]: + Added g_ascii_strtod, g_ascii_dtostr and g_ascii_formatd. + + * tests/Makefile.am: + * tests/strtod-test.c: + Add tests for g_ascii_strtod & co. + 2001-10-23 Tor Lillqvist * config.h.win32.in: Typo: GLIB_MICRO_VERSION and diff --git a/ChangeLog.pre-2-2 b/ChangeLog.pre-2-2 index 97a5475..0817d37 100644 --- a/ChangeLog.pre-2-2 +++ b/ChangeLog.pre-2-2 @@ -1,3 +1,18 @@ +2001-10-24 Alex Larsson + + * docs/reference/glib/glib-sections.txt: + Add g_strtod & co. + + * docs/reference/glib/tmpl/string_utils.sgml: + Add docs for G_ASCII_DTOSTR_BUF_SIZE. + + * glib/gstrfuncs.[ch]: + Added g_ascii_strtod, g_ascii_dtostr and g_ascii_formatd. + + * tests/Makefile.am: + * tests/strtod-test.c: + Add tests for g_ascii_strtod & co. + 2001-10-23 Tor Lillqvist * config.h.win32.in: Typo: GLIB_MICRO_VERSION and diff --git a/ChangeLog.pre-2-4 b/ChangeLog.pre-2-4 index 97a5475..0817d37 100644 --- a/ChangeLog.pre-2-4 +++ b/ChangeLog.pre-2-4 @@ -1,3 +1,18 @@ +2001-10-24 Alex Larsson + + * docs/reference/glib/glib-sections.txt: + Add g_strtod & co. + + * docs/reference/glib/tmpl/string_utils.sgml: + Add docs for G_ASCII_DTOSTR_BUF_SIZE. + + * glib/gstrfuncs.[ch]: + Added g_ascii_strtod, g_ascii_dtostr and g_ascii_formatd. + + * tests/Makefile.am: + * tests/strtod-test.c: + Add tests for g_ascii_strtod & co. + 2001-10-23 Tor Lillqvist * config.h.win32.in: Typo: GLIB_MICRO_VERSION and diff --git a/ChangeLog.pre-2-6 b/ChangeLog.pre-2-6 index 97a5475..0817d37 100644 --- a/ChangeLog.pre-2-6 +++ b/ChangeLog.pre-2-6 @@ -1,3 +1,18 @@ +2001-10-24 Alex Larsson + + * docs/reference/glib/glib-sections.txt: + Add g_strtod & co. + + * docs/reference/glib/tmpl/string_utils.sgml: + Add docs for G_ASCII_DTOSTR_BUF_SIZE. + + * glib/gstrfuncs.[ch]: + Added g_ascii_strtod, g_ascii_dtostr and g_ascii_formatd. + + * tests/Makefile.am: + * tests/strtod-test.c: + Add tests for g_ascii_strtod & co. + 2001-10-23 Tor Lillqvist * config.h.win32.in: Typo: GLIB_MICRO_VERSION and diff --git a/ChangeLog.pre-2-8 b/ChangeLog.pre-2-8 index 97a5475..0817d37 100644 --- a/ChangeLog.pre-2-8 +++ b/ChangeLog.pre-2-8 @@ -1,3 +1,18 @@ +2001-10-24 Alex Larsson + + * docs/reference/glib/glib-sections.txt: + Add g_strtod & co. + + * docs/reference/glib/tmpl/string_utils.sgml: + Add docs for G_ASCII_DTOSTR_BUF_SIZE. + + * glib/gstrfuncs.[ch]: + Added g_ascii_strtod, g_ascii_dtostr and g_ascii_formatd. + + * tests/Makefile.am: + * tests/strtod-test.c: + Add tests for g_ascii_strtod & co. + 2001-10-23 Tor Lillqvist * config.h.win32.in: Typo: GLIB_MICRO_VERSION and diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt index d42467a..34d5112 100644 --- a/docs/reference/glib/glib-sections.txt +++ b/docs/reference/glib/glib-sections.txt @@ -922,6 +922,12 @@ g_strncasecmp g_strreverse + + +G_ASCII_DTOSTR_BUF_SIZE +g_ascii_strtod +g_ascii_dtostr +g_ascii_formatd g_strtod diff --git a/docs/reference/glib/tmpl/string_utils.sgml b/docs/reference/glib/tmpl/string_utils.sgml index 065b0b4..0135b3b 100644 --- a/docs/reference/glib/tmpl/string_utils.sgml +++ b/docs/reference/glib/tmpl/string_utils.sgml @@ -562,19 +562,68 @@ For example, g_strreverse ("abcdef") will result in "fedcba". @Returns: the same pointer passed in as @string. + + +A good size for a buffer to be passed into g_ascii_dtostr. +It is guaranteed to be enough for all output of that function on systems with + 64bit IEEE compatible doubles. + + +The typical usage would be something like: + + + + char buf[G_ASCII_DTOSTR_BUF_SIZE]; + + fprintf (out, "value=%s\n", g_ascii_dtostr (buf, sizeof (buf), value)); + + + + + + + + + + +@nptr: +@endptr: +@Returns: + + + + + + + +@buffer: +@buf_len: +@d: +@Returns: + +@format: + + + + + + + +@buffer: +@buf_len: +@format: +@d: +@Returns: + + -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 in the "C" locale, and returns the best -match. + -@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. +@nptr: +@endptr: +@Returns: diff --git a/glib/gstrfuncs.c b/glib/gstrfuncs.c index d05360a..e53f59d 100644 --- a/glib/gstrfuncs.c +++ b/glib/gstrfuncs.c @@ -39,6 +39,7 @@ #include #include #include +#include #include /* For tolower() */ #if !defined (HAVE_STRSIGNAL) || !defined(NO_SYS_SIGLIST_DECL) #include @@ -253,9 +254,29 @@ 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. + * + * This function should seldom be used. The normal situation when reading + * numbers not for human consumption is to use @g_ascii_strtod(). Only when + * you know that you must expect both locale formated and C formated numbers + * should you use this. Make sure that you don't pass strings such as comma + * separated lists of values, since the commas may be interpreted as a decimal + * point in some locales, causing unexpected results. + * + * 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 +291,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) { @@ -294,6 +307,278 @@ g_strtod (const gchar *nptr, } } +/** + * 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. + * + * If the correct value would cause overflow, plus or minus HUGE_VAL + * is returned (according to the sign of the value), and ERANGE is + * stored in errno. If the correct value would cause underflow, + * zero is returned and ERANGE is stored in errno. + * + * This function resets errno before calling strtod() so that + * you can reliably detect overflow and underflow. + * + * 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 */ + } + + /* Set errno to zero, so that we can distinguish zero results + and underflows */ + errno = 0; + + 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. + * @d: The double to convert + * + * Converts a double to a string, using the '.' as + * decimal_point. + * + * This functions generates enough precision that converting + * the string back using @g_strtod gives the same machine-number + * (on machines with IEEE compatible 64bit doubles). 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, + gdouble d) +{ + return g_ascii_formatd (buffer, buf_len, "%.17g", d); +} + +/** + * g_ascii_formatd: + * @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 just want to want to serialize the value into a + * string, use @g_ascii_dtostr. + * + * Return value: The pointer to the buffer with the converted string. + **/ +gchar * +g_ascii_formatd (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) { diff --git a/glib/gstrfuncs.h b/glib/gstrfuncs.h index 7aedde1..0c4f59e 100644 --- a/glib/gstrfuncs.h +++ b/glib/gstrfuncs.h @@ -93,13 +93,11 @@ gint g_ascii_xdigit_value (gchar c) G_GNUC_CONST; */ #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); @@ -118,6 +116,24 @@ 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. + * Then add 10 for good measure */ +#define G_ASCII_DTOSTR_BUF_SIZE (29 + 10) +gchar * g_ascii_dtostr (gchar *buffer, + gint buf_len, + gdouble d); +gchar * g_ascii_formatd (gchar *buffer, + gint buf_len, + const gchar *format, + gdouble d); + /* removes leading spaces */ gchar* g_strchug (gchar *string); /* removes trailing spaces */ diff --git a/tests/Makefile.am b/tests/Makefile.am index 5a2910d..5160be6 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -72,6 +72,7 @@ test_programs = \ spawn-test \ strfunc-test \ string-test \ + strtod-test \ thread-test \ threadpool-test \ tree-test \ @@ -114,6 +115,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) diff --git a/tests/strtod-test.c b/tests/strtod-test.c new file mode 100644 index 0000000..bdb7017 --- /dev/null +++ b/tests/strtod-test.c @@ -0,0 +1,53 @@ +#include +#include +#include +#include + +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), d), NULL)); + + d = -179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0; + g_assert (d == g_ascii_strtod (g_ascii_dtostr (buffer, sizeof (buffer), d), NULL)); + + d = pow (2.0, -1024.1); + g_assert (d == g_ascii_strtod (g_ascii_dtostr (buffer, sizeof (buffer), d), NULL)); + + d = -pow (2.0, -1024.1); + g_assert (d == g_ascii_strtod (g_ascii_dtostr (buffer, sizeof (buffer), d), NULL)); + + + return 0; +}