By zbrojny120


2019-04-09 10:11:26 8 Comments

How can two versions of the same function, differing only in one being inline and the other one not, return different values? Here is some code I wrote today and I am not sure how it works.

#include <cmath>
#include <iostream>

bool is_cube(double r)
{
    return floor(cbrt(r)) == cbrt(r);
}

bool inline is_cube_inline(double r)
{
    return floor(cbrt(r)) == cbrt(r);
}

int main()
{
    std::cout << (floor(cbrt(27.0)) == cbrt(27.0)) << std::endl;
    std::cout << (is_cube(27.0)) << std::endl;
    std::cout << (is_cube_inline(27.0)) << std::endl;
}

I would expect all outputs to be equal to 1, but it actually outputs this (g++ 8.3.1, no flags):

1
0
1

instead of

1
1
1

Edit: clang++ 7.0.0 outputs this:

0
0
0

and g++ -Ofast this:

1
1
1

2 comments

@P.W 2019-04-09 11:06:22

As observed, using the == operator to compare floating point values has resulted in different outputs with different compilers and at different optimization levels.

One good way to compare floating point values is the relative tolerance test outlined in the article: Floating-point tolerances revisited.

We first calculate the Epsilon (the relative tolerance) value which in this case would be:

double Epsilon = std::max(std::cbrt(r), std::floor(std::cbrt(r))) * std::numeric_limits<double>::epsilon();

And then use it in both the inline and non-inline functions in this manner:

return (std::fabs(std::floor(std::cbrt(r)) - std::cbrt(r)) < Epsilon);

The functions now are:

bool is_cube(double r)
{
    double Epsilon = std::max(std::cbrt(r), std::floor(std::cbrt(r))) * std::numeric_limits<double>::epsilon();    
    return (std::fabs(std::floor(std::cbrt(r)) - std::cbrt(r)) < Epsilon);
}

bool inline is_cube_inline(double r)
{
    double Epsilon = std::max(std::cbrt(r), std::floor(std::cbrt(r))) * std::numeric_limits<double>::epsilon();
    return (std::fabs(std::round(std::cbrt(r)) - std::cbrt(r)) < Epsilon);
}

Now the output will be as expected ([1 1 1]) with different compilers and at different optimization levels.

Live demo

@Ken Thomases 2019-04-10 02:55:12

What's the purpose of the max() call? By definition, floor(x) is less than or equal to x, so max(x, floor(x)) will always equal x.

@P.W 2019-04-10 04:10:47

@KenThomases: In this particular case, where one argument to max is just the floor of the other, it is not required. But I considered a general case where arguments to max can be values or expressions which are independent of each other.

@Peter A. Schneider 2019-04-10 10:13:36

Shouldn't operator==(double, double) do exactly that, check for the difference being smaller than a scaled epsilon? About 90% of floating point related questions on SO wouldn't exist then.

@P.W 2019-04-10 10:20:25

I think it is better if the user gets to specify the Epsilon value depending on their particular requirement.

@J. Antonio Perez 2019-04-09 10:25:06

Explanation

Some compilers (notably GCC) use higher precision when evaluating expressions at compile time. If an expression depends only on constant inputs and literals, it may be evaluated at compile time even if the expression is not assigned to a constexpr variable. Whether or not this occurs depends on:

  • The complexity of the expression
  • The threshold the compiler uses as a cutoff when attempting to perform compile time evaluation
  • Other heuristics used in special cases (such as when clang elides loops)

If an expression is explicitly provided, as in the first case, it has lower complexity and the compiler is likely to evaluate it at compile time.

Similarly, if a function is marked inline, the compiler is more likely to evaluate it at compile time because inline functions raise the threshold at which evaluation can occur.

Higher optimization levels also increase this threshold, as in the -Ofast example, where all expressions evaluate to true on gcc due to higher precision compile-time evaluation.

We can observe this behavior here on compiler explorer. When compiled with -O1, only the function marked inline is evaluated at compile-time, but at -O3 both functions are evaluated at compile-time.

NB: In the compiler-explorer examples, I use printf instead iostream because it reduces the complexity of the main function, making the effect more visible.

Demonstrating that inline doesn’t affect runtime evaluation

We can ensure that none of the expressions are evaluated at compile time by obtaining value from standard input, and when we do this, all 3 expressions return false as demonstrated here: https://ideone.com/QZbv6X

#include <cmath>
#include <iostream>

bool is_cube(double r)
{
    return floor(cbrt(r)) == cbrt(r);
}
 
bool inline is_cube_inline(double r)
{
    return floor(cbrt(r)) == cbrt(r);
}

int main()
{
    double value;
    std::cin >> value;
    std::cout << (floor(cbrt(value)) == cbrt(value)) << std::endl; // false
    std::cout << (is_cube(value)) << std::endl; // false
    std::cout << (is_cube_inline(value)) << std::endl; // false
}

Contrast with this example, where we use the same compiler settings but provide the value at compile-time, resulting in the higher-precision compile-time evaluation.

Related Questions

Sponsored Content

0 Answered Questions

C++ - Odd Reciprocal Inequivalence

12 Answered Questions

[SOLVED] When should I write the keyword 'inline' for a function/method?

1 Answered Questions

[SOLVED] Problems benchmarking simple code with googlebenchmark

  • 2017-02-14 20:21:07
  • eleanora
  • 210 View
  • 6 Score
  • 1 Answer
  • Tags:   c++

14 Answered Questions

[SOLVED] Benefits of inline functions in C++?

  • 2008-09-28 13:35:23
  • Lennie De Villiers
  • 163741 View
  • 248 Score
  • 14 Answer
  • Tags:   c++ inline-functions

3 Answered Questions

[SOLVED] Function overloading with different return types

1 Answered Questions

[SOLVED] How to implement StringBuilder class which to be able to accept IO manipulators

  • 2015-01-16 15:47:42
  • bobeff
  • 506 View
  • 0 Score
  • 1 Answer
  • Tags:   c++ stl

0 Answered Questions

std::atomic_is_lock_free(shared_ptr<T>*) didn't compile

  • 2014-02-10 05:32:27
  • 大宝剑
  • 994 View
  • 5 Score
  • 0 Answer
  • Tags:   c++ c++11

2 Answered Questions

[SOLVED] setw within a function to return an ostream

5 Answered Questions

[SOLVED] The meaning of static in C++

Sponsored Content