6

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?

asteri
  • 11,402
  • 13
  • 60
  • 84
  • 1
    Can you use BigDecimal instead of double? – matt freake Aug 22 '12 at 16:47
  • Yep, don't try to do this with doubles or floats. Floating point numbers don't work this way. – Hovercraft Full Of Eels Aug 22 '12 at 16:47
  • Just because doubles store approximations in some cases, it doesn't mean there are multiple representations of the same number. The only numbers with non-unique representations are `0` and `NaN`, and they shouldn't matter – murgatroid99 Aug 22 '12 at 16:47
  • 3
    How about `(x == Math.floor(x))`? – Mike Christensen Aug 22 '12 at 16:49
  • 2
    perhaps `if((d-(int)d)>0)` ..... – perilbrain Aug 22 '12 at 16:49
  • You should tell us more about what overall problem you're trying to solve, not how you're trying to solve it in code. The best solution may be entirely different from what you're trying. – Hovercraft Full Of Eels Aug 22 '12 at 16:49
  • @Disco3 Unfortunately, I am unable to dictate the format that I receive the data in, and what it gives me is a `double`. I suppose I could construct a `BigDecimal` from the `double`, but I'd still be curious to know the answer to the question, even if I chose to use this semi-hacky workaround. – asteri Aug 22 '12 at 16:49
  • 1
    "is an integer" means can be converted to an int or has a null fraction part? There's a slight difference, for example 1.0e100 has a null fraction part but cannot be converted to an int (overflow). – aka.nice Aug 22 '12 at 16:53
  • @aka.nice It means "has a null fraction part". I want to determine if the double represents an integer, not whether or not it can be converted to the primitive data type called `int`. – asteri Aug 22 '12 at 16:54
  • It's best not to populate a BigDecimal from a double, as if the double is one of the approximate values, it will get put into the Big Decimal as the approximate value too. – matt freake Aug 22 '12 at 16:58
  • @Disco3 - OP already commented that the use of double is forced. converting double to BigDecimal is and _exact_ conversion (i.e. you lose no precision over the given double value). – jtahlborn Aug 22 '12 at 17:03
  • 3
    If it means "has a null fraction part" then (int)d==d DOES NOT work fine for all d (especially those >=2^31 or <-2^31), you should rather use Math.floor(d) == d as proposed by Eric Postpischil rather than accepted answer. – aka.nice Aug 22 '12 at 17:49
  • `Math.rint` is notably faster than `Math.floor`, and just as applicable in this situation. But if you can use Guava, [`DoubleMath.isMathematicalInteger(x)`](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/math/DoubleMath.html#isMathematicalInteger(double)) will either already be faster, or will be faster as soon as I check in this change... – Louis Wasserman Aug 22 '12 at 22:16

5 Answers5

11

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.

nneonneo
  • 171,345
  • 36
  • 312
  • 383
  • 1
    +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. ;) – Peter Lawrey Aug 22 '12 at 16:52
  • 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? – asteri Aug 22 '12 at 16:52
  • Nevermind. Definitely misread :) – Paul Aug 22 '12 at 16:52
  • @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 Aug 22 '12 at 16:56
  • 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? – asteri Aug 22 '12 at 16:59
  • 5
    @JeffGohlke: Per the [Java specification](http://docs.oracle.com/javase/specs/jls/se7/jls7.pdf), 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 Aug 22 '12 at 17:02
  • 1
    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. – nneonneo Aug 22 '12 at 17:04
  • Thank you, sir. Very helpful and informative. – asteri Aug 22 '12 at 17:06
  • 1
    @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. – Eric Postpischil Aug 22 '12 at 17:18
  • @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. – nneonneo Aug 22 '12 at 17:42
3

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.

Squidly
  • 2,707
  • 19
  • 43
  • 1
    But this ignores how doubles and floating point numbers work. Not a good suggestion. – Hovercraft Full Of Eels Aug 22 '12 at 16:48
  • 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. – Squidly Aug 22 '12 at 16:50
  • 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. – Eric Postpischil Aug 22 '12 at 18:36
2
if(new BigDecimal(d).scale() <= 0) {
    //do stuff
}
jtahlborn
  • 52,909
  • 5
  • 76
  • 118
1

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.

murgatroid99
  • 19,007
  • 10
  • 60
  • 95
  • 1
    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. – nneonneo Aug 22 '12 at 16:58
  • Can you elaborate? I wouldn't be surprised if I missed something, but I don't see it – murgatroid99 Aug 22 '12 at 16:58
  • 3
    `Math.floor(d) == d` performs the test entirely in floating point, avoiding the problems of conversion to integer. – Eric Postpischil Aug 22 '12 at 17:03
  • I don't see the problem with converting to an integer – murgatroid99 Aug 22 '12 at 17:08
  • @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. – murgatroid99 Aug 22 '12 at 17:30
  • 1
    @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. – Eric Postpischil Aug 22 '12 at 17:47
  • 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 – murgatroid99 Aug 22 '12 at 17:50
  • @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). – supercat Mar 31 '15 at 21:15
-2

Doubles are a binary fraction with a binary exponent. You cannot be certain that an integer can be exactly represented as a double, especially not if it has been calculated from other values.

Hence the normal way to approach this is to say that it needs to be "sufficiently close" to an integer value, where sufficiently close typically mean "within X %" (where X is rather small).

I.e. if X is 1 then 1.98 and 2.02 would both be considered to be close enough to be 2. If X is 0.01 then it needs to be between 1.9998 and 2.0002 to be close enough.

Thorbjørn Ravn Andersen
  • 73,784
  • 33
  • 194
  • 347
  • 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 – Adrian Redgers Jan 27 '18 at 15:44