8

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.

orlp
  • 112,504
  • 36
  • 218
  • 315
  • The requirement " for this question I'm only interested in answers that are backed up by guarantees from the C++ standard" is unreasonable and silly for this question. It's akin to asking for a way to implement bubble sort and require that answers be backed up by the C++ standard. The C++ standard has nothing to say about bubble sort. – Cheers and hth. - Alf Apr 27 '15 at 03:04
  • `i < f` is a "standards-compliant" way to find out if `i` is smaller than `f`. – Barry Apr 27 '15 at 03:13
  • 3
    @Cheersandhth.-Alf I disagree. The entire point of the question is to find a method that is well-defined on any C++ implementation. You can do this for bubble sort, but I'm not sure you can for this comparison, hence my question. – orlp Apr 27 '15 at 03:14
  • @Barry Yet it fails for the counterexamples in my question - so that's clearly not right. – orlp Apr 27 '15 at 03:15
  • @orlp: not sure if you understand what you're asking for. e.g. are you aware that the standard supports decimal floating point representation? do you want that covered? are you aware that the standard (most probably a mishap) doesn't require symmetric integer ranges? do you want that covered? are you aware that the standard doesn't even define e.g. multiplication? do you want that covered? in short, there's a very long distance between "a method that is well-defined on any [extant] C++ implementation", and formally guaranteed. – Cheers and hth. - Alf Apr 27 '15 at 03:25
  • @Cheersandhth.-Alf Yes I'm aware. That's why I suspect the answer to this question is 'no'. I don't think that makes the question unreasonable however. Perhaps we could make the question a bit more interesting by not just assuming `std::is_floating_point`, but also `std::numeric_limits::is_iec559`? – orlp Apr 27 '15 at 03:35
  • For arbitrary types this is a hard problem. For types where you can determine the limits ahead of time, it's much easier. – Mark Ransom Apr 27 '15 at 04:09

2 Answers2

2
  1. If f is outside the range of I, you can tell the result just by its sign.

  2. If f is inside the range of I but too big to have a fractional part, compare it as an integer.

  3. 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

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • … ehh, this code only works for *signed* integers. You can add an overload for unsigned integers by stripping out all the sign business. – Potatoswatter Jun 20 '15 at 04:19
0

This is how I would do it:

I presume that f is finite, the cases of infinite and NaN are to be handled elsewhere.

  1. compare f and F(i), if not equal, you're done, f and i are either < or >

  2. if equal, then compare I(f) and i

The only assumptions are:

  • if there is a float having exactly the value i, then F(i) gives that value

  • if there is an integer having exactly same value as f, then I(f) gives that value

  • monotonicity of functions F and I

EDIT

To be more explicit, above tricks are for writing a comparison function, not just testing equality...

floatType F(intType i);
intType I(floatType f);
int cmpfi(floatType f,intType i)
{
  assert(isfinite(f));
  if(f < F(i)) return -1;
  if(f == F(i))
  {
    if( I(f) < i ) return -1;
    return I(f) > i;
  }
  return 1;
}

Up to you to transform this draft into a C++ code that can handle several different floatType/intType

aka.nice
  • 9,100
  • 1
  • 28
  • 40
  • 1
    I'm looking for an ordering test, not equality. – orlp Apr 27 '15 at 16:39
  • @orlp, I did not detail the answer, but these hints are for writing a sort of cmp function – aka.nice Apr 27 '15 at 16:42
  • 1
    Well, in step 1 you said that "you're done" after finding out that `f != i`. That clearly does not solve the problem :) – orlp Apr 27 '15 at 16:43
  • @orlp, ok, some more details to help you extrapolate my thoughts – aka.nice Apr 27 '15 at 16:51
  • I unaccepted because I found out [the exact requirements of the standard](http://i.imgur.com/jGVGoD4.png) - it's very close, but subtly different around `std::numeric_limits::max()`. – orlp Apr 28 '15 at 05:44
  • This answer in the current state doesn't solve the problem. `I(f)` could be undefined behavior, see this question: http://stackoverflow.com/questions/29932335/is-round-trip-through-floating-point-always-defined-behavior-if-floating-point-r . – orlp Apr 29 '15 at 01:06
  • @orlp You are right, there is a problem when F(i) == F(imax), because I(F(i)) might overflow. Since there is NO standard way to detect overflow in post-condition, that means some pre-condition tests, and the action gonna be crooked as usual... – aka.nice Apr 29 '15 at 16:49