libretro / swanstation

GNU General Public License v3.0
93 stars 21 forks source link

Don't use floating-point for time #116

Open guijan opened 1 week ago

guijan commented 1 week ago

The timer API in swanstation correctly stores time as an integer: https://github.com/libretro/swanstation/blob/7a27436548128c00e70b08dde63c52118e2a6228/src/common/timer.h#L9 But then provides APIs to convert it to and from floating-point: https://github.com/libretro/swanstation/blob/7a27436548128c00e70b08dde63c52118e2a6228/src/common/timer.h#L14-L17 The floating-point API is used in the code, here's one example: https://github.com/libretro/swanstation/blob/7a27436548128c00e70b08dde63c52118e2a6228/src/core/gpu_backend.cpp#L213-L214

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: https://github.com/libretro/swanstation/blob/7a27436548128c00e70b08dde63c52118e2a6228/src/common/timer.cpp#L59-L60 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.