#include "print_float.h" #include #include "common/goal_constants.h" #include "common/util/Assert.h" #include "third-party/dragonbox.h" #include "third-party/fmt/core.h" /*! * Convert a float to a string. The string is _always_ in this format: * [negative_sign] [at least 1 digit] [decimal point] [at least 1 digit] * and, if you trust the dragonbox library, should be the shortest possible representation * that round-trips through a properly implemented string -> float conversion. */ std::string float_to_string(float value, bool append_trailing_decimal) { constexpr int buff_size = 128; char buff[buff_size]; float_to_cstr(value, buff, append_trailing_decimal); return {buff}; } /*! * Wrapper around float_to_string, for printing meters. Unlike float_to_string, it does not append * decimals by default. */ std::string meters_to_string(float value, bool append_trailing_decimal) { return float_to_string(value / METER_LENGTH, append_trailing_decimal); } /*! * Convert a fixed point value to a float. Fixed point values usually end up with strange numbers * that were definitely not what was written when we do a naive conversion. This function * is a bit more clever. */ float fixed_point_to_float(s64 value, s64 scale) { float sign = value < 0 ? -1 : 1; value = std::abs(value); double naive = (double)value / scale; double flt_scale = 1; while (flt_scale < scale) { // 1 -> 0.1 flt_scale *= 10; } // we have a scale with enough precision for our fixed point. now find a good decimal. // start at something resonable. s64 fixed_start = (s64)(naive * flt_scale); fixed_start = fixed_start - (fixed_start % 5); // add e.g. 0.005 in a 1/1000 scale s64 fixed_add = 0; while ((s64)((fixed_start + fixed_add) / flt_scale * scale) < value) { fixed_add += 5; } if ((s64)((fixed_start + fixed_add) / flt_scale * scale) == value) { return (fixed_start + fixed_add) / flt_scale * sign; } // add e.g. 0.001 in a 1/1000 scale fixed_add = 0; while ((s64)((fixed_start + fixed_add) / flt_scale * scale) < value) { fixed_add += 1; } if ((s64)((fixed_start + fixed_add) / flt_scale * scale) == value) { return (fixed_start + fixed_add) / flt_scale * sign; } // added too much! ASSERT_MSG(false, fmt::format("fixed_point_to_float failed hard. v: {} s: {}", value, scale)); return 0; } /*! * Convert a fixed point value to a string. Wrapper around fixed_point_to_string */ std::string fixed_point_to_string(s64 value, s64 scale, bool append_trailing_decimal) { return float_to_string(fixed_point_to_float(value, scale), append_trailing_decimal); } /*! * Wrapper around fixed_point_to_string, for printing seconds. */ std::string seconds_to_string(s64 value, bool append_trailing_decimal) { return fixed_point_to_string(value, TICKS_PER_SECOND, append_trailing_decimal); } int float_to_cstr(float value, char* buffer, bool append_trailing_decimal) { ASSERT(std::isfinite(value)); // dragonbox gives us: // - an integer, representing the decimal value // - sign // - exponent int i = 0; // the exponent/significand representation of dragonbox is ambiguous with how it represents 0, // so just handle that as a special case if (value == 0) { buffer[i++] = '0'; if (append_trailing_decimal) { buffer[i++] = '.'; buffer[i++] = '0'; } buffer[i++] = '\0'; } auto decimal = jkj::dragonbox::to_decimal(value); // in all cases, we need to convert the decimal to characters. char digit_buff[64]; int num_digits = 0; u64 significand = decimal.significand; while (significand) { digit_buff[num_digits++] = '0' + (significand % 10); significand /= 10; } if (decimal.exponent >= 0) { // needs 0 or more trailing zeros before decimal (no nonzeros after decimal). // print in four parts: // negative sign | digits | 000's | .0 // part 1 if (decimal.is_negative) { buffer[i++] = '-'; } // part 2 for (int digit = 0; digit < num_digits; digit++) { buffer[i++] = digit_buff[num_digits - (digit + 1)]; } // part 3 for (int j = 0; j < decimal.exponent; j++) { buffer[i++] = '0'; } // part 4 if (append_trailing_decimal) { buffer[i++] = '.'; buffer[i++] = '0'; } buffer[i++] = '\0'; } else { // some nonzero digits after decimal. if (num_digits <= -decimal.exponent) { // all after the decimal // negative sign | 0. | 000's | digits // part 1 if (decimal.is_negative) { buffer[i++] = '-'; } // part 2 buffer[i++] = '0'; buffer[i++] = '.'; // part 3 int zeros = -decimal.exponent - num_digits; for (int j = 0; j < zeros; j++) { buffer[i++] = '0'; } // part 4 for (int digit = 0; digit < num_digits; digit++) { buffer[i++] = digit_buff[num_digits - (digit + 1)]; } buffer[i++] = '\0'; } else { // some before, some after. // negative sign | digits | . | digits // part 1 if (decimal.is_negative) { buffer[i++] = '-'; } // part 2 int digits_before_decimal = num_digits + decimal.exponent; int digit = 0; for (; digit < digits_before_decimal; digit++) { buffer[i++] = digit_buff[num_digits - (digit + 1)]; } // part 3 buffer[i++] = '.'; // part 4 for (; digit < num_digits; digit++) { buffer[i++] = digit_buff[num_digits - (digit + 1)]; } buffer[i++] = '\0'; } } return i; }