/* -*- Mode: C; c-file-style: "python" -*- */ #include #include /* ascii character tests (as opposed to locale tests) */ #define ISSPACE(c) ((c) == ' ' || (c) == '\f' || (c) == '\n' || \ (c) == '\r' || (c) == '\t' || (c) == '\v') #define ISDIGIT(c) ((c) >= '0' && (c) <= '9') /** * PyOS_ascii_strtod: * @nptr: the string to convert to a numeric value. * @endptr: if non-%NULL, it returns the character after * the last character used in the conversion. * * Converts a string to a #gdouble value. * This function behaves like the standard strtod() function * does in the C locale. It does this without actually * changing the current locale, since that would not be * thread-safe. * * This function is typically used when reading configuration * files or other non-user input that should be locale independent. * To handle input from the user you should normally use the * locale-sensitive system strtod() function. * * If the correct value would cause overflow, plus or minus %HUGE_VAL * is returned (according to the sign of the value), and %ERANGE is * stored in %errno. If the correct value would cause underflow, * zero is returned and %ERANGE is stored in %errno. * If memory allocation fails, %ENOMEM is stored in %errno. * * This function resets %errno before calling strtod() so that * you can reliably detect overflow and underflow. * * Return value: the #gdouble value. **/ /* Use system strtod; since strtod is locale aware, we may have to first fix the decimal separator. Note that unlike _Py_dg_strtod, the system strtod may not always give correctly rounded results. */ double PyOS_ascii_strtod(const char *nptr, char **endptr) { char *fail_pos; double val = -1.0; struct lconv *locale_data; const char *decimal_point; size_t decimal_point_len; const char *p, *decimal_point_pos; const char *end = NULL; /* Silence gcc */ const char *digits_pos = NULL; int negate = 0; assert(nptr != NULL); fail_pos = NULL; locale_data = localeconv(); decimal_point = locale_data->decimal_point; decimal_point_len = strlen(decimal_point); assert(decimal_point_len != 0); decimal_point_pos = NULL; /* We process any leading whitespace and the optional sign manually, then pass the remainder to the system strtod. This ensures that the result of an underflow has the correct sign. (bug #1725) */ p = nptr; /* Skip leading space */ while (ISSPACE(*p)) p++; /* Process leading sign, if present */ if (*p == '-') { negate = 1; p++; } else if (*p == '+') { p++; } /* What's left should begin with a digit, a decimal point, or one of the letters i, I, n, N. It should not begin with 0x or 0X */ if ((!ISDIGIT(*p) && *p != '.' && *p != 'i' && *p != 'I' && *p != 'n' && *p != 'N') || (*p == '0' && (p[1] == 'x' || p[1] == 'X'))) { if (endptr) *endptr = (char*)nptr; errno = EINVAL; return val; } digits_pos = p; if (decimal_point[0] != '.' || decimal_point[1] != 0) { while (ISDIGIT(*p)) p++; if (*p == '.') { decimal_point_pos = p++; while (ISDIGIT(*p)) p++; if (*p == 'e' || *p == 'E') p++; if (*p == '+' || *p == '-') p++; while (ISDIGIT(*p)) p++; end = p; } else if (strncmp(p, decimal_point, decimal_point_len) == 0) { /* Python bug #1417699 */ if (endptr) *endptr = (char*)nptr; errno = EINVAL; return val; } /* For the other cases, we need not convert the decimal point */ } /* Set errno to zero, so that we can distinguish zero results and underflows */ errno = 0; if (decimal_point_pos) { char *copy, *c; /* We need to convert the '.' to the locale specific decimal point */ copy = (char *)PyMem_MALLOC(end - digits_pos + 1 + decimal_point_len); if (copy == NULL) { if (endptr) *endptr = (char *)nptr; errno = ENOMEM; return val; } c = copy; memcpy(c, digits_pos, decimal_point_pos - digits_pos); c += decimal_point_pos - digits_pos; memcpy(c, decimal_point, decimal_point_len); c += decimal_point_len; memcpy(c, decimal_point_pos + 1, end - (decimal_point_pos + 1)); c += end - (decimal_point_pos + 1); *c = 0; val = strtod(copy, &fail_pos); if (fail_pos) { if (fail_pos > decimal_point_pos) fail_pos = (char *)digits_pos + (fail_pos - copy) - (decimal_point_len - 1); else fail_pos = (char *)digits_pos + (fail_pos - copy); } PyMem_FREE(copy); } else { val = strtod(digits_pos, &fail_pos); } if (fail_pos == digits_pos) fail_pos = (char *)nptr; if (negate && fail_pos != nptr) val = -val; if (endptr) *endptr = fail_pos; return val; } double PyOS_ascii_atof(const char *nptr) { return PyOS_ascii_strtod(nptr, NULL); } /* Given a string that may have a decimal point in the current locale, change it back to a dot. Since the string cannot get longer, no need for a maximum buffer size parameter. */ Py_LOCAL_INLINE(void) change_decimal_from_locale_to_dot(char* buffer) { struct lconv *locale_data = localeconv(); const char *decimal_point = locale_data->decimal_point; if (decimal_point[0] != '.' || decimal_point[1] != 0) { size_t decimal_point_len = strlen(decimal_point); if (*buffer == '+' || *buffer == '-') buffer++; while (isdigit(Py_CHARMASK(*buffer))) buffer++; if (strncmp(buffer, decimal_point, decimal_point_len) == 0) { *buffer = '.'; buffer++; if (decimal_point_len > 1) { /* buffer needs to get smaller */ size_t rest_len = strlen(buffer + (decimal_point_len - 1)); memmove(buffer, buffer + (decimal_point_len - 1), rest_len); buffer[rest_len] = 0; } } } } /* From the C99 standard, section 7.19.6: The exponent always contains at least two digits, and only as many more digits as necessary to represent the exponent. */ #define MIN_EXPONENT_DIGITS 2 /* Ensure that any exponent, if present, is at least MIN_EXPONENT_DIGITS in length. */ Py_LOCAL_INLINE(void) ensure_minumim_exponent_length(char* buffer, size_t buf_size) { char *p = strpbrk(buffer, "eE"); if (p && (*(p + 1) == '-' || *(p + 1) == '+')) { char *start = p + 2; int exponent_digit_cnt = 0; int leading_zero_cnt = 0; int in_leading_zeros = 1; int significant_digit_cnt; /* Skip over the exponent and the sign. */ p += 2; /* Find the end of the exponent, keeping track of leading zeros. */ while (*p && isdigit(Py_CHARMASK(*p))) { if (in_leading_zeros && *p == '0') ++leading_zero_cnt; if (*p != '0') in_leading_zeros = 0; ++p; ++exponent_digit_cnt; } significant_digit_cnt = exponent_digit_cnt - leading_zero_cnt; if (exponent_digit_cnt == MIN_EXPONENT_DIGITS) { /* If there are 2 exactly digits, we're done, regardless of what they contain */ } else if (exponent_digit_cnt > MIN_EXPONENT_DIGITS) { int extra_zeros_cnt; /* There are more than 2 digits in the exponent. See if we can delete some of the leading zeros */ if (significant_digit_cnt < MIN_EXPONENT_DIGITS) significant_digit_cnt = MIN_EXPONENT_DIGITS; extra_zeros_cnt = exponent_digit_cnt - significant_digit_cnt; /* Delete extra_zeros_cnt worth of characters from the front of the exponent */ assert(extra_zeros_cnt >= 0); /* Add one to significant_digit_cnt to copy the trailing 0 byte, thus setting the length */ memmove(start, start + extra_zeros_cnt, significant_digit_cnt + 1); } else { /* If there are fewer than 2 digits, add zeros until there are 2, if there's enough room */ int zeros = MIN_EXPONENT_DIGITS - exponent_digit_cnt; if (start + zeros + exponent_digit_cnt + 1 < buffer + buf_size) { memmove(start + zeros, start, exponent_digit_cnt + 1); memset(start, '0', zeros); } } } } /* Ensure that buffer has a decimal point in it. The decimal point will not be in the current locale, it will always be '.'. Don't add a decimal if an exponent is present. */ Py_LOCAL_INLINE(void) ensure_decimal_point(char* buffer, size_t buf_size) { int insert_count = 0; char* chars_to_insert; /* search for the first non-digit character */ char *p = buffer; if (*p == '-' || *p == '+') /* Skip leading sign, if present. I think this could only ever be '-', but it can't hurt to check for both. */ ++p; while (*p && isdigit(Py_CHARMASK(*p))) ++p; if (*p == '.') { if (isdigit(Py_CHARMASK(*(p+1)))) { /* Nothing to do, we already have a decimal point and a digit after it */ } else { /* We have a decimal point, but no following digit. Insert a zero after the decimal. */ ++p; chars_to_insert = "0"; insert_count = 1; } } else if (!(*p == 'e' || *p == 'E')) { /* Don't add ".0" if we have an exponent. */ chars_to_insert = ".0"; insert_count = 2; } if (insert_count) { size_t buf_len = strlen(buffer); if (buf_len + insert_count + 1 >= buf_size) { /* If there is not enough room in the buffer for the additional text, just skip it. It's not worth generating an error over. */ } else { memmove(p + insert_count, p, buffer + strlen(buffer) - p + 1); memcpy(p, chars_to_insert, insert_count); } } } /* see FORMATBUFLEN in unicodeobject.c */ #define FLOAT_FORMATBUFLEN 120 /** * PyOS_ascii_formatd: * @buffer: A buffer to place the resulting string in * @buf_size: The length of the buffer. * @format: The printf()-style format to use for the * code to use for converting. * @d: The #gdouble to convert * * Converts a #gdouble to a string, using the '.' as * decimal point. To format the number you pass in * a printf()-style format string. Allowed conversion * specifiers are 'e', 'E', 'f', 'F', 'g', 'G', and 'Z'. * * 'Z' is the same as 'g', except it always has a decimal and * at least one digit after the decimal. * * Return value: The pointer to the buffer with the converted string. **/ char * PyOS_ascii_formatd(char *buffer, size_t buf_size, const char *format, double d) { char format_char; size_t format_len = strlen(format); /* Issue 2264: code 'Z' requires copying the format. 'Z' is 'g', but also with at least one character past the decimal. */ char tmp_format[FLOAT_FORMATBUFLEN]; /* The last character in the format string must be the format char */ format_char = format[format_len - 1]; if (format[0] != '%') return NULL; /* I'm not sure why this test is here. It's ensuring that the format string after the first character doesn't have a single quote, a lowercase l, or a percent. This is the reverse of the commented-out test about 10 lines ago. */ if (strpbrk(format + 1, "'l%")) return NULL; /* Also curious about this function is that it accepts format strings like "%xg", which are invalid for floats. In general, the interface to this function is not very good, but changing it is difficult because it's a public API. */ if (!(format_char == 'e' || format_char == 'E' || format_char == 'f' || format_char == 'F' || format_char == 'g' || format_char == 'G' || format_char == 'Z')) return NULL; /* Map 'Z' format_char to 'g', by copying the format string and replacing the final char with a 'g' */ if (format_char == 'Z') { if (format_len + 1 >= sizeof(tmp_format)) { /* The format won't fit in our copy. Error out. In practice, this will never happen and will be detected by returning NULL */ return NULL; } strcpy(tmp_format, format); tmp_format[format_len - 1] = 'g'; format = tmp_format; } /* Have PyOS_snprintf do the hard work */ PyOS_snprintf(buffer, buf_size, format, d); /* Do various fixups on the return string */ /* Get the current locale, and find the decimal point string. Convert that string back to a dot. */ change_decimal_from_locale_to_dot(buffer); /* If an exponent exists, ensure that the exponent is at least MIN_EXPONENT_DIGITS digits, providing the buffer is large enough for the extra zeros. Also, if there are more than MIN_EXPONENT_DIGITS, remove as many zeros as possible until we get back to MIN_EXPONENT_DIGITS */ ensure_minumim_exponent_length(buffer, buf_size); /* If format_char is 'Z', make sure we have at least one character after the decimal point (and make sure we have a decimal point). */ if (format_char == 'Z') ensure_decimal_point(buffer, buf_size); return buffer; } PyAPI_FUNC(char *) PyOS_double_to_string(double val, char format_code, int precision, int flags, int *type) { char buf[128]; char format[32]; Py_ssize_t len; char *result; char *p; int t; int upper = 0; /* Validate format_code, and map upper and lower case */ switch (format_code) { case 'e': /* exponent */ case 'f': /* fixed */ case 'g': /* general */ break; case 'E': upper = 1; format_code = 'e'; break; case 'F': upper = 1; format_code = 'f'; break; case 'G': upper = 1; format_code = 'g'; break; case 'r': /* repr format */ /* Supplied precision is unused, must be 0. */ if (precision != 0) { PyErr_BadInternalCall(); return NULL; } precision = 17; format_code = 'g'; break; case 's': /* str format */ /* Supplied precision is unused, must be 0. */ if (precision != 0) { PyErr_BadInternalCall(); return NULL; } precision = 12; format_code = 'g'; break; default: PyErr_BadInternalCall(); return NULL; } /* Handle nan and inf. */ if (Py_IS_NAN(val)) { strcpy(buf, "nan"); t = Py_DTST_NAN; } else if (Py_IS_INFINITY(val)) { if (copysign(1., val) == 1.) strcpy(buf, "inf"); else strcpy(buf, "-inf"); t = Py_DTST_INFINITE; } else { t = Py_DTST_FINITE; if (flags & Py_DTSF_ADD_DOT_0) format_code = 'Z'; PyOS_snprintf(format, 32, "%%%s.%i%c", (flags & Py_DTSF_ALT ? "#" : ""), precision, format_code); PyOS_ascii_formatd(buf, sizeof(buf), format, val); } len = strlen(buf); /* Add 1 for the trailing 0 byte. Add 1 because we might need to make room for the sign. */ result = PyMem_Malloc(len + 2); if (result == NULL) { PyErr_NoMemory(); return NULL; } p = result; /* Never add sign for nan/inf, even if asked. */ if (flags & Py_DTSF_SIGN && buf[0] != '-' && t == Py_DTST_FINITE) *p++ = '+'; strcpy(p, buf); if (upper) { /* Convert to upper case. */ char *p1; for (p1 = p; *p1; p1++) *p1 = toupper(*p1); } if (type) *type = t; return result; }