<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Issue 3503: chrono::ceil has surprising requirement</title>
<meta property="og:title" content="Issue 3503: chrono::ceil has surprising requirement">
<meta property="og:description" content="C++ library issue. Status: New">
<meta property="og:url" content="https://cplusplus.github.io/LWG/issue3503.html">
<meta property="og:type" content="website">
<meta property="og:image" content="http://cplusplus.github.io/LWG/images/cpp_logo.png">
<meta property="og:image:alt" content="C++ logo">
<style>
  p {text-align:justify}
  li {text-align:justify}
  pre code.backtick::before { content: "`" }
  pre code.backtick::after { content: "`" }
  blockquote.note
  {
    background-color:#E0E0E0;
    padding-left: 15px;
    padding-right: 15px;
    padding-top: 1px;
    padding-bottom: 1px;
  }
  ins {background-color:#A0FFA0}
  del {background-color:#FFA0A0}
  table.issues-index { border: 1px solid; border-collapse: collapse; }
  table.issues-index th { text-align: center; padding: 4px; border: 1px solid; }
  table.issues-index td { padding: 4px; border: 1px solid; }
  table.issues-index td:nth-child(1) { text-align: right; }
  table.issues-index td:nth-child(2) { text-align: left; }
  table.issues-index td:nth-child(3) { text-align: left; }
  table.issues-index td:nth-child(4) { text-align: left; }
  table.issues-index td:nth-child(5) { text-align: center; }
  table.issues-index td:nth-child(6) { text-align: center; }
  table.issues-index td:nth-child(7) { text-align: left; }
  table.issues-index td:nth-child(5) span.no-pr { color: red; }
  @media (prefers-color-scheme: dark) {
     html {
        color: #ddd;
        background-color: black;
     }
     ins {
        background-color: #225522
     }
     del {
        background-color: #662222
     }
     a {
        color: #6af
     }
     a:visited {
        color: #6af
     }
     blockquote.note
     {
        background-color: rgba(255, 255, 255, .10)
     }
  }
</style>
</head>
<body>
<hr>
<p><em>This page is a snapshot from the LWG issues list, see the <a href="lwg-active.html">Library Active Issues List</a> for more information and the meaning of <a href="lwg-active.html#New">New</a> status.</em></p>
<h3 id="3503"><a href="lwg-active.html#3503">3503</a>. <code>chrono::ceil</code> has surprising requirement</h3>
<p><b>Section:</b> 30.5.8 <a href="https://wg21.link/time.duration.cast">[time.duration.cast]</a> <b>Status:</b> <a href="lwg-active.html#New">New</a>
 <b>Submitter:</b> Jonathan Wakely <b>Opened:</b> 2020-11-18 <b>Last modified:</b> 2024-09-19</p>
<p><b>Priority: </b>3
</p>
<p><b>View all other</b> <a href="lwg-index.html#time.duration.cast">issues</a> in [time.duration.cast].</p>
<p><b>View all issues with</b> <a href="lwg-status.html#New">New</a> status.</p>
<p><b>Discussion:</b></p>
<p>
30.5.8 <a href="https://wg21.link/time.duration.cast">[time.duration.cast]</a> p7 requires that the return value is "The least result <code>t</code>
representable in <code>ToDuration</code> for which <code>t &gt;= d</code>".
<p/>
This means that <code>chrono::ceil&lt;chrono::microseconds&gt;(chrono::duration&lt;float, milli&gt;(m)).count()</code>
is required to be the smallest integer <code>n</code> such that <code>(float)n == m*1000.0f</code>, which might be less
than the mathematically correct value of <code>m &times; 1000</code>.
<p/>
(The specific values below assume <code>float</code> uses the IEEE binary32 format and default rounding, but
similar problems will exist for other formats, even if the specific values are different.)
<p/>
For example, if <code>m == 13421772.0f</code> then the naively expected result is <code>n == 13421772000</code>, but
the standard requires <code>n == 13421771265</code>, a significantly lower value. This surprising result is a
consequence of how the <code>chrono::ceil</code> spec interacts with floating-point arithmetic, due to the fact that
for the integers in the range <code>[13421770753, 13421772799]</code>, only one can be exactly represented as
32-bit <code>float</code>. All but that one will be rounded to a different value when converted to <code>float</code>.
<p/>
A straightforward implementation of <code>chrono::ceil</code> will produce <code>(long long)(13421772.0f * 1000)</code>
which is <code>13421771776</code>, which is less than the expected result, but compares equal using the <code>t &gt;= d</code>
expression. That expression converts both operands to their <code>common_type</code>, which is
<code>chrono::duration&lt;float, micro&gt;</code>. That means we compare <code>(float)13421771776 &gt;= (13421772.0f * 1000)</code>
which is <code>true</code>. But the spec requires an even worse result. All integers in <code>[13421771265, 13421771776)</code>
are also rounded to that value when converted to <code>float</code>. That means <code>chrono::microseconds(13421771265)</code>
is "the least result representable in <code>ToDuration</code> for which <code>t &gt;= d</code>".
<p/>
Meeting the "least result" requirement is impractical, and unhelpful. The straightforward result <code>13421771776</code>
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.
<p/>
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.
<p/>
The same problem almost certainly exists for <code>chrono::floor</code> in reverse.
</p>

<p><i>[2020-11-29; Reflector prioritization]</i></p>

<p>
Set priority to 3 during reflector discussions.
</p>
<p><i>[2024-09-19; Jonathan adds a note]</i></p>

<p>
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.
</p>
<p>
For example, when converting from <code class='backtick'>double</code> to <code class='backtick'>float</code>:
<pre>
<code>chrono::floor&lt;duration&lt;float&gt;&gt;(duration&lt;double&gt;(0.1))</code>
</pre>
This produces the result <code>duration&lt;float&gt;(-0.9f)</code>
with the reference implementation in <a href="https://wg21.link/P0092R1" title=" Polishing">P0092R1</a>,
and the implementations in libstdc++, libc++, and MSVC.
This is because <code>0.1f &lt;= 0.1</code> is false,
so the result is <code>duration&lt;float&gt;(0.1f - 1.0f)</code>,
which is not the greatest value representable that is not greater than <code class='backtick'>1.0</code>.
The correct result according to the standard would be
<code>duration&lt;float&gt;(nexttoward(0.1f, -HUGE_VAL))</code>,
but we can't use <code class='backtick'>nexttoward</code> for arbitrary <code class='backtick'>treat_as_floating_point</code> types,
only for <code class='backtick'>float</code>, <code class='backtick'>double</code> and <code class='backtick'>long double</code>.
</p>
<p>
STL found cases where
<code>ceil&lt;duration&lt;float&gt;&gt;(duration&lt;double&gt;(x))</code>
produces a value that is lower than <code class='backtick'>x</code>, e.g. for <code class='backtick'>x = 13421771263.0</code>
the result is <code class='backtick'>13421770752.0f</code>.
</p>
<p>
A possible resolution for this problem would be to make <code class='backtick'>ceil</code> and <code class='backtick'>floor</code>
behave exactly like <code class='backtick'>duration_cast</code> when the result is a floating-point type.
This would still permit a <code class='backtick'>ceil</code> that is smaller than the input
(and a <code class='backtick'>floor</code> 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.
</p>


<p id="res-3503"><b>Proposed resolution:</b></p>
<p>
This wording is relative to <a href="https://wg21.link/N4988" title=" Working Draft, Programming Languages — C++">N4988</a>.
</p>
<ol>
<li><p>Modify 30.5.8 <a href="https://wg21.link/time.duration.cast">[time.duration.cast]</a> as indicated:</p>
<blockquote>
<pre>
template&lt;class ToDuration, class Rep, class Period&gt;
  constexpr ToDuration floor(const duration&lt;Rep, Period&gt;&amp; d);
</pre>
<p>-4-
<i>Constraints</i>: <code class='backtick'>ToDuration</code> is a specialization of <code class='backtick'>duration</code>.
</p>
<p>-5-
<del>
<i>Returns</i>:
The greatest result <code class='backtick'>t</code> representable in <code class='backtick'>ToDuration</code> for which
<code>t &lt;= d</code>.
</del>
</p>
<p>
<ins><i>Effects</i>: Equivalent to:</ins>
<pre><code><ins>auto t = duration_cast&lt;ToDuration&gt;(d);
if constexpr (treat_as_floating_point_v&lt;typename ToDuration::rep&gt;)
  return t;
else if (t &lt;= d)
  return t;
else
  return --t;
</ins></code></pre>
</p>

<pre>
template&lt;class ToDuration, class Rep, class Period&gt;
  constexpr ToDuration ceil(const duration&lt;Rep, Period&gt;&amp; d);
</pre>
<p>-6-
<i>Constraints</i>: <code class='backtick'>ToDuration</code> is a specialization of <code class='backtick'>duration</code>.
</p>
<p>-7-
<del>
<i>Returns</i>:
The least result <code class='backtick'>t</code> representable in <code class='backtick'>ToDuration</code> for which
<code>t &gt;= d</code>.
</del>
</p>
<p>
<ins><i>Effects</i>: Equivalent to:</ins>
<pre><code><ins>auto t = duration_cast&lt;ToDuration&gt;(d);
if constexpr (treat_as_floating_point_v&lt;typename ToDuration::rep&gt;)
  return t;
else if (t &gt;= d)
  return t;
else
  return ++t;
</ins></code></pre>
</p>
</blockquote>
</li>
</ol>






</body>
</html>
