OP's method is a good start. 
(long long)((double)a) == a
Yet has a problem.   E.g. long long a = LLONG_MAX; ((double)a) results is a rounded value exceeding LLONG_MAX.  
The following will certainly not overflow double.
(Pathological exception: LLONG_MIN exceeds -DBL_MAX).
volatile double b = (double) a;
Converting back to long long and testing against a is sufficient to meet OP's goal.  Only need to insure b is in long long range.  @gnasher729 Let us assume 2's complement and double uses FLT_RADIX != 10.  In that case, the lowest long long is a power-of-2 and the highest is a power-of-2 minus 1 and conversion to double can be made exact with careful calculation of the long long limits, as follows.
bool check_ll(long long a) {
  constant double d_longLong_min = LLONG_MIN;
  constant double d_longLong_max_plus_1 = (LLONG_MAX/2 + 1)*2.0;
  volatile double b = (double) a;
  if (b < d_longLong_min || b >= d_longLong_max_plus_1) {
    return false;
  }
  return (long long) b == a;
}
[edit simplify - more general]
A test of b near LLONG_MIN is only needed when long long does not use 2's complement
bool check_ll2(long long a) {
  volatile double b = (double) a;
  constant double d_longLong_max_plus_1 = (LLONG_MAX/2 + 1)*2.0;
  #if LLONG_MIN == -LLONG_MAX
    constant double d_longLong_min_minus_1 = (LLONG_MIN/2 - 1)*2.0;;
    if (b <= d_longLong_min_minus_1 || b >= d_longLong_max_plus_1) {
      return false;
    }
  #else
    if (b >= d_longLong_max_plus_1) {
      return false;
    }
  #endif
  return (long long) b == a;
}
I would not expect a compile to be able to optimize out (long long)((double)a) == a.  IAC, by using an intermediate volatile double, code prevents that.