10

I'm writing a Java class that tracks a fractional number, but needs to be able to increment and decrement it. Using a double for this number seems to work fine for small values, but I'm trying to figure out its limits.

I was thinking that one way of determining it might be to run a program that incremented a double and a long until they were no longer equal. However this takes too long to run.

double x = 0;
long y = 0;
while ((long)x == y)
{
    x++;
    y++;
}
System.out.println("X: " + x);
System.out.println("Y: " + y);

What are double's limits where you can no longer increment a value and actually add one to it? I'd think that decrementing a double would have a similar problem with negative numbers at some point.

3

2 Answers 2

14

You essentially want the largest positive number with the ulp of 1. This is the largest positive number where adding 1 will still change it.

From the Wikipedia page we can see that ulp for a normal number x between 2^e and 2^(e+1) is calculated as 2^(e-p+1), where p is the precision (number of bits in the mantissa).

enter image description here

For double, p is 53, so we want to find a value of e such that 2^(e-52) is 1. Obviously, e should be 52.

Then we can construct a double with the exponent 52, and the largest mantissa possible. You can write it as a long:

Double.longBitsToDouble(
        0x433FFFFFFFFFFFFFL
);

Due to how doubles are represented, we need to write "433" (1075 in decimal). This is the number from which subtracting 1023 will give you the desired exponent of 52.

This is the largest number where adding 1 will still change the number. nextUp of this number will remain unchanged when you add 1:

double x = Double.longBitsToDouble(
        0x433FFFFFFFFFFFFFL
);
double y = Math.nextUp(x);
System.out.println(x + 1 == x); // false
System.out.println(y + 1 == y); // true

Another way of writing x is 0x1p53 - 1 (credits to Holger in the comments), i.e. one less than the number with an exponent of 53.


The situation is symmetric for decrementing:

double x = -Double.longBitsToDouble(
        0x433FFFFFFFFFFFFFL
);
double y = Math.nextDown(x);
System.out.println(x - 1 == x); // false
System.out.println(y - 1 == y); // true
7
  • 2
    The double 0x433FFFFFFFFFFFFFL is available as Number.MAX_SAFE_INTEGER in JavaScript, but I don't see an equivalent constant for it anywhere in Java. Commented Sep 29 at 17:37
  • 1
    @StephenOstermiller JS does all kinds of conversions silently, so I imagine that would be very useful (I don’t use JS at all so don’t quote me on that).
    – Sweeper
    Commented Sep 29 at 18:13
  • 3
    Note to stupid myself: the value being discussed is actually 9007199254740991. The value 0x433FFFFFFFFFFFFFL is the integer representation of its IEEE encoding. Commented Sep 30 at 8:02
  • 4
    Note that the simplest way to express the double value whose mantissa would require 53 bits, is 0x1p53. So, for the first snippet y == 0x1p53.
    – Holger
    Commented Sep 30 at 10:04
  • 2
    @StephenOstermiller that’s why I wrote y == 0x1p53 and “the double value whose mantissa would require 53 bits” which is one bigger than x, the value that still fits into 52 bits. You can use 0x1p53-1 as constant expression to initialize x, it’s still simpler than the alternatives.
    – Holger
    Commented Sep 30 at 10:28
1

IEEE doubles have 52 bits in the mantissa field, but IEEE format use an "implicit 1", so this gives us an effective 53 bit mantissa. As a result all integers up to 253-1 can be represented.

253 can also be represented exactly, since it is a power of 2. So 253-1 can be safely incremented by 1.

253+1 on the other hand cannot be represented exactly, it would require an effective 54 bit mantissa.

AFAIK Java uses "round to nearest with ties rounded to even". So attempting to add 1 to 253 will give a result of 253.

Not the answer you're looking for? Browse other questions tagged or ask your own question.