5
\$\begingroup\$

I am trying to make an is_integer template function to determine a number is an integer or not.

The experimental implementation

  • is_integer Template Function Implementation

    template<typename T, typename TestT = double>
    constexpr bool is_integer(T input)
    {
        TestT floor_input = std::floor(static_cast<TestT>(input));
        if (std::abs(floor_input - input) <= std::numeric_limits<T>::epsilon() )
        {
            return true;
        }
        return false;
    }
    

Full Testing Code

The full testing code:

//  An is_integer Template Function Implementation in C++

#include <cassert>
#include <chrono>
#include <cmath>
#include <iostream>
#include <vector>

template<typename T, typename TestT = double>
constexpr bool is_integer(T input)
{
    TestT floor_input = std::floor(static_cast<TestT>(input));
    if (std::abs(floor_input - input) <= std::numeric_limits<T>::epsilon() )
    {
        return true;
    }
    return false;
}

void isIntegerTest();

int main()
{
    auto start = std::chrono::system_clock::now();
    isIntegerTest();
    auto end = std::chrono::system_clock::now();
    std::chrono::duration<double> elapsed_seconds = end - start;
    std::time_t end_time = std::chrono::system_clock::to_time_t(end);
    std::cout << "Computation finished at " << std::ctime(&end_time) << "elapsed time: " << elapsed_seconds.count() << '\n';
    return 0;
}

void isIntegerTest()
{
    assert(is_integer(1) == true);
    assert(is_integer(2) == true);
    assert(is_integer(3) == true);
    assert(is_integer(1.1) == false);

    float test_number1 = 1.2;
    assert(is_integer(test_number1) == false);
    test_number1 = 1;
    assert(is_integer(test_number1) == true);

    double test_number2 = 2;
    assert(is_integer(test_number2) == true);
    test_number2 = 2.0001;
    assert(is_integer(test_number2) == false);

    return;
}

Godbolt link is here.

All suggestions are welcome.

\$\endgroup\$

2 Answers 2

8
\$\begingroup\$

This is wrong for several reasons.

What do you mean by "is an integer"?

There are many ways to interpret the assertion that some number "is an integer". Do you mean it's an integer type? If so you could use the concept std::integral<T>, but that's obviously not what you mean.

Next is checking if a given number can be stored without loss of precision in an integer variable. That is actually a very useful definition. However, your test would fail that because input might be much larger than can be stored even in a std::uintmax_t.

Then you can check if a given number is actually exactly an integer. I am assuming that that is what you want to achieve here. Luckily that is not too hard, but see below.

Finally, it could be that you have done some floating point operations and want to know if the result is an integer, but due to the inexact nature of floating point it might not come out as an exact integer. In that case, the right tolerance to use actually depends on which and how many operations you did on the numbers before getting the final answer, as every operation increases the error. In any case:

Don't use epsilon

epsilon is the smallest possible difference between floating point numbers that are in the range of 1 to 2. However, consider that input might be much larger than 1, in which case the smallest difference between input and floor_input will be much larger than epsilon. And if input is very close to 0, the opposite will be the case.

You could scale epsilon based on the exponent of input, as shown in this example. But even then it is not necessary:

Use std::modf()

Due to the way the floating point number format IEEE 754 works, you can tell exactly if a given float or double is an integer or not. One way would be to just cast the number to an integer and compare it with the floating point version:

template<typename T>
constexpr bool is_integer(T input)
{
    return static_cast<intmax_t>(input) == input;
}

Which works well until input is larger than intmax_t can handle. Luckily, an even better way is available by using std::modf(), which splits a floating point value into an integer and fractional part:

template<std::floating_point T>
constexpr bool is_integer(T input)
{
    T integer_part;
    return std::modf(input, &integer_part) == 0;
}

template<std::integral T>
constexpr bool is_integer(T input)
{
    return true;
}

This also returns true for +INFINITY and -INFINITY. Is infinity an integer though? On the other hand, all floating point values with an exponent larger than the size of the mantissa in bits are integers, so treating infinity as integer might be considered reasonable.

What about other numeric types?

There are more numeric types than just integers and floating point numbers. What about std::complex? Maybe you are using a library that provides rational numbers? Either you could try in some way to make the code even more generic such that it handles those cases, or you could just forbid those types by requiring that std::is_arithmetic_v<T>.

Unnecessary use of if

Whenever you have a piece of code that looks like:

if (condition)
    return true;
else
    return false;

Just replace that with:

return condition;
\$\endgroup\$
0
3
\$\begingroup\$

The tests miss most of the important cases

The tests only consider a small range of inputs. In particular, we're not testing any of the following possible inputs:

  • doubles with zero fractional part: 2.0
  • unsigned integer types: 1u
  • negative values (of a selection of types): -1, -1.0, -1.1
  • maximum positive/negative doubles: DBL_MAX, -DBL_MAX
  • small values: DBL_MIN, LDBL_TRUE_MIN
  • long doubles: LDBL_MAX, -LDBL_MAX
  • zeros: -0.0, 0.0
  • NaN values: 0./0.
  • infinities: 1./0., -1./0.

Some of these tests will expose errors in the implementation, and some may prompt questions about the specification - those are opportunities to clarify the intent.

No need for std::abs()

std::floor() always returns a value that's not greater than the input. So if we reverse the subtraction (input - floor_input) the result will never be negative. But as G. Sliepen's answer shows, we probably shouldn't be comparing with epsilon anyway. And take care with NaNs and infinities!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.