10

I was looking at these related families of standard functions in cppreference: std::round, std::floor, std::ceil and std::trunc.

Is there any reason why std::round is the only one providing specific signatures for long and long long as return type? I am struggling to think of any reason other than historical, but std::round has been added fairly recently with C++11.

Boann
  • 48,794
  • 16
  • 117
  • 146
Alessandro Teruzzi
  • 3,918
  • 1
  • 27
  • 41
  • [This isn't 100% a duplicate, but it answers your question](https://stackoverflow.com/a/605544/8746648). (First google result for "return value of `std::floor`") – asynts Nov 02 '21 at 17:20
  • 1
    Returning integers doesn't make sense. `std::round` just has it for some reason. – asynts Nov 02 '21 at 17:28
  • @asynts it is an intersting read, but it doesn't answer my question, I would image the same restriction highlighted in the question would apply for std::round, but somehow the standard library offers specific functions for it and not for floor. – Alessandro Teruzzi Nov 02 '21 at 17:29
  • In case of `std::floor` the straight up implicit casting does the job. So why write a function that would do it? – ALX23z Nov 02 '21 at 17:54
  • 6
    most likely because c had the [exact same set of functions](https://en.cppreference.com/w/c/numeric/math/round), but it would be interesting to know why c didn't add the `long` / `long long` versions for `ceil` / `floor` / `trunc`. – Turtlefight Nov 02 '21 at 18:08
  • 1
    @ALX23z And by `std::floor` you mean `std::trunc`, right? :-) The difference is that with casting you can overflow, i.e. UB, and with `std::trunc` you can't. – danadam Nov 03 '21 at 00:40

1 Answers1

0

This is just my guessing but I think the reason might be, that l and ll versions of floor, ceil and trunc can be realized using rint and different rounding modes. For example, llfloor() could be implemented like this:

#include <cfenv>
#include <cmath>
#pragma STDC FENV_ACCESS ON

long long llfloor(double arg) {
    auto save_round = std::fegetround();
    std::fesetround(FE_DOWNWARD);
    auto ret = llrint(arg);
    std::fesetround(save_round);
    return ret;
}

One nice property of l/ll versions is that they raise FE_INVALID exception when the result is outside the range of the result type. Our llfloor() does that too:

#include <iostream>
#include <limits>

int main() {
    double input = std::nextafter(std::numeric_limits<long long>::max(), INFINITY);
    std::cout << "input  =  " << std::fixed << input << "\n";

    std::feclearexcept(FE_ALL_EXCEPT);
    auto result = llfloor(input);
    if (std::fetestexcept(FE_INVALID)) {
        std::cout <<"FE_INVALID was raised\n";
    }
    std::cout << "result = " << result << "\n";
}

and the output is (or see in godbolt):

input  =  9223372036854777856.000000
FE_INVALID was raised
result = -9223372036854775808

You may still be asking "Can't llround be implemented with llrint?". Turns out it can't. The FE_TONEAREST rounding mode does rounding to even for halfway cases, while round does away-from-zero.

Also be aware that the compiler support for accessing or modifying the floating point environment might not be fully there yet:

main.cpp:3: warning: ignoring ‘#pragma STDC FENV_ACCESS’ [-Wunknown-pragmas]
    3 | #pragma STDC FENV_ACCESS ON

(related question: Adding two floating-point numbers )

danadam
  • 3,350
  • 20
  • 18