Let's say I have two objects i
and f
of respective types I
and F
. I know that std::is_integral<I>::value
is true, and std::is_floating_point<F>::value
is true.
Is there a fully standards-compliant way to find out if the value of i
is smaller than the value of f
? Note the emphasis on 'fully standards-compliant', for this question I'm only interested in answers that are backed up by guarantees from the C++ standard.
The trivial implementation i < I(f)
doesn't work, because the value of f
may not fit inside i
. The trivial implementation F(i) < f
doesn't work either, because the precision of f
may not be enough to represent i
, causing i
to get rounded to a value equal to f
(if you have IEEE754 floats, 16777219 < 16777220.f
fails).
But here comes the real dilemma: if you want to use std::numeric_limits::max
to alleviate these problems, your back to the original problem of comparing floats and integers! This is because the type of std::numeric_limits::max
is equal to the original type.
Integers and floats are two different kinds of numerical data. An integer (more commonly called an int) is a number without a decimal point. A float is a floating-point number, which means it is a number that has a decimal place. Floats are used when more precision is needed.
To compare two floating point values, we have to consider the precision in to the comparison. For example, if two numbers are 3.1428 and 3.1415, then they are same up to the precision 0.01, but after that, like 0.001 they are not same.
Casting the int to a float explicitly will do absolutely nothing. The int will be promoted to a float for purposes of comparison anyway.
Comparing for equality Floating point math is not exact. Simple values like 0.1 cannot be precisely represented using binary floating point numbers, and the limited precision of floating point numbers means that slight changes in the order of operations or the precision of intermediates can change the result.
If f
is outside the range of I
, you can tell the result just by its sign.
If f
is inside the range of I
but too big to have a fractional part, compare it as an integer.
Otherwise, it's safe to cast i
to F
because the rounding will not change the result of the comparison: f
is already smaller than any value of I
that would be rounded.
.
template< typename I, typename F >
std::enable_if_t< std::is_integral_v< I > && std::is_floating_point_v< F >,
bool > less( I i, F f ) {
// Return early for operands of different signs.
if ( i < 0 != f < 0 ) return i < 0;
bool rev = i >= 0;
if ( rev ) {
f = - f; // Make both operands non-positive.
i = - i; // (Negativity avoids integer overflow here.)
}
if ( f < /* (F) */ std::numeric_limits<I>::min() ) {
// |i| < |f| because f is outside the range of I.
return rev;
}
if ( f * std::numeric_limits<F>::epsilon() <= -1 ) {
// f must be an integer (in I) because of limited precision in F.
I fi = f;
return rev? fi < i : i < fi;
}
// |f| has better than integer precision.
// If (F) |i| loses precision, it will still be greater than |f|.
return rev? f < i : i < f;
}
Demo: http://coliru.stacked-crooked.com/a/b5c4bea14bc09ee7
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With