summaryrefslogtreecommitdiffstats
path: root/lib/c_strtod.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/c_strtod.c')
-rw-r--r--lib/c_strtod.c109
1 files changed, 109 insertions, 0 deletions
diff --git a/lib/c_strtod.c b/lib/c_strtod.c
new file mode 100644
index 0000000..d25ee27
--- /dev/null
+++ b/lib/c_strtod.c
@@ -0,0 +1,109 @@
+/*
+ * Locale-independent strtod().
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ * Copyright (C) 2021 Karel Zak <kzak@redhat.com>
+ */
+#include "c.h"
+
+#include <locale.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "c_strtod.h"
+
+#ifdef __APPLE__
+# include <xlocale.h>
+#endif
+
+#if defined(HAVE_NEWLOCALE) && (defined(HAVE_STRTOD_L) || defined(HAVE_USELOCALE))
+# define USE_CLOCALE
+#endif
+
+#if defined(USE_CLOCALE)
+static volatile locale_t c_locale;
+
+static locale_t get_c_locale(void)
+{
+ if (!c_locale)
+ c_locale = newlocale(LC_ALL_MASK, "C", (locale_t) 0);
+ return c_locale;
+}
+#endif
+
+
+double c_strtod(char const *str, char **end)
+{
+ double res;
+ int errsv;
+
+#if defined(USE_CLOCALE)
+ locale_t cl = get_c_locale();
+
+#if defined(HAVE_STRTOD_L)
+ /*
+ * A) try strtod_l() for "C" locale
+ */
+ if (cl)
+ return strtod_l(str, end, cl);
+#elif defined(HAVE_USELOCALE)
+ /*
+ * B) classic strtod(), but switch to "C" locale by uselocal()
+ */
+ if (cl) {
+ locale_t org_cl = uselocale(locale);
+ if (!org_cl)
+ return 0;
+
+ res = strtod(str, end);
+ errsv = errno;
+
+ uselocale(org_cl);
+ errno = errsv;
+ return res;
+ }
+#endif /* HAVE_USELOCALE */
+#endif /* USE_CLOCALE */
+ /*
+ * C) classic strtod(), but switch to "C" locale by setlocale()
+ */
+ char *org_locale = setlocale(LC_NUMERIC, NULL);
+
+ if (org_locale) {
+ org_locale = strdup(org_locale);
+ if (!org_locale)
+ return 0;
+
+ setlocale(LC_NUMERIC, "C");
+ }
+ res = strtod(str, end);
+ errsv = errno;
+
+ if (org_locale) {
+ setlocale(LC_NUMERIC, org_locale);
+ free(org_locale);
+ }
+ errno = errsv;
+ return res;
+}
+
+#ifdef TEST_PROGRAM
+int main(int argc, char *argv[])
+{
+ double res;
+ char *end;
+
+ if (argc < 2) {
+ fprintf(stderr, "usage: %s decimal.number\n",
+ program_invocation_short_name);
+ return EXIT_FAILURE;
+ }
+
+ res = c_strtod(argv[1], &end);
+ printf("Result: %g, errno: %d, endptr: '%s'\n", res, errno, end);
+
+ return errno ? EXIT_FAILURE : EXIT_SUCCESS;
+}
+#endif