Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't use floating-point for time #116

Open
guijan opened this issue Jun 26, 2024 · 0 comments
Open

Don't use floating-point for time #116

guijan opened this issue Jun 26, 2024 · 0 comments

Comments

@guijan
Copy link
Contributor

guijan commented Jun 26, 2024

The timer API in swanstation correctly stores time as an integer:

using Value = std::uint64_t;

But then provides APIs to convert it to and from floating-point:
static double ConvertValueToSeconds(Value value);
static double ConvertValueToMilliseconds(Value value);
static double ConvertValueToNanoseconds(Value value);
static Value ConvertSecondsToValue(double s);

The floating-point API is used in the code, here's one example:
const Common::Timer::Value current_time = Common::Timer::GetValue();
if (Common::Timer::ConvertValueToNanoseconds(current_time - last_command_time) < SPIN_TIME_NS)

This article has a mostly good writeup on why storing time in floating-point is bad:
https://randomascii.wordpress.com/2012/02/13/dont-store-that-in-a-float/

Keep in mind that most of the examples in the article use the float type in the context of game dev, which is typically single-precision floating-point IEEE754 (32-bit float). Swanstation uses double which is typically double-precision IEEE754 (64-bit float), so things are not as extreme as in the examples there.

The article also suggests using double as a possible fix, however, that isn't good enough for swanstation. For instance, it appears CLOCK_MONOTONIC is implemented as the time since boot at least on NetBSD and OpenBSD. I can't seem to find the relevant code on FreeBSD or Linux, but they should be similar. Considering every integer between -(2^53) and 2^53 can be stored without rounding in a 64-bit IEEE754 float, the time is acquired via CLOCK_MONOTONIC on Unix, and the unit is nanoseconds:

clock_gettime(CLOCK_MONOTONIC, &tv);
return ((Value)tv.tv_nsec + (Value)tv.tv_sec * 1000000000);

It takes 2^53 / 1000000000 seconds or 104 days of uptime for any conversion of Common::Timer to double to lose precision to rounding.

The separate Windows and Unix code can be mostly unified too, only GetValue() needs to be different.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant