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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>
* config.h.win32.in: Typo: GLIB_MICRO_VERSION and

View File

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

View File

@ -562,19 +562,68 @@ For example, g_strreverse ("abcdef") will result in "fedcba".
@Returns: the same pointer passed in as @string.
<!-- ##### 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>.
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), value));
</literal>
</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.
<!-- ##### FUNCTION g_ascii_strtod ##### -->
<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 ##### -->

View File

@ -39,6 +39,7 @@
#include <stdlib.h>
#include <string.h>
#include <locale.h>
#include <errno.h>
#include <ctype.h> /* For tolower() */
#if !defined (HAVE_STRSIGNAL) || !defined(NO_SYS_SIGLIST_DECL)
#include <signal.h>
@ -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)
{

View File

@ -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 */

View File

@ -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)

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;
}