Skip to content

Commit 725bfd8

Browse files
committed
Issue python#5914: Add new C-API function PyOS_string_to_double, to complement
PyOS_double_to_string, and deprecate PyOS_ascii_strtod and PyOS_ascii_atof.
1 parent 75930f8 commit 725bfd8

10 files changed

Lines changed: 247 additions & 90 deletions

File tree

Doc/c-api/conversion.rst

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,43 @@ The following functions provide locale-independent string to number conversions.
6262

6363
See the Unix man page :manpage:`strtod(2)` for details.
6464

65+
.. deprecated:: 3.1
66+
Use :cfunc:`PyOS_string_to_double` instead.
67+
68+
69+
.. cfunction:: double PyOS_string_to_double(const char *s, char **endptr, PyObject *overflow_exception)
70+
71+
Convert a string ``s`` to a :ctype:`double`, raising a Python
72+
exception on failure. The set of accepted strings corresponds to
73+
the set of strings accepted by Python's :func:`float` constructor,
74+
except that ``s`` must not have leading or trailing whitespace.
75+
The conversion is independent of the current locale.
76+
77+
If ``endptr`` is ``NULL``, convert the whole string. Raise
78+
ValueError and return ``-1.0`` if the string is not a valid
79+
representation of a floating-point number.
80+
81+
If endptr is not ``NULL``, convert as much of the string as
82+
possible and set ``*endptr`` to point to the first unconverted
83+
character. If no initial segment of the string is the valid
84+
representation of a floating-point number, set ``*endptr`` to point
85+
to the beginning of the string, raise ValueError, and return
86+
``-1.0``.
87+
88+
If ``s`` represents a value that is too large to store in a float
89+
(for example, ``"1e500"`` is such a string on many platforms) then
90+
if ``overflow_exception`` is ``NULL`` return ``Py_HUGE_VAL`` (with
91+
an appropriate sign) and don't set any exception. Otherwise,
92+
``overflow_exception`` must point to a Python exception object;
93+
raise that exception and return ``-1.0``. In both cases, set
94+
``*endptr`` to point to the first character after the converted value.
95+
96+
If any other error occurs during the conversion (for example an
97+
out-of-memory error), set the appropriate Python exception and
98+
return ``-1.0``.
99+
100+
.. versionadded:: 3.1
101+
65102

66103
.. cfunction:: char* PyOS_ascii_formatd(char *buffer, size_t buf_len, const char *format, double d)
67104

@@ -117,6 +154,9 @@ The following functions provide locale-independent string to number conversions.
117154

118155
See the Unix man page :manpage:`atof(2)` for details.
119156

157+
.. deprecated:: 3.1
158+
Use PyOS_string_to_double instead.
159+
120160

121161
.. cfunction:: char* PyOS_stricmp(char *s1, char *s2)
122162

Include/pystrtod.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ extern "C" {
99
PyAPI_FUNC(double) PyOS_ascii_strtod(const char *str, char **ptr);
1010
PyAPI_FUNC(double) PyOS_ascii_atof(const char *str);
1111
PyAPI_FUNC(char *) PyOS_ascii_formatd(char *buffer, size_t buf_len, const char *format, double d);
12+
PyAPI_FUNC(double) PyOS_string_to_double(const char *str,
13+
char **endptr,
14+
PyObject *overflow_exception);
1215

1316
/* The caller is responsible for calling PyMem_Free to free the buffer
1417
that's is returned. */

Modules/_pickle.c

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2971,20 +2971,20 @@ load_float(UnpicklerObject *self)
29712971
return bad_readline();
29722972

29732973
errno = 0;
2974-
d = PyOS_ascii_strtod(s, &endptr);
2975-
2976-
if ((errno == ERANGE && !(fabs(d) <= 1.0)) ||
2977-
(endptr[0] != '\n') || (endptr[1] != '\0')) {
2974+
d = PyOS_string_to_double(s, &endptr, PyExc_OverflowError);
2975+
if (d == -1.0 && PyErr_Occurred())
2976+
return -1;
2977+
if ((endptr[0] != '\n') || (endptr[1] != '\0')) {
29782978
PyErr_SetString(PyExc_ValueError, "could not convert string to float");
29792979
return -1;
29802980
}
2981-
2982-
if ((value = PyFloat_FromDouble(d)) == NULL)
2981+
value = PyFloat_FromDouble(d);
2982+
if (value == NULL)
29832983
return -1;
29842984

29852985
PDATA_PUSH(self->stack, value, -1);
29862986
return 0;
2987-
}
2987+
}
29882988

