How can I accurately determine if a double is an integer?

21,072

Solution 1

double can store an exact representation of certain values, such as small integers and (negative or positive) powers of two.

If it does indeed store an exact integer, then ((int)d == d) works fine. And indeed, for any 32-bit integer i, (int)((double)i) == i since a double can exactly represent it.

Note that for very large numbers (greater than about 2**52 in magnitude), a double will always appear to be an integer, as it will no longer be able to store any fractional part. This has implications if you are trying to cast to a Java long, for instance.

Solution 2

How about

 if(d % 1 == 0)

This works because all integers are 0 modulo 1.

Edit To all those who object to this on the grounds of it being slow, I profiled it, and found it to be about 3.5 times slower than casting. Unless this is in a tight loop, I'd say this is a preferable way of working it out, because it's extremely clear what you're testing, and doesn't require any though about the semantics of integer casting.

I profiled it by running time on javac of

class modulo {
    public static void main(String[] args) {
        long successes = 0;
        for(double i = 0.0; i < Integer.MAX_VALUE; i+= 0.125) {
            if(i % 1 == 0)
                successes++;
        }
        System.out.println(successes);
    }
}

VS

class cast {
    public static void main(String[] args) {
        long successes = 0;
        for(double i = 0.0; i < Integer.MAX_VALUE; i+= 0.125) {
            if((int)i == i)
                successes++;
        }
        System.out.println(successes);
    }
}

Both printed 2147483647 at the end.
Modulo took 189.99s on my machine - Cast took 54.75s.

Solution 3

if(new BigDecimal(d).scale() <= 0) {
    //do stuff
}

Solution 4

