Add g_strtod & co.

2001-10-24  Alex Larsson  <alexl@redhat.com>

	* 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.
This commit is contained in:
Alex Larsson 2001-10-24 18:00:11 +00:00 committed by Alexander Larsson
parent b0facb3863
commit 3c39c8fcd0
14 changed files with 557 additions and 26 deletions

View File

@ -1,3 +1,18 @@
2001-10-24 Alex Larsson <alexl@redhat.com>
* 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 <tml@iki.fi> 2001-10-23 Tor Lillqvist <tml@iki.fi>
* config.h.win32.in: Typo: GLIB_MICRO_VERSION and * config.h.win32.in: Typo: GLIB_MICRO_VERSION and

View File

@ -1,3 +1,18 @@
2001-10-24 Alex Larsson <alexl@redhat.com>
* 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 <tml@iki.fi> 2001-10-23 Tor Lillqvist <tml@iki.fi>
* config.h.win32.in: Typo: GLIB_MICRO_VERSION and * config.h.win32.in: Typo: GLIB_MICRO_VERSION and

View File

@ -1,3 +1,18 @@
2001-10-24 Alex Larsson <alexl@redhat.com>
* 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 <tml@iki.fi> 2001-10-23 Tor Lillqvist <tml@iki.fi>
* config.h.win32.in: Typo: GLIB_MICRO_VERSION and * config.h.win32.in: Typo: GLIB_MICRO_VERSION and

View File

@ -1,3 +1,18 @@
2001-10-24 Alex Larsson <alexl@redhat.com>
* 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 <tml@iki.fi> 2001-10-23 Tor Lillqvist <tml@iki.fi>
* config.h.win32.in: Typo: GLIB_MICRO_VERSION and * config.h.win32.in: Typo: GLIB_MICRO_VERSION and

View File

@ -1,3 +1,18 @@
2001-10-24 Alex Larsson <alexl@redhat.com>
* 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 <tml@iki.fi> 2001-10-23 Tor Lillqvist <tml@iki.fi>
* config.h.win32.in: Typo: GLIB_MICRO_VERSION and * config.h.win32.in: Typo: GLIB_MICRO_VERSION and

View File

@ -1,3 +1,18 @@
2001-10-24 Alex Larsson <alexl@redhat.com>
* 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 <tml@iki.fi> 2001-10-23 Tor Lillqvist <tml@iki.fi>
* config.h.win32.in: Typo: GLIB_MICRO_VERSION and * config.h.win32.in: Typo: GLIB_MICRO_VERSION and

View File

@ -1,3 +1,18 @@
2001-10-24 Alex Larsson <alexl@redhat.com>
* 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 <tml@iki.fi> 2001-10-23 Tor Lillqvist <tml@iki.fi>
* config.h.win32.in: Typo: GLIB_MICRO_VERSION and * config.h.win32.in: Typo: GLIB_MICRO_VERSION and

View File

@ -1,3 +1,18 @@
2001-10-24 Alex Larsson <alexl@redhat.com>
* 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 <tml@iki.fi> 2001-10-23 Tor Lillqvist <tml@iki.fi>
* config.h.win32.in: Typo: GLIB_MICRO_VERSION and * config.h.win32.in: Typo: GLIB_MICRO_VERSION and

View File

@ -922,6 +922,12 @@ g_strncasecmp
<SUBSECTION> <SUBSECTION>
g_strreverse g_strreverse
<SUBSECTION>
G_ASCII_DTOSTR_BUF_SIZE
g_ascii_strtod
g_ascii_dtostr
g_ascii_formatd
g_strtod g_strtod
<SUBSECTION> <SUBSECTION>

View File

@ -562,19 +562,68 @@ For example, g_strreverse ("abcdef") will result in "fedcba".
@Returns: the same pointer passed in as @string. @Returns: the same pointer passed in as @string.
<!-- ##### FUNCTION g_strtod ##### --> <!-- ##### MACRO G_ASCII_DTOSTR_BUF_SIZE ##### -->
<para> <para>
Converts a string to a gdouble value. A good size for a buffer to be passed into <function>g_ascii_dtostr</function>.
It calls the standard <function>strtod()</function> function It is guaranteed to be enough for all output of that function on systems with
to handle the conversion, but if the string is not completely converted 64bit IEEE compatible doubles.
it attempts the conversion again in the "C" locale, and returns the best </para>
match. <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), value));
</literal>
</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. <!-- ##### FUNCTION g_ascii_strtod ##### -->
@Returns: the gdouble value. <para>
</para>
@nptr:
@endptr:
@Returns:
<!-- ##### FUNCTION g_ascii_dtostr ##### -->
<para>
</para>
@buffer:
@buf_len:
@d:
@Returns:
<!-- # Unused Parameters # -->
@format:
<!-- ##### FUNCTION g_ascii_formatd ##### -->
<para>
</para>
@buffer:
@buf_len:
@format:
@d:
@Returns:
<!-- ##### FUNCTION g_strtod ##### -->
<para>
</para>
@nptr:
@endptr:
@Returns:
<!-- ##### FUNCTION g_strchug ##### --> <!-- ##### FUNCTION g_strchug ##### -->

