ParseHexFloatingPoint.java revision 11824:409888e3ba56
1/*
2 * Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24/*
25 * @test
26 * @library /lib/testlibrary/
27 * @build jdk.testlibrary.*
28 * @run main ParseHexFloatingPoint
29 * @bug 4826774 8078672
30 * @summary Numerical tests for hexadecimal inputs to parse{Double, Float} (use -Dseed=X to set PRNG seed)
31 * @author Joseph D. Darcy
32 * @key randomness
33 */
34
35public class ParseHexFloatingPoint {
36    private ParseHexFloatingPoint(){}
37
38    public static final double infinityD = Double.POSITIVE_INFINITY;
39    public static final double NaND = Double.NaN;
40
41    static int test(String testName, String input,
42                    double result, double expected) {
43        int failures =0;
44
45        if (Double.compare(result, expected) != 0 ) {
46            System.err.println("Failure for " + testName +
47                               ": For input " + input +
48                               " expected " + expected +
49                               " got " + result + ".");
50        }
51
52        return failures;
53    }
54
55    static int testCase(String input, double expected) {
56        int failures =0;
57
58
59        // Try different combination of letter components
60        input = input.toLowerCase(java.util.Locale.US);
61
62        String [] suffices = {"", "f", "F", "d", "D"};
63        String [] signs = {"", "-", "+"};
64
65        for(int i = 0; i < 2; i++) {
66            String s1 = input;
67            if(i == 1)
68                s1 = s1.replace('x', 'X');
69
70            for(int j = 0; j < 2; j++) {
71                String s2 = s1;
72                if(j == 1)
73                    s2 = s2.replace('p', 'P');
74
75                for(int k = 0; k < 2; k++) {
76                    String s3 = s2;
77                    if(k == 1)
78                        s3 = upperCaseHex(s3);
79
80
81                    for(int m = 0; m < suffices.length; m++) {
82                        String s4 = s3 + suffices[m];
83
84
85                        for(int n = 0; n < signs.length; n++) {
86                            String s5 = signs[n] + s4;
87
88                            double result = Double.parseDouble(s5);
89                            failures += test("Double.parseDouble",
90                                             s5, result, (signs[n].equals("-") ?
91                                                          -expected:
92                                                          expected));
93                        }
94                    }
95                }
96            }
97        }
98
99        return failures;
100    }
101
102    static String upperCaseHex(String s) {
103        return s.replace('a', 'A').replace('b', 'B').replace('c', 'C').
104                 replace('d', 'D').replace('e','E').replace('f', 'F');
105    }
106
107    /*
108     * Test easy and tricky double rounding cases.
109     */
110    static int doubleTests() {
111
112        /*
113         * A String, double pair
114         */
115        class PairSD {
116            public String s;
117            public double d;
118            PairSD(String s, double d) {
119                this.s = s;
120                this.d = d;
121            }
122        }
123        int failures = 0;
124
125
126
127        // Hex strings that convert to three; test basic functionality
128        // of significand and exponent shift adjusts along with the
129        // no-op of adding leading zeros.  These cases don't exercise
130        // the rounding code.
131        String leadingZeros = "0x0000000000000000000";
132        String [] threeTests = {
133            "0x.003p12",
134            "0x.006p11",
135            "0x.00cp10",
136            "0x.018p9",
137
138            "0x.3p4",
139            "0x.6p3",
140            "0x.cp2",
141            "0x1.8p1",
142
143            "0x3p0",
144            "0x6.0p-1",
145            "0xc.0p-2",
146            "0x18.0p-3",
147
148            "0x3000000p-24",
149            "0x3.0p0",
150            "0x3.000000p0",
151        };
152        for(int i=0; i < threeTests.length; i++) {
153            String input = threeTests[i];
154            failures += testCase(input, 3.0);
155
156            input.replaceFirst("^0x", leadingZeros);
157            failures += testCase(input, 3.0);
158        }
159
160        long bigExponents [] = {
161            2*Double.MAX_EXPONENT,
162            2*Double.MIN_EXPONENT,
163
164            (long)Integer.MAX_VALUE-1,
165            (long)Integer.MAX_VALUE,
166            (long)Integer.MAX_VALUE+1,
167
168            (long)Integer.MIN_VALUE-1,
169            (long)Integer.MIN_VALUE,
170            (long)Integer.MIN_VALUE+1,
171
172            Long.MAX_VALUE-1,
173            Long.MAX_VALUE,
174
175            Long.MIN_VALUE+1,
176            Long.MIN_VALUE,
177        };
178
179        // Test zero significand with large exponents.
180        for(int i = 0; i < bigExponents.length; i++) {
181            failures += testCase("0x0.0p"+Long.toString(bigExponents[i]) , 0.0);
182        }
183
184        // Test nonzero significand with large exponents.
185        for(int i = 0; i < bigExponents.length; i++) {
186            long exponent = bigExponents[i];
187            failures += testCase("0x10000.0p"+Long.toString(exponent) ,
188                                 (exponent <0?0.0:infinityD));
189        }
190
191        // Test significands with different lengths and bit patterns.
192        {
193            long signif = 0;
194                for(int i = 1; i <= 0xe; i++) {
195                    signif = (signif <<4) | (long)i;
196                    failures += testCase("0x"+Long.toHexString(signif)+"p0", signif);
197                }
198        }
199
200        PairSD [] testCases = {
201            new PairSD("0x0.0p0",               0.0/16.0),
202            new PairSD("0x0.1p0",               1.0/16.0),
203            new PairSD("0x0.2p0",               2.0/16.0),
204            new PairSD("0x0.3p0",               3.0/16.0),
205            new PairSD("0x0.4p0",               4.0/16.0),
206            new PairSD("0x0.5p0",               5.0/16.0),
207            new PairSD("0x0.6p0",               6.0/16.0),
208            new PairSD("0x0.7p0",               7.0/16.0),
209            new PairSD("0x0.8p0",               8.0/16.0),
210            new PairSD("0x0.9p0",               9.0/16.0),
211            new PairSD("0x0.ap0",               10.0/16.0),
212            new PairSD("0x0.bp0",               11.0/16.0),
213            new PairSD("0x0.cp0",               12.0/16.0),
214            new PairSD("0x0.dp0",               13.0/16.0),
215            new PairSD("0x0.ep0",               14.0/16.0),
216            new PairSD("0x0.fp0",               15.0/16.0),
217
218            // Half-way case between zero and MIN_VALUE rounds down to
219            // zero
220            new PairSD("0x1.0p-1075",           0.0),
221
222            // Slighly more than half-way case between zero and
223            // MIN_VALUES rounds up to zero.
224            new PairSD("0x1.1p-1075",                   Double.MIN_VALUE),
225            new PairSD("0x1.000000000001p-1075",        Double.MIN_VALUE),
226            new PairSD("0x1.000000000000001p-1075",     Double.MIN_VALUE),
227
228            // More subnormal rounding tests
229            new PairSD("0x0.fffffffffffff7fffffp-1022", Math.nextDown(Double.MIN_NORMAL)),
230            new PairSD("0x0.fffffffffffff8p-1022",      Double.MIN_NORMAL),
231            new PairSD("0x0.fffffffffffff800000001p-1022",Double.MIN_NORMAL),
232            new PairSD("0x0.fffffffffffff80000000000000001p-1022",Double.MIN_NORMAL),
233            new PairSD("0x1.0p-1022",                   Double.MIN_NORMAL),
234
235
236            // Large value and overflow rounding tests
237            new PairSD("0x1.fffffffffffffp1023",        Double.MAX_VALUE),
238            new PairSD("0x1.fffffffffffff0000000p1023", Double.MAX_VALUE),
239            new PairSD("0x1.fffffffffffff4p1023",       Double.MAX_VALUE),
240            new PairSD("0x1.fffffffffffff7fffffp1023",  Double.MAX_VALUE),
241            new PairSD("0x1.fffffffffffff8p1023",       infinityD),
242            new PairSD("0x1.fffffffffffff8000001p1023", infinityD),
243
244            new PairSD("0x1.ffffffffffffep1023",        Math.nextDown(Double.MAX_VALUE)),
245            new PairSD("0x1.ffffffffffffe0000p1023",    Math.nextDown(Double.MAX_VALUE)),
246            new PairSD("0x1.ffffffffffffe8p1023",       Math.nextDown(Double.MAX_VALUE)),
247            new PairSD("0x1.ffffffffffffe7p1023",       Math.nextDown(Double.MAX_VALUE)),
248            new PairSD("0x1.ffffffffffffeffffffp1023",  Double.MAX_VALUE),
249            new PairSD("0x1.ffffffffffffe8000001p1023", Double.MAX_VALUE),
250        };
251
252        for (int i = 0; i < testCases.length; i++) {
253            failures += testCase(testCases[i].s,testCases[i].d);
254        }
255
256        failures += significandAlignmentTests();
257
258        {
259            java.util.Random rand = RandomFactory.getRandom();
260            // Consistency check; double => hexadecimal => double
261            // preserves the original value.
262            for(int i = 0; i < 1000; i++) {
263                double d = rand.nextDouble();
264                failures += testCase(Double.toHexString(d), d);
265            }
266        }
267
268        return failures;
269    }
270
271    /*
272     * Verify rounding works the same regardless of how the
273     * significand is aligned on input.  A useful extension could be
274     * to have this sort of test for strings near the overflow
275     * threshold.
276     */
277    static int significandAlignmentTests() {
278        int failures = 0;
279                // baseSignif * 2^baseExp = nextDown(2.0)
280        long [] baseSignifs = {
281            0x1ffffffffffffe00L,
282            0x1fffffffffffff00L
283        };
284
285        double [] answers = {
286            Math.nextDown(Math.nextDown(2.0)),
287            Math.nextDown(2.0),
288            2.0
289        };
290
291        int baseExp = -60;
292        int count = 0;
293        for(int i = 0; i < 2; i++) {
294            for(long j = 0; j <= 0xfL; j++) {
295                for(long k = 0; k <= 8; k+= 4) { // k = {0, 4, 8}
296                    long base = baseSignifs[i];
297                    long testValue = base | (j<<4) | k;
298
299                    int offset = 0;
300                    // Calculate when significand should be incremented
301                    // see table 4.7 in Koren book
302
303                    if ((base & 0x100L) == 0L ) { // lsb is 0
304                        if ( (j >= 8L) &&         // round is 1
305                             ((j & 0x7L) != 0 || k != 0 ) ) // sticky is 1
306                            offset = 1;
307                    }
308                    else {                        // lsb is 1
309                        if (j >= 8L)              // round is 1
310                            offset = 1;
311                    }
312
313                    double expected = answers[i+offset];
314
315                    for(int m = -2; m <= 3; m++) {
316                        count ++;
317
318                        // Form equal value string and evaluate it
319                        String s = "0x" +
320                            Long.toHexString((m >=0) ?(testValue<<m):(testValue>>(-m))) +
321                            "p" + (baseExp - m);
322
323                        failures += testCase(s, expected);
324                    }
325                }
326            }
327        }
328
329        return failures;
330    }
331
332
333    /*
334     * Test tricky float rounding cases.  The code which
335     * reads in a hex string converts the string to a double value.
336     * If a float value is needed, the double value is cast to float.
337     * However, the cast be itself not always guaranteed to return the
338     * right result since:
339     *
340     * 1. hex string => double can discard a sticky bit which would
341     * influence a direct hex string => float conversion.
342     *
343     * 2. hex string => double => float can have a rounding to double
344     * precision which results in a larger float value while a direct
345     * hex string => float conversion would not round up.
346     *
347     * This method includes tests of the latter two possibilities.
348     */
349    static int floatTests(){
350        int failures = 0;
351
352        /*
353         * A String, float pair
354         */
355        class PairSD {
356            public String s;
357            public float f;
358            PairSD(String s, float f) {
359                this.s = s;
360                this.f = f;
361            }
362        }
363
364        String [][] roundingTestCases = {
365            // Target float value       hard rouding version
366
367            {"0x1.000000p0",    "0x1.0000000000001p0"},
368
369            // Try some values that should round up to nextUp(1.0f)
370            {"0x1.000002p0",    "0x1.0000010000001p0"},
371            {"0x1.000002p0",    "0x1.00000100000008p0"},
372            {"0x1.000002p0",    "0x1.0000010000000fp0"},
373            {"0x1.000002p0",    "0x1.00000100000001p0"},
374            {"0x1.000002p0",    "0x1.00000100000000000000000000000000000000001p0"},
375            {"0x1.000002p0",    "0x1.0000010000000fp0"},
376
377            // Potential double rounding cases
378            {"0x1.000002p0",    "0x1.000002fffffffp0"},
379            {"0x1.000002p0",    "0x1.000002fffffff8p0"},
380            {"0x1.000002p0",    "0x1.000002ffffffffp0"},
381
382            {"0x1.000002p0",    "0x1.000002ffff0ffp0"},
383            {"0x1.000002p0",    "0x1.000002ffff0ff8p0"},
384            {"0x1.000002p0",    "0x1.000002ffff0fffp0"},
385
386
387            {"0x1.000000p0",    "0x1.000000fffffffp0"},
388            {"0x1.000000p0",    "0x1.000000fffffff8p0"},
389            {"0x1.000000p0",    "0x1.000000ffffffffp0"},
390
391            {"0x1.000000p0",    "0x1.000000ffffffep0"},
392            {"0x1.000000p0",    "0x1.000000ffffffe8p0"},
393            {"0x1.000000p0",    "0x1.000000ffffffefp0"},
394
395            // Float subnormal cases
396            {"0x0.000002p-126", "0x0.0000010000001p-126"},
397            {"0x0.000002p-126", "0x0.00000100000000000001p-126"},
398
399            {"0x0.000006p-126", "0x0.0000050000001p-126"},
400            {"0x0.000006p-126", "0x0.00000500000000000001p-126"},
401
402            {"0x0.0p-149",      "0x0.7ffffffffffffffp-149"},
403            {"0x1.0p-148",      "0x1.3ffffffffffffffp-148"},
404            {"0x1.cp-147",      "0x1.bffffffffffffffp-147"},
405
406            {"0x1.fffffcp-127", "0x1.fffffdffffffffp-127"},
407        };
408
409        String [] signs = {"", "-"};
410
411        for(int i = 0; i < roundingTestCases.length; i++) {
412            for(int j = 0; j < signs.length; j++) {
413                String expectedIn = signs[j]+roundingTestCases[i][0];
414                String resultIn   = signs[j]+roundingTestCases[i][1];
415
416                float expected =  Float.parseFloat(expectedIn);
417                float result   =  Float.parseFloat(resultIn);
418
419                if( Float.compare(expected, result) != 0) {
420                    failures += 1;
421                    System.err.println("" + (i+1));
422                    System.err.println("Expected = " + Float.toHexString(expected));
423                    System.err.println("Rounded  = " + Float.toHexString(result));
424                    System.err.println("Double   = " + Double.toHexString(Double.parseDouble(resultIn)));
425                    System.err.println("Input    = " + resultIn);
426                    System.err.println("");
427                }
428            }
429        }
430
431        return failures;
432    }
433
434    public static void main(String argv[]) {
435        int failures = 0;
436
437        failures += doubleTests();
438        failures += floatTests();
439
440        if (failures != 0) {
441            throw new RuntimeException("" + failures + " failures while " +
442                                       "testing hexadecimal floating-point " +
443                                       "parsing.");
444        }
445    }
446
447}
448