Replies: 1 comment 3 replies
-
|
Thanks for writing this up — I agree with much of your reasoning here. You’re absolutely right that most developers don’t fully understand the footguns of floating-point numbers, and that other ecosystems (like Java, Go, or C++) tend to be much stricter about avoiding float in date/time APIs. To clarify: This is very much a “practicality beats purity” situation. If Python had a more widespread numeric type for representing fractional values precisely, I’d use it. But in practice, For methods like You’re right that in principle, floats can lose precision, but in practice, it’s very hard to find real-world cases where this matters. For example: TimeDelta(seconds=.0000018).in_hours() == 0.0000000005
TimeDelta(seconds=5).in_hours() == (5 / 3600)
TimeDelta(seconds=0.1 + 0.2) == TimeDelta(seconds=0.3)These both work fine. You start seeing precision loss only at absurd magnitudes like: TimeDelta(hours=24 * 365 * 9999, nanoseconds=1).in_hours()At that point, nanosecond accuracy simply isn’t meaningful. One important nuance: for timestamps, I completely agree that floats are a footgun. That’s why Lastly, removing float doesn’t make I’m definitely open to revisiting this if concrete precision issues turn up in real use cases — but so far, I haven’t seen any that outweigh the simplicity and predictability of the current approach. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
I'm probably going to ruffle some feathers, but I have to write this up for the record.
When creating #295, I noticed that a number of methods returned a
float, which was surprising to me. I expected these to return anint:TimeDelta.in_seconds() -> floatTimeDelta.in_milliseconds() -> floatTimeDelta.in_microseconds() -> floatNow I am noticing a more pervasive use of the
floattype in the parameters for other things, for example,Install.add(...),subtract(...)TimeDelta(...)constructorI feel that the use of
floatis a foot-gun that should not exist in a date-time library.1)) I think the vast majority of programmers do not understand floating point numbers and operations. How many Python people would be surprised that
0.1 + 0.2 == 0.3returnsFalse? Too many.2)) So they learn something about floating point rounding errors in a base-2 system. Do they know the difference between 32-bit floating versus 64-bit floating in the IEEE-754 specification? (My thesis involved 6 years of numerical simulations, and even I don't have a good understanding of IEEE-754). Or the number of bits in the mantissa, and how that translates to decimal precision? This is even before we go into
+0.0being different from-0.0, and+Infversus-Inf, and of course theNaNthat will eventually bite them in the ass? We haven't even covered subnormal numbers.3)) After learning all that, most people will still make the mistake of using equality to compare floating point numbers, i.e.
if x == y. That is almost always a mistake, but not always a mistake unfortunately.My preference is that the
floattype should not be used in a date-time library. It will bite people with hard to debug bugs.Among the major date-time libraries that I have some familiarity with, I can't recall seeing a float type used: Noda Time, java.time, libc, go.time, C++ std::chrono. The exception is the Python
datetimelibrary, which of course has design defects thatwheneveris trying to fix. I think one of the things that should not carry over is the use of afloattype.The removal of the
floattype means that all of theround()functions can be removed. I think that's a good thing. Less code to maintain and document.If someone wanted to get a
floatfrom anInstant, my preference is that that they callInstant.nanoseconds()then divide by1e9. This way, the user is explicitly invoking a float type, and they are consciously opting into all the problems associated with a float type.Beta Was this translation helpful? Give feedback.
All reactions