diff --git a/include/yaml-cpp/fp_to_string.h b/include/yaml-cpp/fp_to_string.h index 2244f43..2ababd5 100644 --- a/include/yaml-cpp/fp_to_string.h +++ b/include/yaml-cpp/fp_to_string.h @@ -20,9 +20,9 @@ namespace fp_formatting { * Converts a integer into its ASCII digits. * * @param begin/end - a buffer, must be at least 20bytes long - * @param value - input value - * @param width - minimum number of digits, fill with '0' to the left. Must be equal or smaller than the buffer size. - * @return - number of digits filled into the buffer. + * @param value - input value + * @param width - minimum number of digits, fill with '0' to the left. Must be equal or smaller than the buffer size. + * @return - number of digits filled into the buffer. * * Example: * std::array buffer; @@ -33,27 +33,27 @@ namespace fp_formatting { * assert(buffer[2] == '3'); */ inline auto ConvertToChars(char* begin, char* end, size_t value, int width=1) -> int { - assert(width >= 1); - assert(end >= begin); // end must be after begin - assert(end-begin >= width); // Buffer must be large enough - assert(end-begin >= 20); // 2^64 has 20digits, so at least 20 digits must be available + assert(width >= 1); + assert(end >= begin); // end must be after begin + assert(end-begin >= width); // Buffer must be large enough + assert(end-begin >= 20); // 2^64 has 20digits, so at least 20 digits must be available - // count number of digits, and fill digits array accordingly - int digits_ct{}; - while (value > 0) { - char c = value % 10 + '0'; - value = value / 10; - digits_ct += 1; - *(end-digits_ct) = c; - } - while(digits_ct < width) { - assert(digits_ct < 64); - digits_ct += 1; - *(end-digits_ct) = '0'; - } - // move data to the front of the array - std::memmove(begin, end-digits_ct, digits_ct); - return digits_ct; + // count number of digits, and fill digits array accordingly + int digits_ct{}; + while (value > 0) { + char c = value % 10 + '0'; + value = value / 10; + digits_ct += 1; + *(end-digits_ct) = c; + } + while(digits_ct < width) { + assert(digits_ct < 64); + digits_ct += 1; + *(end-digits_ct) = '0'; + } + // move data to the front of the array + std::memmove(begin, end-digits_ct, digits_ct); + return digits_ct; } /** @@ -63,144 +63,144 @@ inline auto ConvertToChars(char* begin, char* end, size_t value, int width=1) -> */ template auto FpToString(T v, int precision = 0) -> std::string { -// assert(precision > 0); - // hardcoded constant, at which exponent should switch to a scientific notation - int const lowerExponentThreshold = -5; - int const upperExponentThreshold = (precision==0)?6:precision; - if (precision == 0) { - precision = 6; - } +// assert(precision > 0); + // hardcoded constant, at which exponent should switch to a scientific notation + int const lowerExponentThreshold = -5; + int const upperExponentThreshold = (precision==0)?6:precision; + if (precision == 0) { + precision = 6; + } - // dragonbox/to_decimal does not handle value 0, inf, NaN - if (v == 0 || std::isinf(v) || std::isnan(v)) { - std::stringstream ss; - ss << v; - return ss.str(); - } + // dragonbox/to_decimal does not handle value 0, inf, NaN + if (v == 0 || std::isinf(v) || std::isnan(v)) { + std::stringstream ss; + ss << v; + return ss.str(); + } - auto r = jkj::dragonbox::to_decimal(v); + auto r = jkj::dragonbox::to_decimal(v); - auto digits = std::array{}; // max digits of size_t is 20. - auto digits_ct = ConvertToChars(digits.data(), digits.data() + digits.size(), r.significand); + auto digits = std::array{}; // max digits of size_t is 20. + auto digits_ct = ConvertToChars(digits.data(), digits.data() + digits.size(), r.significand); - // check if requested precision is lower than - // required digits for exact representation - if (digits_ct > precision) { - auto diff = digits_ct - precision; - r.exponent += diff; - digits_ct = precision; + // check if requested precision is lower than + // required digits for exact representation + if (digits_ct > precision) { + auto diff = digits_ct - precision; + r.exponent += diff; + digits_ct = precision; - // round numbers if required - if (digits[digits_ct] >= '5') { - int i{digits_ct-1}; - digits[i] += 1; - while (digits[i] == '9'+1) { - digits_ct -= 1; - r.exponent += 1; - if (i > 0) { - digits[i-1] += 1; - i -= 1; - } else { - digits_ct = 1; - digits[0] = '1'; - break; - } - } - } - } - - std::array output_buffer; // max digits of size_t plus sign, a dot and 2 letters for 'e+' or 'e-' and 4 letters for the exponent - auto output_ptr = &output_buffer[0]; - - // print '-' symbol for negative numbers - if (r.is_negative) { - *(output_ptr++) = '-'; - } - - // exponent if only a single non-zero digit is before the decimal point - int const exponent = r.exponent + digits_ct - 1; - - // case 1: scientific notation - if (exponent >= upperExponentThreshold || exponent <= lowerExponentThreshold) { - // print first digit - *(output_ptr++) = digits[0]; - - // print digits after decimal point - if (digits_ct > 1) { - *(output_ptr++) = '.'; - // print significant numbers after decimal point - for (int i{1}; i < digits_ct; ++i) { - *(output_ptr++) = digits[i]; - } - } - *(output_ptr++) = 'e'; - *(output_ptr++) = (exponent>=0)?'+':'-'; - auto exp_digits = std::array{}; - auto exp_digits_ct = ConvertToChars(exp_digits.data(), exp_digits.data() + exp_digits.size(), std::abs(exponent), /*.precision=*/ 2); - for (int i{0}; i < exp_digits_ct; ++i) { - *(output_ptr++) = exp_digits[i]; - } - - // case 2: default notation - } else { - auto const digits_end = digits.begin() + digits_ct; - auto digits_iter = digits.begin(); - - // print digits before point - int const before_decimal_digits = digits_ct + r.exponent; - if (before_decimal_digits > 0) { - // print digits before point - for (int i{0}; i < std::min(before_decimal_digits, digits_ct); ++i) { - *(output_ptr++) = *(digits_iter++); - } - // print trailing zeros before point - for (int i{0}; i < before_decimal_digits - digits_ct; ++i) { - *(output_ptr++) = '0'; - } - - // print 0 before point if none where printed before + // round numbers if required + if (digits[digits_ct] >= '5') { + int i{digits_ct-1}; + digits[i] += 1; + while (digits[i] == '9'+1) { + digits_ct -= 1; + r.exponent += 1; + if (i > 0) { + digits[i-1] += 1; + i -= 1; } else { - *(output_ptr++) = '0'; - } - - if (digits_iter != digits_end) { - *(output_ptr++) = '.'; - // print 0 afer decimal point, to fill until first digits - int const after_decimal_zeros = -digits_ct - r.exponent; - for (int i{0}; i < after_decimal_zeros; ++i) { - *(output_ptr++) = '0'; - } - - // print significant numbers after decimal point - for (;digits_iter < digits_end; ++digits_iter) { - *(output_ptr++) = *digits_iter; - } + digits_ct = 1; + digits[0] = '1'; + break; } + } } - *output_ptr = '\0'; - return std::string{&output_buffer[0], output_ptr}; + } + + std::array output_buffer; // max digits of size_t plus sign, a dot and 2 letters for 'e+' or 'e-' and 4 letters for the exponent + auto output_ptr = &output_buffer[0]; + + // print '-' symbol for negative numbers + if (r.is_negative) { + *(output_ptr++) = '-'; + } + + // exponent if only a single non-zero digit is before the decimal point + int const exponent = r.exponent + digits_ct - 1; + + // case 1: scientific notation + if (exponent >= upperExponentThreshold || exponent <= lowerExponentThreshold) { + // print first digit + *(output_ptr++) = digits[0]; + + // print digits after decimal point + if (digits_ct > 1) { + *(output_ptr++) = '.'; + // print significant numbers after decimal point + for (int i{1}; i < digits_ct; ++i) { + *(output_ptr++) = digits[i]; + } + } + *(output_ptr++) = 'e'; + *(output_ptr++) = (exponent>=0)?'+':'-'; + auto exp_digits = std::array{}; + auto exp_digits_ct = ConvertToChars(exp_digits.data(), exp_digits.data() + exp_digits.size(), std::abs(exponent), /*.precision=*/ 2); + for (int i{0}; i < exp_digits_ct; ++i) { + *(output_ptr++) = exp_digits[i]; + } + + // case 2: default notation + } else { + auto const digits_end = digits.begin() + digits_ct; + auto digits_iter = digits.begin(); + + // print digits before point + int const before_decimal_digits = digits_ct + r.exponent; + if (before_decimal_digits > 0) { + // print digits before point + for (int i{0}; i < std::min(before_decimal_digits, digits_ct); ++i) { + *(output_ptr++) = *(digits_iter++); + } + // print trailing zeros before point + for (int i{0}; i < before_decimal_digits - digits_ct; ++i) { + *(output_ptr++) = '0'; + } + + // print 0 before point if none where printed before + } else { + *(output_ptr++) = '0'; + } + + if (digits_iter != digits_end) { + *(output_ptr++) = '.'; + // print 0 afer decimal point, to fill until first digits + int const after_decimal_zeros = -digits_ct - r.exponent; + for (int i{0}; i < after_decimal_zeros; ++i) { + *(output_ptr++) = '0'; + } + + // print significant numbers after decimal point + for (;digits_iter < digits_end; ++digits_iter) { + *(output_ptr++) = *digits_iter; + } + } + } + *output_ptr = '\0'; + return std::string{&output_buffer[0], output_ptr}; } } } inline auto FpToString(float v, size_t precision = 0) -> std::string { - return detail::fp_formatting::FpToString(v, precision); + return detail::fp_formatting::FpToString(v, precision); } inline auto FpToString(double v, size_t precision = 0) -> std::string { - return detail::fp_formatting::FpToString(v, precision); + return detail::fp_formatting::FpToString(v, precision); } /** * dragonbox only works for floats/doubles not long double */ inline auto FpToString(long double v, size_t precision = std::numeric_limits::max_digits10) -> std::string { - std::stringstream ss; - ss.precision(precision); - ss.imbue(std::locale("C")); - ss << v; - return ss.str(); + std::stringstream ss; + ss.imbue(std::locale("C")); + ss.precision(precision); + ss << v; + return ss.str(); } } diff --git a/test/fp_to_string_test.cpp b/test/fp_to_string_test.cpp index e7c1dcc..416cdfd 100644 --- a/test/fp_to_string_test.cpp +++ b/test/fp_to_string_test.cpp @@ -9,12 +9,12 @@ namespace { */ template static std::string convert_with_stringstream(T v, size_t precision = 0) { - std::stringstream ss; - if (precision > 0) { - ss << std::setprecision(precision); - } - ss << v; - return ss.str(); + std::stringstream ss; + if (precision > 0) { + ss << std::setprecision(precision); + } + ss << v; + return ss.str(); } // Caution: Test involving 'convert_with_stringstream' are based on std::stringstream