Your method of using if((int)d == d) should always work for any 32-bit integer. To make it work up to 64 bits, you can use if((long)d == d, which is effectively the same except that it accounts for larger magnitude numbers. If d is greater than the maximum long value (or less than the minimum), then it is guaranteed to be an exact integer. A function that tests whether d is an integer can then be constructed as follows:

boolean isInteger(double d){
    if(d > Long.MAX_VALUE || d < Long.MIN_VALUE){
        return true;
    } else if((long)d == d){
        return true;
    } else {
        return false;
    }
}

If a floating point number is an integer, then it is an exact representation of that integer.

Share:
21,072
asteri
Author by

asteri

Updated on August 25, 2020

Comments

  • asteri
    asteri over 3 years

    Specifically in Java, how can I determine if a double is an integer? To clarify, I want to know how I can determine that the double does not in fact contain any fractions or decimals.

    I am concerned essentially with the nature of floating-point numbers. The methods I thought of (and the ones I found via Google) follow basically this format:

    double d = 1.0;
    if((int)d == d) {
        //do stuff
    }
    else {
        // ...
    }
    

    I'm certainly no expert on floating-point numbers and how they behave, but I am under the impression that because the double stores only an approximation of the number, the if() conditional will only enter some of the time (perhaps even a majority of the time). But I am looking for a method which is guaranteed to work 100% of the time, regardless of how the double value is stored in the system.

    Is this possible? If so, how and why?

  • Hovercraft Full Of Eels
    Hovercraft Full Of Eels over 11 years
    But this ignores how doubles and floating point numbers work. Not a good suggestion.
  • Squidly
    Squidly over 11 years
    I'm sorry, why is this not a good suggestion? It doesn't ignore how doubles and floats work. When I say integers, I mean the mathematical objects, not the data type.
  • Vishy
    Vishy over 11 years
    +1 double which has a 53-bit mantissa can represent every 32-bit int without error. It can represent most long values, but not all. Casting to (long) may be preferable. e.g. (int) 3e9 is negative. ;)
  • asteri
    asteri over 11 years
    Thanks for your answer. What happens if an integer is "too large", and what would the range for which a double is capable of storing a value exactly be?
  • Paul
    Paul over 11 years
    Nevermind. Definitely misread :)
  • nneonneo
    nneonneo over 11 years
    @JeffGohlke: If an integer is too big, e.g. 123448578293872985124 (61 bits), the integer will be truncated when converted to a double and the least-significant bits will be lost. Thus, (long)((double)123448578293872985124) != 123448578293872985124.
  • nneonneo
    nneonneo over 11 years
    That's not precisely correct. A double can be an integer, but may be the floating-point representation of multiple integers due to truncation error; this happens with integers around 2**52 and larger.
  • murgatroid99
    murgatroid99 over 11 years
    Can you elaborate? I wouldn't be surprised if I missed something, but I don't see it
  • asteri
    asteri over 11 years
    So essentially, doubles do in fact store exact values where it's possible to do so. They are only approximations where no exact decimal representation is possible?
  • Eric Postpischil
    Eric Postpischil over 11 years
    @JeffGohlke: Per the Java specification, conversion to an int of a double that is too large yields the largest value of int. In this case, the test (int) d == d fails to indicate that the value of d is an integer. I suggest Math.floor(d) == d as an alternative.
  • Eric Postpischil
    Eric Postpischil over 11 years
    Math.floor(d) == d performs the test entirely in floating point, avoiding the problems of conversion to integer.
  • nneonneo
    nneonneo over 11 years
    More precisely: If, represented in scientific base-2, the binary value is terminating and has less than 52 bits (and an exponent within -1023 to +1023), a double can store it exactly. Otherwise, the value may have to be truncated. In particular, powers of two are represented exactly, as are integers less than 2**52. However, values like 0.1 have repeating (non-terminating) representations in binary so they cannot be represented exactly.
  • asteri
    asteri over 11 years
    Thank you, sir. Very helpful and informative.
  • murgatroid99
    murgatroid99 over 11 years
    I don't see the problem with converting to an integer
  • Eric Postpischil
    Eric Postpischil over 11 years
    @nneonneo: That should say “less than 54 significant bits”, not “less than 52”. The significand of a double has 53 bits (1 implicit, 52 explicit). The low end of the normal exponent range is -1022, not -1023. (Denormals extend to -1074.) The condition “the binary value is terminating” is redundant, since non-terminating numerals do not have fewer than 54 bits. Values are commonly rounded, not truncated.
  • murgatroid99
    murgatroid99 over 11 years
    @nneonneo but when the comparison is calculated, (long)d is cast back to double, and since the d is the double representation for the integer (long)d, it should still be equal. However, I changed my answer to reflect that issue anyway. Once a double is in that range (2^52) and above, it is guaranteed to be an integer.
  • nneonneo
    nneonneo over 11 years
    @EricPostpischil: Thanks for the clarification. I didn't want to invoke denormals because they also imply less precision than 53 bits. I am using truncation in the more general sense of 'losing bits'. You are right on the other points.
  • Eric Postpischil
    Eric Postpischil over 11 years
    @murgatroid99: I think you did see the problem with converting to an integer, since you added tests to work around the problem. However, I think the Long.MAX_VALUE version was better. Although that might not have worked the way you intended (Long.MAX_VALUE was implicitly converted to double for comparison, which changed the value, which made the test false when d is 2^63. But the (long) d == d test succeeds because of the same change!), but it worked. I expect the version with 1<<52 to fail because 1 has type int, so 1<<52 uses only the low five bits of 52.
  • murgatroid99
    murgatroid99 over 11 years
    Yes, that makes sense. I wanted to keep it like this (instead of using Math.floor) because it seems in some sense like the most direct extension of the original code
  • Eric Postpischil
    Eric Postpischil over 11 years
    It is fine with regard to floating-point correctness (up to issues with NaNs and infinities that other answers also have). I would not recommend it because division (and remainder) is slow on typical processors.
  • supercat
    supercat about 9 years
    @nneonneo: The "meaning" of a floating-point number depends whether it's the input to an operation, or the output from it. If a+b yields some floating-point value, that value will effectively indicate that the arithmetical sum of a and b, regarding each as an exact value, is within a certain range. Feeding that sum into any subsequent operation, however, will cause the computer to interpret as a particular precise numerical value in the middle of the possible range (which may or may not equal the arithmetical sum mentioned earlier).
  • Adrian Redgers
    Adrian Redgers over 6 years
    I don't understand why this comment has been downvoted. I find it relevant and completely correct. FWIW on my numerical analysis course we were penalized for ever using == in relation to a double - we always had to check the abs difference. If you don't want to rely on the implementation then this is the only safe answer., just make the tolerance something very small, like 10E-12