View File

@ -39,6 +39,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <locale.h> #include <locale.h>
#include <errno.h>
#include <ctype.h> /* For tolower() */ #include <ctype.h> /* For tolower() */
#if !defined (HAVE_STRSIGNAL) || !defined(NO_SYS_SIGLIST_DECL) #if !defined (HAVE_STRSIGNAL) || !defined(NO_SYS_SIGLIST_DECL)
#include <signal.h> #include <signal.h>
@ -253,9 +254,29 @@ g_strconcat (const gchar *string1, ...)
return concat; 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 gdouble
g_strtod (const gchar *nptr, g_strtod (const gchar *nptr,
gchar **endptr) gchar **endptr)
{ {
gchar *fail_pos_1; gchar *fail_pos_1;
gchar *fail_pos_2; gchar *fail_pos_2;
@ -270,15 +291,7 @@ g_strtod (const gchar *nptr,
val_1 = strtod (nptr, &fail_pos_1); val_1 = strtod (nptr, &fail_pos_1);
if (fail_pos_1 && fail_pos_1[0] != 0) if (fail_pos_1 && fail_pos_1[0] != 0)
{ val_2 = g_ascii_strtod (nptr, &fail_pos_2);
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);
}
if (!fail_pos_1 || fail_pos_1[0] == 0 || fail_pos_1 >= 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_CONST_RETURN gchar*
g_strerror (gint errnum) g_strerror (gint errnum)
{ {

View File

@ -93,13 +93,11 @@ gint g_ascii_xdigit_value (gchar c) G_GNUC_CONST;
*/ */
#define G_STR_DELIMITERS "_-|> <." #define G_STR_DELIMITERS "_-|> <."
gchar* g_strdelimit (gchar *string, gchar* g_strdelimit (gchar *string,
const gchar *delimiters, const gchar *delimiters,
gchar new_delimiter); gchar new_delimiter);
gchar* g_strcanon (gchar *string, gchar* g_strcanon (gchar *string,
const gchar *valid_chars, const gchar *valid_chars,
gchar substitutor); gchar substitutor);
gdouble g_strtod (const gchar *nptr,
gchar **endptr);
G_CONST_RETURN gchar* g_strerror (gint errnum) G_GNUC_CONST; G_CONST_RETURN gchar* g_strerror (gint errnum) G_GNUC_CONST;
G_CONST_RETURN gchar* g_strsignal (gint signum) G_GNUC_CONST; G_CONST_RETURN gchar* g_strsignal (gint signum) G_GNUC_CONST;
gchar* g_strreverse (gchar *string); gchar* g_strreverse (gchar *string);
@ -118,6 +116,24 @@ gchar * g_strrstr_len (const gchar *haystack,
gssize haystack_len, gssize haystack_len,
const gchar *needle); 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 */ /* removes leading spaces */
gchar* g_strchug (gchar *string); gchar* g_strchug (gchar *string);
/* removes trailing spaces */ /* removes trailing spaces */

View File

@ -72,6 +72,7 @@ test_programs = \
spawn-test \ spawn-test \
strfunc-test \ strfunc-test \
string-test \ string-test \
strtod-test \
thread-test \ thread-test \
threadpool-test \ threadpool-test \
tree-test \ tree-test \
@ -114,6 +115,7 @@ slist_test_LDADD = $(progs_LDADD)
spawn_test_LDADD = $(progs_LDADD) spawn_test_LDADD = $(progs_LDADD)
strfunc_test_LDADD = $(progs_LDADD) strfunc_test_LDADD = $(progs_LDADD)
string_test_LDADD = $(progs_LDADD) string_test_LDADD = $(progs_LDADD)
strtod_test_LDADD = $(progs_LDADD) -lm
thread_test_LDADD = $(thread_LDADD) thread_test_LDADD = $(thread_LDADD)
threadpool_test_LDADD = $(thread_LDADD) threadpool_test_LDADD = $(thread_LDADD)
tree_test_LDADD = $(progs_LDADD) tree_test_LDADD = $(progs_LDADD)

53
tests/strtod-test.c Normal file
View File

@ -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), 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;
}