简体   繁体   中英

How quickly check whether double fits in float? (Java)

Are there some arithmetic or bitwise operations that can check whether a double fits into a float without loss of precision.

It should not only check that the double range is in the float range, but also that no mantissa bits get lost.

Bye

PS: This answers the problem half way for C#: How to check if a double can fit into a float without conversion to infinity But I need a solution that works for Java.

How about this:

double d = ...;
if ((double)(float)d == d) {
   System.out.println(d + " fits into float!");
}

The idea is pretty simple: We first cast to float and then back to double and check whether the result is still the same. If d does not fit into float, then some precision would have been lost at the (float)d cast and thus the result would be different.

Strictly speaking, the cast back to double is not necessary as the comparision operator will do this cast implicitly, so (float)d == d is also fine.

If you are worried about the performance of this, because many float operations are a lot slower than comparable int operations: This is pretty much a non-issue here. Conversion between float and double is extremely efficient in modern CPUs. It can even be vectorized! There are the cvtpd2ps and cvtps2pd instructions in the SSE2 instruction set that perform the conversion from double to float and vice versa (4 values are converted at once). The instructions have a latency of 4 cycles on all Intel CPUs that support them. 4 cycles for 4 conversions is extremely fast.

A straight-forward solution could look like this:

public class Scribble {
    public static void main(String[] args) {
        for (int i = 1; i <= 10; i++) {

            double d = 1d / ((double)i);
            float f = (float) d;

            boolean lossless = d == f;

            System.out.println(d + " can be converted " + (lossless ? "lossless" : "only with loss"));
        }
    }
}

it outputs:

1.0 can be converted lossless
0.5 can be converted lossless
0.3333333333333333 can be converted only with loss
0.25 can be converted lossless
0.2 can be converted only with loss
0.16666666666666666 can be converted only with loss
0.14285714285714285 can be converted only with loss
0.125 can be converted lossless
0.1111111111111111 can be converted only with loss
0.1 can be converted only with loss

edit: speed comparison shows, that method2 seems to be fastest:

 method1  |  method2  |  method3 
237094654 | 209365345 | 468025911
214129288 | 209917275 | 448695709
232093486 | 197637245 | 448153336
249210162 | 200163771 | 460200921
240685446 | 200638561 | 447061763
332890287 | 337870633 | 450452194
247054322 | 199045232 | 449442540
235533069 | 200767924 | 452743201
256274670 | 199153775 | 453373979
298277375 | 198659529 | 456672251
229360115 | 205883096 | 454198291
252680123 | 224850463 | 452860277
246047739 | 200070587 | 458091501
304270790 | 204517093 | 463688631
235058620 | 204675812 | 448639390
260565871 | 205834286 | 458372075
256008432 | 242574024 | 498943242
311210028 | 208080237 | 478777466
242014926 | 208995343 | 457901380
239893559 | 205111348 | 451616471

code:

public class Scribble {

    static int size = 1024*1024*100;
    static boolean[] results = new boolean[size];
    static double[] values = new double[size];

    public static void main(String[] args) {

        // generate values
        for (int i = 0; i < size; i++)
            values[i] = 1d / ((double)i);

        long start;
        long duration;

        System.out.println(" method1  |  method2  |  method3 ");
        for (int i = 0; i < 20; i++) {
            start = System.nanoTime();
            method1(size);
            duration = System.nanoTime() - start;
            System.out.printf("%9d", duration);

            start = System.nanoTime();
            method2(size);
            duration = System.nanoTime() - start;
            System.out.printf(" | %9d", duration);

            start = System.nanoTime();
            method3(size);
            duration = System.nanoTime() - start;
            System.out.printf(" | %9d\n", duration);
        }
    }

    private static void method1(int size) {
        boolean[] results = new boolean[size];
        for (int i = 0; i < size; i++) {
            double d = values[i];
            float f = (float) d;

            boolean lossless = d == f;
            results[i] = lossless;
        }
    }

    private static void method2(int size) {
        for (int i = 0; i < size; i++) {
            double d = values[i];
            results[i] = d == (double)(float)d;
        }
    }

    private static void method3(int size) {
        for (int i = 0; i < size; i++) {
            double d = values[i];
            results[i] = Double.compare(d, (float) d) == 0;
        }
    }
}

Similarly to casting the number to float and back to double and checking for equality ( == ), Double.compare() can also be used:

double d = 2/3.0;

// 0 means OK, d fits into float
if (Double.compare(d, (float) d) == 0)
    System.out.println("OK, fits into float.");

Moreover, since comparing a float to double will implicitly cast the float to double , we can simply write:

if ((float) d == d)
    System.out.println("OK, fits into float.");

If you want to know if your double value fits the MAX and MIN range of float yout can´t use cast like (float)d == d because d may fits the float ranges but not necessary has the same decimals after cast.

In than case you must compare with Float.MAX_VALUE and Float.MIN_VALUE

return d <= Float.MAX_VALUE && d >= Float.MIN_VALUE;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM