mirror of
https://github.com/open-goal/jak-project.git
synced 2024-10-20 11:26:18 -04:00
203 lines
5.5 KiB
C++
203 lines
5.5 KiB
C++
#include "print_float.h"
|
|
|
|
#include <cmath>
|
|
|
|
#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;
|
|
}
|