aboutsummaryrefslogtreecommitdiffstats
path: root/strtod.c
blob: fe731988754642da7b5d267e4df21274c7e8ffaf (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/*
 * Sane helper for 'strtod()'.
 *
 * Sad that we even need this, but the C library version has
 * insane locale behavior, and while the Qt "doDouble()" routines
 * are better in that regard, they don't have an end pointer
 * (having replaced it with the completely idiotic "ok" boolean
 * pointer instead).
 *
 * I wonder what drugs people are on sometimes.
 *
 * Right now we support the following flags to limit the
 * parsing some ways:
 *
 *   STRTOD_NO_SIGN	- don't accept signs
 *   STRTOD_NO_DOT	- no decimal dots, I'm European
 *   STRTOD_NO_COMMA	- no comma, please, I'm C locale
 *   STRTOD_NO_EXPONENT	- no exponent parsing, I'm human
 *
 * The "negative" flags are so that the common case can just
 * use a flag value of 0, and only if you have some special
 * requirements do you need to state those with explicit flags.
 *
 * So if you want the C locale kind of parsing, you'd use the
 * STRTOD_NO_COMMA flag to disallow a decimal comma. But if you
 * want a more relaxed "Hey, Europeans are people too, even if
 * they have locales with commas", just pass in a zero flag.
 */
#include <ctype.h>
#include "dive.h"

double strtod_flags(const char *str, const char **ptr, unsigned int flags)
{
	char c;
	const char *p = str, *ep;
	double val = 0.0;
	double decimal = 1.0;
	int sign = 0, esign = 0;
	int numbers = 0, dot = 0;

	/* skip spaces */
	while (isspace(c = *p++))
		/* */;

	/* optional sign */
	if (!(flags & STRTOD_NO_SIGN)) {
		switch (c) {
		case '-':
			sign = 1;
			/* fallthrough */
		case '+':
			c = *p++;
		}
	}

	/* Mantissa */
	for (;;c = *p++) {
		if ((c == '.' && !(flags & STRTOD_NO_DOT)) ||
		    (c == ',' && !(flags & STRTOD_NO_COMMA))) {
			if (dot)
				goto done;
			dot = 1;
			continue;
		}
		if (c >= '0' && c <= '9') {
			numbers++;
			if (dot) {
				decimal /= 10;
				val += (c - '0') * decimal;
			} else {
				val = (val * 10) + (c - '0');
			}
			continue;
		}
		if (c != 'e' && c != 'E')
			goto done;
		if (flags & STRTOD_NO_EXPONENT)
			goto done;
		break;
	}

	if (!numbers)
		goto done;

	/* Exponent */
	ep = p;
	c = *ep++;
	switch (c) {
	case '-':
		esign = 1;
		/* fallthrough */
	case '+':
		c = *ep++;
	}

	if (c >= '0' && c <= '9') {
		p = ep;
		int exponent = c - '0';

		for (;;) {
			c = *p++;
			if (c < '0' || c > '9')
				break;
			exponent *= 10;
			exponent += c - '0';
		}

		/* We're not going to bother playing games */
		if (exponent > 308)
			exponent = 308;

		while (exponent-- > 0) {
			if (esign)
				val /= 10;
			else
				val *= 10;
		}
	}

done:
	if (!numbers)
		goto no_conversion;
	if (ptr)
		*ptr = p-1;
	return sign ? -val : val;

no_conversion:
	if (ptr)
		*ptr = str;
	return 0.0;
}