Created on 2020-11-18.00:00:00 last changed 2 months ago
Proposed resolution:
This wording is relative to N4988.
Modify [time.duration.cast] as indicated:
template<class ToDuration, class Rep, class Period> constexpr ToDuration floor(const duration<Rep, Period>& d);-4- Constraints: `ToDuration` is a specialization of `duration`.
-5-
Returns: The greatest result `t` representable in `ToDuration` for whicht <= d
.Effects: Equivalent to:
auto t = duration_cast<ToDuration>(d); if constexpr (treat_as_floating_point_v<typename ToDuration::rep>) return t; else if (t <= d) return t; else return --t;
template<class ToDuration, class Rep, class Period> constexpr ToDuration ceil(const duration<Rep, Period>& d);-6- Constraints: `ToDuration` is a specialization of `duration`.
-7-
Returns: The least result `t` representable in `ToDuration` for whicht >= d
.Effects: Equivalent to:
auto t = duration_cast<ToDuration>(d); if constexpr (treat_as_floating_point_v<typename ToDuration::rep>) return t; else if (t >= d) return t; else return ++t;
[ 2024-09-19; Jonathan adds a note ]
Another problem discovered by STL occurs when the result is floating-point. We can't just add 1. In fact, there is no requirement for whole-numberness.
For example, when converting from `double` to `float`:
chrono::floor<duration<float>>(duration<double>(0.1))
This produces the result duration<float>(-0.9f)
with the reference implementation in P0092R1,
and the implementations in libstdc++, libc++, and MSVC.
This is because 0.1f <= 0.1
is false,
so the result is duration<float>(0.1f - 1.0f)
,
which is not the greatest value representable that is not greater than `1.0`.
The correct result according to the standard would be
duration<float>(nexttoward(0.1f, -HUGE_VAL))
,
but we can't use `nexttoward` for arbitrary `treat_as_floating_point` types,
only for `float`, `double` and `long double`.
STL found cases where
ceil<duration<float>>(duration<double>(x))
produces a value that is lower than `x`, e.g. for `x = 13421771263.0`
the result is `13421770752.0f`.
A possible resolution for this problem would be to make `ceil` and `floor` behave exactly like `duration_cast` when the result is a floating-point type. This would still permit a `ceil` that is smaller than the input (and a `floor` result that is larger) but that's just a consequence of converting to a floating-point type with less precision. We could also specify that for non-floating-point result types, the effects should be what all known implementations do. That would mean the behaviour is at least predictable and explainable, even if the result is not always the correct mathematical value.
[ 2020-11-29; Reflector prioritization ]
Set priority to 3 during reflector discussions.
[time.duration.cast] p7 requires that the return value is "The least result t representable in ToDuration for which t >= d".
This means that chrono::ceil<chrono::microseconds>(chrono::duration<float, milli>(m)).count() is required to be the smallest integer n such that (float)n == m*1000.0f, which might be less than the mathematically correct value of m × 1000. (The specific values below assume float uses the IEEE binary32 format and default rounding, but similar problems will exist for other formats, even if the specific values are different.) For example, if m == 13421772.0f then the naively expected result is n == 13421772000, but the standard requires n == 13421771265, a significantly lower value. This surprising result is a consequence of how the chrono::ceil spec interacts with floating-point arithmetic, due to the fact that for the integers in the range [13421770753, 13421772799], only one can be exactly represented as 32-bit float. All but that one will be rounded to a different value when converted to float. A straightforward implementation of chrono::ceil will produce (long long)(13421772.0f * 1000) which is 13421771776, which is less than the expected result, but compares equal using the t >= d expression. That expression converts both operands to their common_type, which is chrono::duration<float, micro>. That means we compare (float)13421771776 >= (13421772.0f * 1000) which is true. But the spec requires an even worse result. All integers in [13421771265, 13421771776) are also rounded to that value when converted to float. That means chrono::microseconds(13421771265) is "the least result representable in ToDuration for which t >= d". Meeting the "least result" requirement is impractical, and unhelpful. The straightforward result 13421771776 is already lower than the naively expected result (which is surprising for a "ceil" function). To meet the standard's requirements the implementation would have to do extra work, just to produce an even lower (and even more surprising) result. It might be impractical to require the naively expected value to be returned (the additional work might have unacceptable performance implications), but the standard should at least permit the straightforward result instead of requiring an even worse one. The same problem almost certainly exists for chrono::floor in reverse.History | |||
---|---|---|---|
Date | User | Action | Args |
2024-09-19 11:37:02 | admin | set | messages: + msg14389 |
2024-09-19 11:37:02 | admin | set | messages: + msg14388 |
2020-11-29 13:57:16 | admin | set | messages: + msg11634 |
2020-11-18 00:00:00 | admin | create |