29892989
static int
29902990
load_binfloat(UnpicklerObject *self)

Modules/_testcapimodule.c

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1045,6 +1045,54 @@ test_with_docstring(PyObject *self)
10451045
Py_RETURN_NONE;
10461046
}
10471047

1048+
/* Test PyOS_string_to_double. */
1049+
static PyObject *
1050+
test_string_to_double(PyObject *self) {
1051+
double result;
1052+
char *msg;
1053+
1054+
#define CHECK_STRING(STR, expected) \
1055+
result = PyOS_string_to_double(STR, NULL, NULL); \
1056+
if (result == -1.0 && PyErr_Occurred()) \
1057+
return NULL; \
1058+
if (result != expected) { \
1059+
msg = "conversion of " STR " to float failed"; \
1060+
goto fail; \
1061+
}
1062+
1063+
#define CHECK_INVALID(STR) \
1064+
result = PyOS_string_to_double(STR, NULL, NULL); \
1065+
if (result == -1.0 && PyErr_Occurred()) { \
1066+
if (PyErr_ExceptionMatches(PyExc_ValueError)) \
1067+
PyErr_Clear(); \
1068+
else \
1069+
return NULL; \
1070+
} \
1071+
else { \
1072+
msg = "conversion of " STR " didn't raise ValueError"; \
1073+
goto fail; \
1074+
}
1075+
1076+
CHECK_STRING("0.1", 0.1);
1077+
CHECK_STRING("1.234", 1.234);
1078+
CHECK_STRING("-1.35", -1.35);
1079+
CHECK_STRING(".1e01", 1.0);
1080+
CHECK_STRING("2.e-2", 0.02);
1081+
1082+
CHECK_INVALID(" 0.1");
1083+
CHECK_INVALID("\t\n-3");
1084+
CHECK_INVALID(".123 ");
1085+
CHECK_INVALID("3\n");
1086+
CHECK_INVALID("123abc");
1087+
1088+
Py_RETURN_NONE;
1089+
fail:
1090+
return raiseTestError("test_string_to_double", msg);
1091+
#undef CHECK_STRING
1092+
#undef CHECK_INVALID
1093+
}
1094+
1095+
10481096
#ifdef HAVE_GETTIMEOFDAY
10491097
/* Profiling of integer performance */
10501098
static void print_delta(int test, struct timeval *s, struct timeval *e)
@@ -1223,6 +1271,7 @@ static PyMethodDef TestMethods[] = {
12231271
{"test_empty_argparse", (PyCFunction)test_empty_argparse,METH_NOARGS},
12241272
{"test_null_strings", (PyCFunction)test_null_strings, METH_NOARGS},
12251273
{"test_string_from_format", (PyCFunction)test_string_from_format, METH_NOARGS},
1274+
{"test_string_to_double", (PyCFunction)test_string_to_double, METH_NOARGS},
12261275
{"test_with_docstring", (PyCFunction)test_with_docstring, METH_NOARGS,
12271276
PyDoc_STR("This is a pretty normal docstring.")},
12281277

Objects/complexobject.c

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -799,25 +799,26 @@ complex_subtype_from_string(PyTypeObject *type, PyObject *v)
799799
*/
800800

801801
/* first look for forms starting with <float> */
802-
errno = 0;
803-
z = PyOS_ascii_strtod(s, &end);
804-
if (end == s && errno == ENOMEM)
805-
return PyErr_NoMemory();
806-
if (errno == ERANGE && fabs(z) >= 1.0)
807-
goto overflow;
808-
802+
z = PyOS_string_to_double(s, &end, PyExc_OverflowError);
803+
if (z == -1.0 && PyErr_Occurred()) {
804+
if (PyErr_ExceptionMatches(PyExc_ValueError))
805+
PyErr_Clear();
806+
else
807+
return NULL;
808+
}
809809
if (end != s) {
810810
/* all 4 forms starting with <float> land here */
811811
s = end;
812812
if (*s == '+' || *s == '-') {
813813
/* <float><signed-float>j | <float><sign>j */
814814
x = z;
815-
errno = 0;
816-
y = PyOS_ascii_strtod(s, &end);
817-
if (end == s && errno == ENOMEM)
818-
return PyErr_NoMemory();
819-
if (errno == ERANGE && fabs(y) >= 1.0)
820-
goto overflow;
815+
y = PyOS_string_to_double(s, &end, PyExc_OverflowError);
816+
if (y == -1.0 && PyErr_Occurred()) {
817+
if (PyErr_ExceptionMatches(PyExc_ValueError))
818+
PyErr_Clear();
819+
else
820+
return NULL;
821+
}
821822
if (end != s)
822823
/* <float><signed-float>j */
823824
s = end;
@@ -877,11 +878,6 @@ complex_subtype_from_string(PyTypeObject *type, PyObject *v)
877878
PyErr_SetString(PyExc_ValueError,
878879
"complex() arg is a malformed string");
879880
return NULL;
880-
881-
overflow:
882-
PyErr_SetString(PyExc_OverflowError,
883-
"complex() arg overflow");
884-
return NULL;
885881
}
886882

887883
static PyObject *

Objects/floatobject.c

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -193,36 +193,20 @@ PyFloat_FromString(PyObject *v)
193193
/* We don't care about overflow or underflow. If the platform
194194
* supports them, infinities and signed zeroes (on underflow) are
195195
* fine. */
196-
errno = 0;
197-
PyFPE_START_PROTECT("strtod", goto error)
198-
x = PyOS_ascii_strtod(s, (char **)&end);
199-
PyFPE_END_PROTECT(x)
200-
if (end == s) {
201-
if (errno == ENOMEM)
202-
PyErr_NoMemory();
203-
else {
204-
PyOS_snprintf(buffer, sizeof(buffer),
205-
"invalid literal for float(): %.200s", s);
206-
PyErr_SetString(PyExc_ValueError, buffer);
207-
}
196+
x = PyOS_string_to_double(s, (char **)&end, NULL);
197+
if (x == -1.0 && PyErr_Occurred())
208198
goto error;
209-
}
210-
/* Since end != s, the platform made *some* kind of sense out
211-
of the input. Trust it. */
212199
while (*end && isspace(Py_CHARMASK(*end)))
213200
end++;
214-
if (end != last) {
215-
if (*end == '\0')
216-
PyErr_SetString(PyExc_ValueError,
217-
"null byte in argument for float()");
218-
else {
219-
PyOS_snprintf(buffer, sizeof(buffer),
220-
"invalid literal for float(): %.200s", s);
221-
PyErr_SetString(PyExc_ValueError, buffer);
222-
}
223-
goto error;
201+
if (end == last)
202+
result = PyFloat_FromDouble(x);
203+
else {
204+
PyOS_snprintf(buffer, sizeof(buffer),
205+
"invalid literal for float(): %.200s", s);
206+
PyErr_SetString(PyExc_ValueError, buffer);
207+
result = NULL;
224208
}
225-
result = PyFloat_FromDouble(x);
209+
226210
error:
227211
if (s_buffer)
228212
PyMem_FREE(s_buffer);

Python/ast.c

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3162,18 +3162,18 @@ parsenumber(struct compiling *c, const char *s)
31623162
#ifndef WITHOUT_COMPLEX
31633163
if (imflag) {
31643164
compl.real = 0.;
3165-
PyFPE_START_PROTECT("atof", return 0)
3166-
compl.imag = PyOS_ascii_atof(s);
3167-
PyFPE_END_PROTECT(c)
3168-
return PyComplex_FromCComplex(compl);
3165+
compl.imag = PyOS_string_to_double(s, (char **)&end, NULL);
3166+
if (compl.imag == -1.0 && PyErr_Occurred())
3167+
return NULL;
3168+
return PyComplex_FromCComplex(compl);
31693169
}
31703170
else
31713171
#endif
31723172
{
3173-
PyFPE_START_PROTECT("atof", return 0)
3174-
dx = PyOS_ascii_atof(s);
3175-
PyFPE_END_PROTECT(dx)
3176-
return PyFloat_FromDouble(dx);
3173+
dx = PyOS_string_to_double(s, NULL, NULL);
3174+
if (dx == -1.0 && PyErr_Occurred())
3175+
return NULL;
3176+
return PyFloat_FromDouble(dx);
31773177
}
31783178
}
31793179

Python/dtoa.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@
6161
* that hasn't been MALLOC'ed, private_mem should only be used when k <=
6262
* Kmax.
6363
*
64+
* 7. _Py_dg_strtod has been modified so that it doesn't accept strings with
65+
* leading whitespace.
66+
*
6467
***************************************************************/
6568

6669
/* Please send bug reports for the original dtoa.c code to David M. Gay (dmg
@@ -1355,13 +1358,15 @@ _Py_dg_strtod(const char *s00, char **se)
13551358
/* no break */
13561359
case 0:
13571360
goto ret0;
1361+
/* modify original dtoa.c so that it doesn't accept leading whitespace
13581362
case '\t':
13591363
case '\n':
13601364
case '\v':
13611365
case '\f':
13621366
case '\r':
13631367
case ' ':
13641368
continue;
1369+
*/
13651370
default:
13661371
goto break2;
13671372
}

Python/marshal.c

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -670,18 +670,17 @@ r_object(RFILE *p)
670670
{
671671
char buf[256];
672672
double dx;
673+
retval = NULL;
673674
n = r_byte(p);
674675
if (n == EOF || r_string(buf, (int)n, p) != n) {
675676
PyErr_SetString(PyExc_EOFError,
676677
"EOF read where object expected");
677-
retval = NULL;
678678
break;
679679
}
680680
buf[n] = '\0';
681-
retval = NULL;
682-
PyFPE_START_PROTECT("atof", break)
683-
dx = PyOS_ascii_atof(buf);
684-
PyFPE_END_PROTECT(dx)
681+
dx = PyOS_string_to_double(buf, NULL, NULL);
682+
if (dx == -1.0 && PyErr_Occurred())
683+
break;
685684
retval = PyFloat_FromDouble(dx);
686685
break;
687686
}
@@ -710,29 +709,27 @@ r_object(RFILE *p)
710709
{
711710
char buf[256];
712711
Py_complex c;
712+
retval = NULL;
713713
n = r_byte(p);
714714
if (n == EOF || r_string(buf, (int)n, p) != n) {
715715
PyErr_SetString(PyExc_EOFError,
716716
"EOF read where object expected");
717-
retval = NULL;
718717
break;
719718
}
720719
buf[n] = '\0';
721-
retval = NULL;
722-
PyFPE_START_PROTECT("atof", break;)
723-
c.real = PyOS_ascii_atof(buf);
724-
PyFPE_END_PROTECT(c)
720+
c.real = PyOS_string_to_double(buf, NULL, NULL);
721+
if (c.real == -1.0 && PyErr_Occurred())
722+
break;
725723
n = r_byte(p);
726724
if (n == EOF || r_string(buf, (int)n, p) != n) {
727725
PyErr_SetString(PyExc_EOFError,
728726
"EOF read where object expected");
729-
retval = NULL;
730727
break;
731728
}
732729
buf[n] = '\0';
733-
PyFPE_START_PROTECT("atof", break)
734-
c.imag = PyOS_ascii_atof(buf);
735-
PyFPE_END_PROTECT(c)
730+
c.imag = PyOS_string_to_double(buf, NULL, NULL);
731+
if (c.imag == -1.0 && PyErr_Occurred())
732+
break;
736733
retval = PyComplex_FromCComplex(c);
737734
break;
738735
}

0 commit comments

Comments
 (0)