| /* | |
| * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| /* | |
| * @test | |
| * @bug 8289551 | |
| * @summary Verify conversion between float and the binary16 format | |
| * @library ../Math | |
| * @build FloatConsts | |
| * @run main Binary16Conversion | |
| * @run main/othervm -XX:+UnlockDiagnosticVMOptions | |
| * -XX:DisableIntrinsic=_float16ToFloat,_floatToFloat16 Binary16Conversion | |
| */ | |
| public class Binary16Conversion { | |
| public static void main(String... argv) { | |
| int errors = 0; | |
| errors += binary16RoundTrip(); | |
| // Note that helper methods do sign-symmetric testing | |
| errors += binary16CardinalValues(); | |
| errors += roundFloatToBinary16(); | |
| errors += roundFloatToBinary16HalfWayCases(); | |
| errors += roundFloatToBinary16FullBinade(); | |
| errors += alternativeImplementation(); | |
| if (errors > 0) | |
| throw new RuntimeException(errors + " errors"); | |
| } | |
| /* | |
| * Put all 16-bit values through a conversion loop and make sure | |
| * the values are preserved (NaN bit patterns notwithstanding). | |
| */ | |
| private static int binary16RoundTrip() { | |
| int errors = 0; | |
| for (int i = Short.MIN_VALUE; i < Short.MAX_VALUE; i++) { | |
| short s = (short)i; | |
| float f = Float.float16ToFloat(s); | |
| short s2 = Float.floatToFloat16(f); | |
| if (!Binary16.equivalent(s, s2)) { | |
| errors++; | |
| System.out.println("Roundtrip failure on " + | |
| Integer.toHexString(0xFFFF & (int)s) + | |
| "\t got back " + Integer.toHexString(0xFFFF & (int)s2)); | |
| } | |
| } | |
| return errors; | |
| } | |
| private static int binary16CardinalValues() { | |
| int errors = 0; | |
| // Encode short value for different binary16 cardinal values as an | |
| // integer-valued float. | |
| float[][] testCases = { | |
| {Binary16.POSITIVE_ZERO, +0.0f}, | |
| {Binary16.MIN_VALUE, 0x1.0p-24f}, | |
| {Binary16.MAX_SUBNORMAL, 0x1.ff8p-15f}, | |
| {Binary16.MIN_NORMAL, 0x1.0p-14f}, | |
| {Binary16.ONE, 1.0f}, | |
| {Binary16.MAX_VALUE, 65504.0f}, | |
| {Binary16.POSITIVE_INFINITY, Float.POSITIVE_INFINITY}, | |
| }; | |
| // Check conversions in both directions | |
| // short -> float | |
| for (var testCase : testCases) { | |
| errors += compareAndReportError((short)testCase[0], | |
| testCase[1]); | |
| } | |
| // float -> short | |
| for (var testCase : testCases) { | |
| errors += compareAndReportError(testCase[1], | |
| (short)testCase[0]); | |
| } | |
| return errors; | |
| } | |
| private static int roundFloatToBinary16() { | |
| int errors = 0; | |
| float[][] testCases = { | |
| // Test all combinations of LSB, round, and sticky bit | |
| // LSB = 0, test combination of round and sticky | |
| {0x1.ff8000p-1f, (short)0x3bfe}, // round = 0, sticky = 0 | |
| {0x1.ff8010p-1f, (short)0x3bfe}, // round = 0, sticky = 1 | |
| {0x1.ffa000p-1f, (short)0x3bfe}, // round = 1, sticky = 0 | |
| {0x1.ffa010p-1f, (short)0x3bff}, // round = 1, sticky = 1 => ++ | |
| // LSB = 1, test combination of round and sticky | |
| {0x1.ffc000p-1f, Binary16.ONE-1}, // round = 0, sticky = 0 | |
| {0x1.ffc010p-1f, Binary16.ONE-1}, // round = 0, sticky = 1 | |
| {0x1.ffe000p-1f, Binary16.ONE}, // round = 1, sticky = 0 => ++ | |
| {0x1.ffe010p-1f, Binary16.ONE}, // round = 1, sticky = 1 => ++ | |
| // Test subnormal rounding | |
| // Largest subnormal binary16 0x03ff => 0x1.ff8p-15f; LSB = 1 | |
| {0x1.ff8000p-15f, Binary16.MAX_SUBNORMAL}, // round = 0, sticky = 0 | |
| {0x1.ff8010p-15f, Binary16.MAX_SUBNORMAL}, // round = 0, sticky = 1 | |
| {0x1.ffc000p-15f, Binary16.MIN_NORMAL}, // round = 1, sticky = 0 => ++ | |
| {0x1.ffc010p-15f, Binary16.MIN_NORMAL}, // round = 1, sticky = 1 => ++ | |
| // Test rounding near binary16 MIN_VALUE | |
| // Smallest in magnitude subnormal binary16 value 0x0001 => 0x1.0p-24f | |
| // Half-way case,0x1.0p-25f, and smaller should round down to zero | |
| {0x1.fffffep-26f, Binary16.POSITIVE_ZERO}, // nextDown in float | |
| {0x1.000000p-25f, Binary16.POSITIVE_ZERO}, | |
| {0x1.000002p-25f, Binary16.MIN_VALUE}, // nextUp in float | |
| {0x1.100000p-25f, Binary16.MIN_VALUE}, | |
| // Test rounding near overflow threshold | |
| // Largest normal binary16 number 0x7bff => 0x1.ffcp15f; LSB = 1 | |
| {0x1.ffc000p15f, Binary16.MAX_VALUE}, // round = 0, sticky = 0 | |
| {0x1.ffc010p15f, Binary16.MAX_VALUE}, // round = 0, sticky = 1 | |
| {0x1.ffe000p15f, Binary16.POSITIVE_INFINITY}, // round = 1, sticky = 0 => ++ | |
| {0x1.ffe010p15f, Binary16.POSITIVE_INFINITY}, // round = 1, sticky = 1 => ++ | |
| }; | |
| for (var testCase : testCases) { | |
| errors += compareAndReportError(testCase[0], | |
| (short)testCase[1]); | |
| } | |
| return errors; | |
| } | |
| private static int roundFloatToBinary16HalfWayCases() { | |
| int errors = 0; | |
| // Test rounding of exact half-way cases between each pair of | |
| // finite exactly-representable binary16 numbers. Also test | |
| // rounding of half-way +/- ulp of the *float* value. | |
| // Additionally, test +/- float ulp of the endpoints. (Other | |
| // tests in this file make sure all short values round-trip so | |
| // that doesn't need to be tested here.) | |
| for (int i = Binary16.POSITIVE_ZERO; // 0x0000 | |
| i <= Binary16.MAX_VALUE; // 0x7bff | |
| i += 2) { // Check every even/odd pair once | |
| short lower = (short) i; | |
| short upper = (short)(i+1); | |
| float lowerFloat = Float.float16ToFloat(lower); | |
| float upperFloat = Float.float16ToFloat(upper); | |
| assert lowerFloat < upperFloat; | |
| float midway = (lowerFloat + upperFloat) * 0.5f; // Exact midpoint | |
| errors += compareAndReportError(Math.nextUp(lowerFloat), lower); | |
| errors += compareAndReportError(Math.nextDown(midway), lower); | |
| // Under round to nearest even, the midway point will | |
| // round *down* to the (even) lower endpoint. | |
| errors += compareAndReportError( midway, lower); | |
| errors += compareAndReportError(Math.nextUp( midway), upper); | |
| errors += compareAndReportError(Math.nextDown(upperFloat), upper); | |
| } | |
| // More testing around the overflow threshold | |
| // Binary16.ulp(Binary16.MAX_VALUE) == 32.0f; test around Binary16.MAX_VALUE + 1/2 ulp | |
| float binary16_MAX_VALUE = Float.float16ToFloat(Binary16.MAX_VALUE); | |
| float binary16_MAX_VALUE_halfUlp = binary16_MAX_VALUE + 16.0f; | |
| errors += compareAndReportError(Math.nextDown(binary16_MAX_VALUE), Binary16.MAX_VALUE); | |
| errors += compareAndReportError( binary16_MAX_VALUE, Binary16.MAX_VALUE); | |
| errors += compareAndReportError(Math.nextUp( binary16_MAX_VALUE), Binary16.MAX_VALUE); | |
| // Binary16.MAX_VALUE is an "odd" value since its LSB = 1 so | |
| // the half-way value greater than Binary16.MAX_VALUE should | |
| // round up to the next even value, in this case Binary16.POSITIVE_INFINITY. | |
| errors += compareAndReportError(Math.nextDown(binary16_MAX_VALUE_halfUlp), Binary16.MAX_VALUE); | |
| errors += compareAndReportError( binary16_MAX_VALUE_halfUlp, Binary16.POSITIVE_INFINITY); | |
| errors += compareAndReportError(Math.nextUp( binary16_MAX_VALUE_halfUlp), Binary16.POSITIVE_INFINITY); | |
| return errors; | |
| } | |
| private static int compareAndReportError(float input, | |
| short expected) { | |
| // Round to nearest even is sign symmetric | |
| return compareAndReportError0( input, expected) + | |
| compareAndReportError0(-input, Binary16.negate(expected)); | |
| } | |
| private static int compareAndReportError0(float input, | |
| short expected) { | |
| short actual = Float.floatToFloat16(input); | |
| if (!Binary16.equivalent(actual, expected)) { | |
| System.out.println("Unexpected result of converting " + | |
| Float.toHexString(input) + | |
| " to short. Expected 0x" + Integer.toHexString(0xFFFF & expected) + | |
| " got 0x" + Integer.toHexString(0xFFFF & actual)); | |
| return 1; | |
| } | |
| return 0; | |
| } | |
| private static int compareAndReportError0(short input, | |
| float expected) { | |
| float actual = Float.float16ToFloat(input); | |
| if (Float.compare(actual, expected) != 0) { | |
| System.out.println("Unexpected result of converting " + | |
| Integer.toHexString(input & 0xFFFF) + | |
| " to float. Expected " + Float.toHexString(expected) + | |
| " got " + Float.toHexString(actual)); | |
| return 1; | |
| } | |
| return 0; | |
| } | |
| private static int compareAndReportError(short input, | |
| float expected) { | |
| // Round to nearest even is sign symmetric | |
| return compareAndReportError0( input, expected) + | |
| compareAndReportError0(Binary16.negate(input), -expected); | |
| } | |
| private static int roundFloatToBinary16FullBinade() { | |
| int errors = 0; | |
| // For each float value between 1.0 and less than 2.0 | |
| // (i.e. set of float values with an exponent of 0), convert | |
| // each value to binary16 and then convert that binary16 value | |
| // back to float. | |
| // | |
| // Any exponent could be used; the maximum exponent for normal | |
| // values would not exercise the full set of code paths since | |
| // there is an up-front check on values that would overflow, | |
| // which correspond to a ripple-carry of the significand that | |
| // bumps the exponent. | |
| short previous = (short)0; | |
| for (int i = Float.floatToIntBits(1.0f); | |
| i <= Float.floatToIntBits(Math.nextDown(2.0f)); | |
| i++) { | |
| // (Could also express the loop control directly in terms | |
| // of floating-point operations, incrementing by ulp(1.0), | |
| // etc.) | |
| float f = Float.intBitsToFloat(i); | |
| short f_as_bin16 = Float.floatToFloat16(f); | |
| short f_as_bin16_down = (short)(f_as_bin16 - 1); | |
| short f_as_bin16_up = (short)(f_as_bin16 + 1); | |
| // Across successive float values to convert to binary16, | |
| // the binary16 results should be semi-monotonic, | |
| // non-decreasing in this case. | |
| // Only positive binary16 values so can compare using integer operations | |
| if (f_as_bin16 < previous) { | |
| errors++; | |
| System.out.println("Semi-monotonicity violation observed on " + | |
| Integer.toHexString(0xfff & f_as_bin16)); | |
| } | |
| previous = f_as_bin16; | |
| // If round-to-nearest was correctly done, when exactly | |
| // mapped back to float, f_as_bin16 should be at least as | |
| // close as either of its neighbors to the original value | |
| // of f. | |
| float f_prime_down = Float.float16ToFloat(f_as_bin16_down); | |
| float f_prime = Float.float16ToFloat(f_as_bin16); | |
| float f_prime_up = Float.float16ToFloat(f_as_bin16_up); | |
| float f_prime_diff = Math.abs(f - f_prime); | |
| if (f_prime_diff == 0.0) { | |
| continue; | |
| } | |
| float f_prime_down_diff = Math.abs(f - f_prime_down); | |
| float f_prime_up_diff = Math.abs(f - f_prime_up); | |
| if (f_prime_diff > f_prime_down_diff || | |
| f_prime_diff > f_prime_up_diff) { | |
| errors++; | |
| System.out.println("Round-to-nearest violation on converting " + | |
| Float.toHexString(f) + " to binary16 and back."); | |
| } | |
| } | |
| return errors; | |
| } | |
| private static int alternativeImplementation() { | |
| int errors = 0; | |
| // For exhaustive test of all float values use | |
| // for (long ell = Integer.MIN_VALUE; ell <= Integer.MAX_VALUE; ell++) { | |
| for (long ell = Float.floatToIntBits(2.0f); | |
| ell <= Float.floatToIntBits(4.0f); | |
| ell++) { | |
| float f = Float.intBitsToFloat((int)ell); | |
| short s1 = Float.floatToFloat16(f); | |
| short s2 = altFloatToFloat16(f); | |
| if (s1 != s2) { | |
| errors++; | |
| System.out.println("Different conversion of float value " + Float.toHexString(f)); | |
| } | |
| } | |
| return errors; | |
| } | |
| /* | |
| * Rely on float operations to do rounding in both normal and | |
| * subnormal binary16 cases. | |
| */ | |
| public static short altFloatToFloat16(float f) { | |
| int doppel = Float.floatToRawIntBits(f); | |
| short sign_bit = (short)((doppel & 0x8000_0000) >> 16); | |
| if (Float.isNaN(f)) { | |
| // Preserve sign and attempt to preserve significand bits | |
| return (short)(sign_bit | |
| | 0x7c00 // max exponent + 1 | |
| // Preserve high order bit of float NaN in the | |
| // binary16 result NaN (tenth bit); OR in remaining | |
| // bits into lower 9 bits of binary 16 significand. | |
| | (doppel & 0x007f_e000) >> 13 // 10 bits | |
| | (doppel & 0x0000_1ff0) >> 4 // 9 bits | |
| | (doppel & 0x0000_000f)); // 4 bits | |
| } | |
| float abs_f = Math.abs(f); | |
| // The overflow threshold is binary16 MAX_VALUE + 1/2 ulp | |
| if (abs_f >= (65504.0f + 16.0f) ) { | |
| return (short)(sign_bit | 0x7c00); // Positive or negative infinity | |
| } else { | |
| // Smallest magnitude nonzero representable binary16 value | |
| // is equal to 0x1.0p-24; half-way and smaller rounds to zero. | |
| if (abs_f <= 0x1.0p-25f) { // Covers float zeros and subnormals. | |
| return sign_bit; // Positive or negative zero | |
| } | |
| // Dealing with finite values in exponent range of | |
| // binary16 (when rounding is done, could still round up) | |
| int exp = Math.getExponent(f); | |
| assert -25 <= exp && exp <= 15; | |
| short signif_bits; | |
| if (exp <= -15) { // scale down to float subnormal range to do rounding | |
| // Use a float multiply to compute the correct | |
| // trailing significand bits for a binary16 subnormal. | |
| // | |
| // The exponent range of normalized binary16 subnormal | |
| // values is [-24, -15]. The exponent range of float | |
| // subnormals is [-149, -140]. Multiply abs_f down by | |
| // 2^(-125) -- since (-125 = -149 - (-24)) -- so that | |
| // the trailing bits of a subnormal float represent | |
| // the correct trailing bits of a binary16 subnormal. | |
| exp = -15; // Subnormal encoding using -E_max. | |
| float f_adjust = abs_f * 0x1.0p-125f; | |
| // In case the significand rounds up and has a carry | |
| // propagate all the way up, take the bottom 11 bits | |
| // rather than bottom 10 bits. Adding this value, | |
| // rather than OR'ing htis value, will cause the right | |
| // exponent adjustment. | |
| signif_bits = (short)(Float.floatToRawIntBits(f_adjust) & 0x07ff); | |
| return (short)(sign_bit | ( ((exp + 15) << 10) + signif_bits ) ); | |
| } else { | |
| // Scale down to subnormal range to round off excess bits | |
| int scalingExp = -139 - exp; | |
| float scaled = Math.scalb(Math.scalb(f, scalingExp), | |
| -scalingExp); | |
| exp = Math.getExponent(scaled); | |
| doppel = Float.floatToRawIntBits(scaled); | |
| signif_bits = (short)((doppel & 0x007f_e000) >> | |
| (FloatConsts.SIGNIFICAND_WIDTH - 11)); | |
| return (short)(sign_bit | ( ((exp + 15) << 10) | signif_bits ) ); | |
| } | |
| } | |
| } | |
| public static class Binary16 { | |
| public static final short POSITIVE_INFINITY = (short)0x7c00; | |
| public static final short MAX_VALUE = 0x7bff; | |
| public static final short ONE = 0x3c00; | |
| public static final short MIN_NORMAL = 0x0400; | |
| public static final short MAX_SUBNORMAL = 0x03ff; | |
| public static final short MIN_VALUE = 0x0001; | |
| public static final short POSITIVE_ZERO = 0x0000; | |
| public static boolean isNaN(short binary16) { | |
| return ((binary16 & 0x7c00) == 0x7c00) // Max exponent and... | |
| && ((binary16 & 0x03ff) != 0 ); // significand nonzero. | |
| } | |
| public static short negate(short binary16) { | |
| return (short)(binary16 ^ 0x8000 ); // Flip only sign bit. | |
| } | |
| public static boolean equivalent(short bin16_1, short bin16_2) { | |
| return (bin16_1 == bin16_2) || | |
| isNaN(bin16_1) && isNaN(bin16_2); | |
| } | |
| } | |
| } |