]>
Commit | Line | Data |
---|---|---|
670b10ae KZ |
1 | /* |
2 | * Locale-independent strtod(). | |
3 | * | |
4 | * This file may be redistributed under the terms of the | |
5 | * GNU Lesser General Public License. | |
6 | * | |
7 | * Copyright (C) 2021 Karel Zak <kzak@redhat.com> | |
8 | */ | |
9 | #include "c.h" | |
10 | ||
11 | #include <locale.h> | |
12 | #include <stdlib.h> | |
13 | #include <string.h> | |
14 | ||
15 | #include "c_strtod.h" | |
16 | ||
dc6a38ed KZ |
17 | #ifdef __APPLE__ |
18 | # include <xlocale.h> | |
19 | #endif | |
20 | ||
670b10ae KZ |
21 | #if defined(HAVE_NEWLOCALE) && (defined(HAVE_STRTOD_L) || defined(HAVE_USELOCALE)) |
22 | # define USE_CLOCALE | |
23 | #endif | |
24 | ||
25 | #if defined(USE_CLOCALE) | |
26 | static volatile locale_t c_locale; | |
27 | ||
28 | static locale_t get_c_locale(void) | |
29 | { | |
30 | if (!c_locale) | |
31 | c_locale = newlocale(LC_ALL_MASK, "C", (locale_t) 0); | |
32 | return c_locale; | |
33 | } | |
34 | #endif | |
35 | ||
36 | ||
37 | double c_strtod(char const *str, char **end) | |
38 | { | |
39 | double res; | |
40 | int errsv; | |
41 | ||
42 | #if defined(USE_CLOCALE) | |
43 | locale_t cl = get_c_locale(); | |
44 | ||
45 | #if defined(HAVE_STRTOD_L) | |
46 | /* | |
47 | * A) try strtod_l() for "C" locale | |
48 | */ | |
49 | if (cl) | |
50 | return strtod_l(str, end, cl); | |
51 | #elif defined(HAVE_USELOCALE) | |
52 | /* | |
53 | * B) classic strtod(), but switch to "C" locale by uselocal() | |
54 | */ | |
55 | if (cl) { | |
56 | locale_t org_cl = uselocale(locale); | |
57 | if (!org_cl) | |
58 | return 0; | |
59 | ||
60 | res = strtod(str, end); | |
61 | errsv = errno; | |
62 | ||
63 | uselocale(org_cl); | |
64 | errno = errsv; | |
65 | return res; | |
66 | } | |
67 | #endif /* HAVE_USELOCALE */ | |
68 | #endif /* USE_CLOCALE */ | |
69 | /* | |
70 | * C) classic strtod(), but switch to "C" locale by setlocale() | |
71 | */ | |
72 | char *org_locale = setlocale(LC_NUMERIC, NULL); | |
73 | ||
74 | if (org_locale) { | |
75 | org_locale = strdup(org_locale); | |
76 | if (!org_locale) | |
77 | return 0; | |
78 | ||
79 | setlocale(LC_NUMERIC, "C"); | |
80 | } | |
81 | res = strtod(str, end); | |
82 | errsv = errno; | |
83 | ||
84 | if (org_locale) { | |
85 | setlocale(LC_NUMERIC, org_locale); | |
86 | free(org_locale); | |
87 | } | |
88 | errno = errsv; | |
89 | return res; | |
90 | } | |
91 | ||
92 | #ifdef TEST_PROGRAM | |
93 | int main(int argc, char *argv[]) | |
94 | { | |
95 | double res; | |
96 | char *end; | |
97 | ||
98 | if (argc < 2) { | |
99 | fprintf(stderr, "usage: %s decimal.number\n", | |
100 | program_invocation_short_name); | |
101 | return EXIT_FAILURE; | |
102 | } | |
103 | ||
104 | res = c_strtod(argv[1], &end); | |
105 | printf("Result: %g, errno: %d, endptr: '%s'\n", res, errno, end); | |
106 | ||
107 | return errno ? EXIT_FAILURE : EXIT_SUCCESS; | |
108 | } | |
109 | #endif |