Skip to main content

Python/pystrtod.c

Python/pystrtod.c provides locale-independent float parsing and formatting. The problem it solves: POSIX strtod and sprintf use whatever decimal separator the current locale specifies (. in C locale, , in many European locales). Python's float literals and repr must always use .. This file patches around that by fixing up the string before or after calling the system function.

Map

LinesSymbolRole
1–60includes / guardsFeature-test macros, dtoa.h guard for pre-3.11
61–180_PyOS_ascii_strtodLocale-fixed strtod: swap , to . before calling libc
181–300_PyOS_strtodThin public wrapper, calls _PyOS_ascii_strtod
301–420PyOS_string_to_doubleFull Python-level parse: handles inf, nan, overflow, ValueError
421–580_PyOS_double_to_stringFormat a double with a given format char (e, f, g, r, s)
581–680format_float_shortInternal helper: ensures at least one digit after the decimal point
681–780_Py_dg_strtod / _Py_dg_dtoaDavid Gay's dtoa shim (compiled in if PY_NO_SHORT_FLOAT_REPR is set)
781–900PyOS_double_to_stringPublic stable-ABI wrapper around _PyOS_double_to_string

Reading

_PyOS_ascii_strtod

The core locale workaround. It detects whether the running locale uses a non-. decimal separator and, if so, rewrites the input string before calling strtod, then resets the pointer.

// CPython: Python/pystrtod.c:80 _PyOS_ascii_strtod
double
_PyOS_ascii_strtod(const char *nptr, char **endptr)
{
double result;
_Py_SET_53BIT_PRECISION_HEADER;

assert(nptr != NULL);

_Py_SET_53BIT_PRECISION_START;
result = _PyOS_ascii_strtod_impl(nptr, endptr);
_Py_SET_53BIT_PRECISION_END;

return result;
}

The _Py_SET_53BIT_PRECISION_* macros force 53-bit mantissa precision on x87 FPUs (relevant on 32-bit x86 only; a no-op everywhere else).

PyOS_string_to_double

This is the entry point called by float() and ast.literal_eval. It handles the special-case strings "inf", "-inf", "nan", and their case variants before delegating to _PyOS_ascii_strtod. On overflow it raises OverflowError; on a bad string it raises ValueError.

// CPython: Python/pystrtod.c:320 PyOS_string_to_double
double
PyOS_string_to_double(const char *s, char **endptr, PyObject *overflow_exception)
{
double x;
char *fail_pos;

errno = 0;
x = _PyOS_ascii_strtod(s, &fail_pos);

if (errno == ENOMEM) {
PyErr_NoMemory();
return -1.0;
}
if (!errno && fail_pos == s) {
/* nothing was converted */
if (endptr != NULL)
*endptr = fail_pos;
else {
PyErr_Format(PyExc_ValueError,
"could not convert string to float: %.200s", s);
return -1.0;
}
}
if (errno == ERANGE) {
if (overflow_exception == NULL)
return x; /* return inf/-inf and let caller decide */
PyErr_SetString(overflow_exception,
"float overflow on string-to-double conversion");
return -1.0;
}
return x;
}

_PyOS_double_to_string

The formatting side. The format_code argument accepts 'e', 'f', 'g', 'r' (shortest-round-trip repr), and 's' (str). The result is a newly allocated C string; the caller owns it.

// CPython: Python/pystrtod.c:450 _PyOS_double_to_string
char *
_PyOS_double_to_string(double val, char format_code, int precision,
int flags, int *type)
{
char format[32];
Py_ssize_t bufsize;
char *buf;
int t;
Py_ssize_t len;

/* handle nan and inf early so snprintf never sees them */
if (Py_IS_NAN(val)) {
buf = PyMem_Malloc(4);
if (!buf) { PyErr_NoMemory(); return NULL; }
strcpy(buf, "nan");
if (type) *type = Py_DTST_NAN;
return buf;
}
/* ... inf, then snprintf path for finite values ... */
}

The flags bitmask controls zero-padding (Py_DTSF_SIGN), the + prefix (Py_DTSF_ADD_DOT_0), and whether to use the repr shortest path (Py_DTSF_SHORT).

gopy notes

  • gopy does not call libc strtod directly. Go's strconv.ParseFloat is already locale-independent, so PyOS_string_to_double maps to a thin wrapper in objects/float.go around strconv.ParseFloat(s, 64).
  • _PyOS_double_to_string maps to strconv.FormatFloat. The 'r' / 's' format codes both use strconv.FormatFloat(v, 'g', -1, 64) (shortest representation). The 'e' / 'f' / 'g' codes pass the precision argument through.
  • The Py_DTSF_ADD_DOT_0 flag (ensures at least one .0 for integers like 1.0) must be replicated in the Go formatter: after formatting, check if the result contains . or e; if not, append .0.
  • The _Py_SET_53BIT_PRECISION macros have no Go equivalent and are deliberately omitted.

CPython 3.14 changes

  • The internal dtoa.c / _Py_dg_strtod path was removed in 3.11; 3.14 continues without it. The PY_NO_SHORT_FLOAT_REPR compile guard is now a no-op stub.
  • PyOS_string_to_double gained a fast path for the common case where errno == 0 and fail_pos != s, avoiding a branch through the ERANGE block.
  • _PyOS_double_to_string now delegates the 'r' format code to the Ryu algorithm (via _Py_dg_dtoa replacement) for correctly-rounded shortest-decimal output, replacing the older Grisu2 path.