2

I have stumbled upon quirky C++ behavior that I cannot explain.

I am trying to calculate the dimensions of an image when resizing it (maintaining its ratio) to fit as much screen as possible. The x, y variables are dimensions of the image and X, Y variables are dimensions of the screen. When the resulting dimensions are not integers, I need to round them using standard rules of mathematics.

This program for inputs 499999999999999999 10 1000000000000000000 19 gives the (incorrect) answer of 950000000000000000 19.

#include <iostream>
#include <cmath>
#include <algorithm>

int main()
{
    long long x, y, X, Y;
    std::cin >> x >> y >> X >> Y;

    long double ratio = std::min((long double)X/x, (long double)Y/y);

    x = round(ratio*x);
    y = round(ratio*y);

    std::cout << x << " " << y << std::endl;
    return 0;
}

However, code below (only change is using namespace std; and removing std:: from main function body) gives the correct answer of 949999999999999998 19.

#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;

int main()
{
    long long x, y, X, Y;
    cin >> x >> y >> X >> Y;

    long double ratio = min((long double)X/x, (long double)Y/y);

    x = round(ratio*x);
    y = round(ratio*y);

    cout << x << " " << y << endl;
    return 0;
}

I am using g++ (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0 and compiling the program with g++ -std=c++17 -Wall -Wextra -Wshadow.

k-dx
  • 45
  • 5
  • To add more to this: It also doesn't work when I change the std::min(...) to a simple if (x < y) check. – k-dx Jun 12 '22 at 00:02
  • 6
    There are multiple `round` functions (from c and from c++). You should maybe take a look at the differences between them. Specifically the parameters they take. – Taekahn Jun 12 '22 at 00:12
  • that's one of the reasons why ["using namespace std" shouldn't be used](https://stackoverflow.com/q/1452721/995714) – phuclv Jun 12 '22 at 02:35
  • 1
    @phuclv In this case the version with `using namespace std;` happens to have the desired behavior. I think the correct lesson here is, additionally, to always qualify standard library functions with `std::` even if their names may be imported into the global namespace (for declarations from C headers). – user17732522 Jun 12 '22 at 02:37

1 Answers1

9

In the first case you are calling double round(double) function from the global namespace, which is pulled in by the cmath header.

In the second case you are calling overloaded long double std::round(long double) function from the std namespace since your are using namespace std.

You can fix your code by adding std:: in front of round, as in:

#include <iostream>
#include <cmath>
#include <algorithm>

int main()
{
    long long x, y, X, Y;
    std::cin >> x >> y >> X >> Y;

    auto ratio = std::min((long double)X / x, (long double)Y / y);

    x = std::round(ratio * x); // <-- add std:: here
    y = std::round(ratio * y); // <-- add std:: here

    std::cout << x << " " << y;
    return 0;
}

UPDATE:

To explain what's going on here, when you call round in the first case, ratio * x is implicitly converted to double (which can only store 15 significant digits) before it is passed to the function.

This leads to loss of precision and an unexpected result in your case. No quirks.

  • Well that makes sense, thank you. But why is [double round(double)](https://en.cppreference.com/w/c/numeric/math/round) called, when it is defined in `math.h` (which I do not include)? It is the 'default' round function to call in C++? – k-dx Jun 12 '22 at 06:14
  • 1
    @k-dx `` may, or may not, include `math.h` to use things from it. Depends on which compiler you use. A C++ header is allowed to include any other header it needs, exactly which ones varies between implementations. – BoP Jun 12 '22 at 07:45