diff --git a/.gitignore b/.gitignore index 5baeeea..adda3c7 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,8 @@ replay_pid* build .flattened-pom.xml + +.classpath +.factorypath +.project +.settings diff --git a/asdf-core/src/main/java/org/asdfformat/asdf/io/impl/InlineBlockV1_0_0.java b/asdf-core/src/main/java/org/asdfformat/asdf/io/impl/InlineBlockV1_0_0.java index 7914195..22f898e 100644 --- a/asdf-core/src/main/java/org/asdfformat/asdf/io/impl/InlineBlockV1_0_0.java +++ b/asdf-core/src/main/java/org/asdfformat/asdf/io/impl/InlineBlockV1_0_0.java @@ -4,6 +4,7 @@ import org.asdfformat.asdf.ndarray.DataType; import org.asdfformat.asdf.ndarray.DataTypeFamilyType; import org.asdfformat.asdf.ndarray.DataTypes; +import org.asdfformat.asdf.ndarray.impl.Float16Utils; import org.asdfformat.asdf.node.AsdfNode; import org.asdfformat.asdf.util.AsdfCharsets; @@ -25,6 +26,7 @@ public class InlineBlockV1_0_0 implements Block { SIMPLE_VALUE_WRITERS.put(DataTypes.INT32, (b, n) -> b.putInt(n.asInt())); SIMPLE_VALUE_WRITERS.put(DataTypes.UINT64, (b, n) -> b.putLong(n.asLong())); SIMPLE_VALUE_WRITERS.put(DataTypes.INT64, (b, n) -> b.putLong(n.asLong())); + SIMPLE_VALUE_WRITERS.put(DataTypes.FLOAT16, (b, n) -> b.putShort(Float16Utils.floatToFloat16(n.asFloat()))); SIMPLE_VALUE_WRITERS.put(DataTypes.FLOAT32, (b, n) -> b.putFloat(n.asFloat())); SIMPLE_VALUE_WRITERS.put(DataTypes.FLOAT64, (b, n) -> b.putDouble(n.asDouble())); SIMPLE_VALUE_WRITERS.put(DataTypes.BOOL8, (b, n) -> b.put((byte)(n.asBoolean() ? 1 : 0))); diff --git a/asdf-core/src/main/java/org/asdfformat/asdf/ndarray/DataTypes.java b/asdf-core/src/main/java/org/asdfformat/asdf/ndarray/DataTypes.java index a17de90..5288737 100644 --- a/asdf-core/src/main/java/org/asdfformat/asdf/ndarray/DataTypes.java +++ b/asdf-core/src/main/java/org/asdfformat/asdf/ndarray/DataTypes.java @@ -58,6 +58,12 @@ public class DataTypes { new HashSet<>(Arrays.asList(Long.TYPE, BigInteger.class)) ); + public static final DataType FLOAT16 = new SimpleDataTypeImpl( + DataTypeFamilyType.FLOAT, + 2, + new HashSet<>(Arrays.asList(Float.TYPE, Double.TYPE, BigDecimal.class)) + ); + public static final DataType FLOAT32 = new SimpleDataTypeImpl( DataTypeFamilyType.FLOAT, 4, diff --git a/asdf-core/src/main/java/org/asdfformat/asdf/ndarray/impl/BigDecimalNdArrayImpl.java b/asdf-core/src/main/java/org/asdfformat/asdf/ndarray/impl/BigDecimalNdArrayImpl.java index 1b44fac..389ccf1 100644 --- a/asdf-core/src/main/java/org/asdfformat/asdf/ndarray/impl/BigDecimalNdArrayImpl.java +++ b/asdf-core/src/main/java/org/asdfformat/asdf/ndarray/impl/BigDecimalNdArrayImpl.java @@ -12,6 +12,8 @@ import java.util.function.BiConsumer; import java.util.function.Function; +import static org.asdfformat.asdf.ndarray.impl.Float16Utils.float16ToFloat; + public class BigDecimalNdArrayImpl extends NdArrayBase implements BigDecimalNdArray { public BigDecimalNdArrayImpl(final DataType dataType, final int[] shape, final ByteOrder byteOrder, final int[] strides, final int offset, final Block block) { super(dataType, shape, byteOrder, strides, offset, block); @@ -46,6 +48,8 @@ public BigDecimal get(final int... indices) { } else { throw new RuntimeException("Unhandled datatype: " + dataType); } + } else if (dataType.equals(DataTypes.FLOAT16)) { + return BigDecimal.valueOf(float16ToFloat(byteBuffer.getShort())); } else if (dataType.equals(DataTypes.FLOAT32)) { return BigDecimal.valueOf(byteBuffer.getFloat()); } else if (dataType.equals(DataTypes.FLOAT64)) { @@ -84,6 +88,12 @@ public ARRAY toArray(final ARRAY array) { arr[index + i] = valueCreator.apply(buffer); } }; + } else if (dataType.equals(DataTypes.FLOAT16)) { + setter = (byteBuffer, arr, index, length) -> { + for (int i = 0; i < length; i++) { + arr[index + i] = BigDecimal.valueOf(float16ToFloat(byteBuffer.getShort())); + } + }; } else if (dataType.equals(DataTypes.FLOAT32)) { setter = (byteBuffer, arr, index, length) -> { for (int i = 0; i < length; i++) { diff --git a/asdf-core/src/main/java/org/asdfformat/asdf/ndarray/impl/DoubleNdArrayImpl.java b/asdf-core/src/main/java/org/asdfformat/asdf/ndarray/impl/DoubleNdArrayImpl.java index 983ab49..172e674 100644 --- a/asdf-core/src/main/java/org/asdfformat/asdf/ndarray/impl/DoubleNdArrayImpl.java +++ b/asdf-core/src/main/java/org/asdfformat/asdf/ndarray/impl/DoubleNdArrayImpl.java @@ -8,6 +8,8 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; +import static org.asdfformat.asdf.ndarray.impl.Float16Utils.float16ToFloat; + public class DoubleNdArrayImpl extends NdArrayBase implements DoubleNdArray { public DoubleNdArrayImpl(final DataType dataType, final int[] shape, final ByteOrder byteOrder, final int[] strides, final int offset, final Block block) { super(dataType, shape, byteOrder, strides, offset, block); @@ -34,6 +36,8 @@ public double get(int... indices) { return byteBuffer.getDouble(); } else if (dataType.equals(DataTypes.FLOAT32)) { return byteBuffer.getFloat(); + } else if (dataType.equals(DataTypes.FLOAT16)) { + return float16ToFloat(byteBuffer.getShort()); } else { throw new RuntimeException("Unhandled datatype: " + dataType); } @@ -52,6 +56,12 @@ public ARRAY toArray(final ARRAY array) { arr[index + i] = floatArr[i]; } }; + } else if (dataType.equals(DataTypes.FLOAT16)) { + setter = (byteBuffer, arr, index, length) -> { + for (int i = 0; i < length; i++) { + arr[index + i] = float16ToFloat(byteBuffer.getShort()); + } + }; } else { throw new RuntimeException("Unhandled datatype: " + dataType); } diff --git a/asdf-core/src/main/java/org/asdfformat/asdf/ndarray/impl/Float16Utils.java b/asdf-core/src/main/java/org/asdfformat/asdf/ndarray/impl/Float16Utils.java new file mode 100644 index 0000000..41f0e53 --- /dev/null +++ b/asdf-core/src/main/java/org/asdfformat/asdf/ndarray/impl/Float16Utils.java @@ -0,0 +1,81 @@ +package org.asdfformat.asdf.ndarray.impl; + +public class Float16Utils { + public static float float16ToFloat(final short bits) { + final int halfBits = bits & 0xFFFF; + final int sign = (halfBits >>> 15) & 0x1; + final int exponent = (halfBits >>> 10) & 0x1F; + final int mantissa = halfBits & 0x3FF; + + final int floatBits; + if (exponent == 0) { + if (mantissa == 0) { + floatBits = sign << 31; + } else { + // Subnormal: normalize by shifting mantissa until the leading 1 is in bit 10 + int m = mantissa; + int e = -14 + 127; + while ((m & 0x400) == 0) { + m <<= 1; + e--; + } + m &= 0x3FF; + floatBits = (sign << 31) | (e << 23) | (m << 13); + } + } else if (exponent == 31) { + // Inf or NaN: rebased exponent to float32's 255 + floatBits = (sign << 31) | (0xFF << 23) | (mantissa << 13); + } else { + // Normal: rebase exponent from bias-15 to bias-127 + final int floatExponent = exponent - 15 + 127; + floatBits = (sign << 31) | (floatExponent << 23) | (mantissa << 13); + } + + return Float.intBitsToFloat(floatBits); + } + + public static short floatToFloat16(final float value) { + final int floatBits = Float.floatToIntBits(value); + final int sign = (floatBits >>> 31) & 0x1; + final int exponent = (floatBits >>> 23) & 0xFF; + final int mantissa = floatBits & 0x7FFFFF; + + final int halfBits; + if (exponent == 0) { + halfBits = sign << 15; + } else if (exponent == 0xFF) { + if (mantissa == 0) { + halfBits = (sign << 15) | (0x1F << 10); + } else { + final int halfMantissa = mantissa >>> 13; + halfBits = (sign << 15) | (0x1F << 10) | (halfMantissa != 0 ? halfMantissa : 0x1); + } + } else { + final int halfExponent = exponent - 127 + 15; + if (halfExponent >= 31) { + halfBits = (sign << 15) | (0x1F << 10); + } else if (halfExponent <= 0) { + if (halfExponent < -10) { + halfBits = sign << 15; + } else { + final int shift = 1 - halfExponent + 13; + final int m = (mantissa | 0x800000) >>> shift; + final int roundBit = ((mantissa | 0x800000) >>> (shift - 1)) & 0x1; + final int stickyBit = ((mantissa | 0x800000) & ((1 << (shift - 1)) - 1)) != 0 ? 1 : 0; + final int rounded = m + (roundBit & (stickyBit | (m & 1))); + halfBits = (sign << 15) | rounded; + } + } else { + final int truncated = (mantissa >>> 13); + final int roundBit = (mantissa >>> 12) & 0x1; + final int stickyBit = (mantissa & 0xFFF) != 0 ? 1 : 0; + final int rounded = truncated + (roundBit & (stickyBit | (truncated & 1))); + halfBits = (sign << 15) | ((halfExponent << 10) + rounded); + } + } + + return (short) halfBits; + } + + private Float16Utils() {} +} diff --git a/asdf-core/src/main/java/org/asdfformat/asdf/ndarray/impl/FloatNdArrayImpl.java b/asdf-core/src/main/java/org/asdfformat/asdf/ndarray/impl/FloatNdArrayImpl.java index 5bfa9b6..5fb3f20 100644 --- a/asdf-core/src/main/java/org/asdfformat/asdf/ndarray/impl/FloatNdArrayImpl.java +++ b/asdf-core/src/main/java/org/asdfformat/asdf/ndarray/impl/FloatNdArrayImpl.java @@ -8,6 +8,8 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; +import static org.asdfformat.asdf.ndarray.impl.Float16Utils.float16ToFloat; + public class FloatNdArrayImpl extends NdArrayBase implements FloatNdArray { public FloatNdArrayImpl(final DataType dataType, final int[] shape, final ByteOrder byteOrder, final int[] strides, final int offset, final Block block) { super(dataType, shape, byteOrder, strides, offset, block); @@ -32,6 +34,8 @@ public float get(int... indices) { final ByteBuffer byteBuffer = getByteBufferAt(indices); if (dataType.equals(DataTypes.FLOAT32)) { return byteBuffer.getFloat(); + } else if (dataType.equals(DataTypes.FLOAT16)) { + return float16ToFloat(byteBuffer.getShort()); } else { throw new RuntimeException("Unhandled datatype: " + dataType); } @@ -39,7 +43,18 @@ public float get(int... indices) { @Override public ARRAY toArray(final ARRAY array) { - final ArraySetter setter = (byteBuffer, arr, index, length) -> byteBuffer.asFloatBuffer().get(arr, index, length); + final ArraySetter setter; + if (dataType.equals(DataTypes.FLOAT32)) { + setter = (byteBuffer, arr, index, length) -> byteBuffer.asFloatBuffer().get(arr, index, length); + } else if (dataType.equals(DataTypes.FLOAT16)) { + setter = (byteBuffer, arr, index, length) -> { + for (int i = 0; i < length; i++) { + arr[index + i] = float16ToFloat(byteBuffer.getShort()); + } + }; + } else { + throw new RuntimeException("Unhandled datatype: " + dataType); + } return toArray(array, Float.TYPE, setter); } } diff --git a/asdf-core/src/main/java/org/asdfformat/asdf/standard/impl/NdArrayHandler_1_x.java b/asdf-core/src/main/java/org/asdfformat/asdf/standard/impl/NdArrayHandler_1_x.java index 2472307..56dff64 100644 --- a/asdf-core/src/main/java/org/asdfformat/asdf/standard/impl/NdArrayHandler_1_x.java +++ b/asdf-core/src/main/java/org/asdfformat/asdf/standard/impl/NdArrayHandler_1_x.java @@ -35,6 +35,7 @@ public class NdArrayHandler_1_x implements NdArrayHandler { SIMPLE_DATA_TYPES.put("uint8", DataTypes.UINT8); SIMPLE_DATA_TYPES.put("uint16", DataTypes.UINT16); SIMPLE_DATA_TYPES.put("uint32", DataTypes.UINT32); + SIMPLE_DATA_TYPES.put("float16", DataTypes.FLOAT16); SIMPLE_DATA_TYPES.put("float32", DataTypes.FLOAT32); SIMPLE_DATA_TYPES.put("float64", DataTypes.FLOAT64); SIMPLE_DATA_TYPES.put("complex64", DataTypes.COMPLEX64); diff --git a/asdf-core/src/test/java/org/asdfformat/asdf/ndarray/NdArrayFloat16ReferenceTest.java b/asdf-core/src/test/java/org/asdfformat/asdf/ndarray/NdArrayFloat16ReferenceTest.java new file mode 100644 index 0000000..bc0ac0c --- /dev/null +++ b/asdf-core/src/test/java/org/asdfformat/asdf/ndarray/NdArrayFloat16ReferenceTest.java @@ -0,0 +1,120 @@ +package org.asdfformat.asdf.ndarray; + +import org.asdfformat.asdf.Asdf; +import org.asdfformat.asdf.AsdfFile; +import org.asdfformat.asdf.standard.AsdfStandardType; +import org.asdfformat.asdf.testing.CoreReferenceFileType; +import org.asdfformat.asdf.testing.ReferenceFileUtils; +import org.asdfformat.asdf.util.Version; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.io.IOException; +import java.math.BigDecimal; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.stream.Stream; + +import static org.asdfformat.asdf.testing.TestCategories.REFERENCE_TESTS; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Tag(REFERENCE_TESTS) +public class NdArrayFloat16ReferenceTest { + private static final Version FLOAT16_MIN_VERSION = new Version(1, 6, 0); + + private static final CoreReferenceFileType[] FILE_TYPES = { + CoreReferenceFileType.NDARRAY_FLOAT16_1D_BLOCK_BIG, + CoreReferenceFileType.NDARRAY_FLOAT16_1D_BLOCK_LITTLE, + CoreReferenceFileType.NDARRAY_FLOAT16_1D_INLINE, + }; + + private static Stream float16Args() { + return Arrays.stream(FILE_TYPES) + .flatMap(fileType -> Arrays.stream(AsdfStandardType.values()) + .filter(std -> std.getVersion().compareTo(FLOAT16_MIN_VERSION) >= 0) + .map(std -> Arguments.of(fileType, std))); + } + + @ParameterizedTest + @MethodSource("float16Args") + public void testFloat1d(final CoreReferenceFileType coreTestFileType, final AsdfStandardType asdfStandardType) throws IOException { + final Path path = ReferenceFileUtils.getPath(coreTestFileType, asdfStandardType.getVersion()); + + try (final AsdfFile asdfFile = Asdf.open(path)) { + final FloatNdArray floatNdArray = asdfFile.getTree().get("arr").asNdArray().asFloatNdArray(); + + assertEquals(-65504.0f, floatNdArray.get(0)); + assertEquals(65504.0f, floatNdArray.get(1)); + assertEquals(5.9604645E-8f, floatNdArray.get(2)); + assertEquals(0.0f, floatNdArray.get(3)); + assertTrue(Float.isNaN(floatNdArray.get(4))); + assertEquals(Float.POSITIVE_INFINITY, floatNdArray.get(5)); + assertEquals(Float.NEGATIVE_INFINITY, floatNdArray.get(6)); + assertEquals(3.140625f, floatNdArray.get(7)); + assertEquals(-3.140625f, floatNdArray.get(8)); + + final float[] arr = floatNdArray.toArray(new float[9]); + assertEquals(-65504.0f, arr[0]); + assertEquals(65504.0f, arr[1]); + assertEquals(5.9604645E-8f, arr[2]); + assertEquals(0.0f, arr[3]); + assertTrue(Float.isNaN(arr[4])); + assertEquals(Float.POSITIVE_INFINITY, arr[5]); + assertEquals(Float.NEGATIVE_INFINITY, arr[6]); + assertEquals(3.140625f, arr[7]); + assertEquals(-3.140625f, arr[8]); + } + } + + @ParameterizedTest + @MethodSource("float16Args") + public void testDouble1d(final CoreReferenceFileType coreTestFileType, final AsdfStandardType asdfStandardType) throws IOException { + final Path path = ReferenceFileUtils.getPath(coreTestFileType, asdfStandardType.getVersion()); + + try (final AsdfFile asdfFile = Asdf.open(path)) { + final DoubleNdArray doubleNdArray = asdfFile.getTree().get("arr").asNdArray().asDoubleNdArray(); + + assertEquals(-65504.0, doubleNdArray.get(0)); + assertEquals(65504.0, doubleNdArray.get(1)); + assertEquals(5.960464477539063E-8, doubleNdArray.get(2)); + assertEquals(0.0, doubleNdArray.get(3)); + assertTrue(Double.isNaN(doubleNdArray.get(4))); + assertEquals(Double.POSITIVE_INFINITY, doubleNdArray.get(5)); + assertEquals(Double.NEGATIVE_INFINITY, doubleNdArray.get(6)); + assertEquals(3.140625, doubleNdArray.get(7)); + assertEquals(-3.140625, doubleNdArray.get(8)); + + final double[] arr = doubleNdArray.toArray(new double[9]); + assertEquals(-65504.0, arr[0]); + assertEquals(65504.0, arr[1]); + assertEquals(5.960464477539063E-8, arr[2]); + assertEquals(0.0, arr[3]); + assertTrue(Double.isNaN(arr[4])); + assertEquals(Double.POSITIVE_INFINITY, arr[5]); + assertEquals(Double.NEGATIVE_INFINITY, arr[6]); + assertEquals(3.140625, arr[7]); + assertEquals(-3.140625, arr[8]); + } + } + + @ParameterizedTest + @MethodSource("float16Args") + public void testBigDecimal1d(final CoreReferenceFileType coreTestFileType, final AsdfStandardType asdfStandardType) throws IOException { + final Path path = ReferenceFileUtils.getPath(coreTestFileType, asdfStandardType.getVersion()); + + try (final AsdfFile asdfFile = Asdf.open(path)) { + final BigDecimalNdArray bigDecimalNdArray = asdfFile.getTree().get("arr").asNdArray().asBigDecimalNdArray(); + + assertEquals(BigDecimal.valueOf(-65504.0), bigDecimalNdArray.get(0)); + assertEquals(BigDecimal.valueOf(65504.0), bigDecimalNdArray.get(1)); + assertEquals(BigDecimal.valueOf(5.960464477539063E-8), bigDecimalNdArray.get(2)); + assertEquals(BigDecimal.valueOf(0.0), bigDecimalNdArray.get(3)); + assertEquals(BigDecimal.valueOf(3.140625), bigDecimalNdArray.get(7)); + assertEquals(BigDecimal.valueOf(-3.140625), bigDecimalNdArray.get(8)); + + } + } +} diff --git a/asdf-core/src/test/java/org/asdfformat/asdf/ndarray/impl/Float16UtilsTest.java b/asdf-core/src/test/java/org/asdfformat/asdf/ndarray/impl/Float16UtilsTest.java new file mode 100644 index 0000000..53c6b84 --- /dev/null +++ b/asdf-core/src/test/java/org/asdfformat/asdf/ndarray/impl/Float16UtilsTest.java @@ -0,0 +1,210 @@ +package org.asdfformat.asdf.ndarray.impl; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class Float16UtilsTest { + @Test + public void testPositiveZero() { + assertEquals(0.0f, Float16Utils.float16ToFloat((short) 0x0000)); + assertEquals( + Float.floatToIntBits(0.0f), + Float.floatToIntBits(Float16Utils.float16ToFloat((short) 0x0000)) + ); + } + + @Test + public void testNegativeZero() { + assertEquals(-0.0f, Float16Utils.float16ToFloat((short) 0x8000)); + assertEquals( + Float.floatToIntBits(-0.0f), + Float.floatToIntBits(Float16Utils.float16ToFloat((short) 0x8000)) + ); + } + + @Test + public void testOne() { + assertEquals(1.0f, Float16Utils.float16ToFloat((short) 0x3C00)); + } + + @Test + public void testTwo() { + assertEquals(2.0f, Float16Utils.float16ToFloat((short) 0x4000)); + } + + @Test + public void testNegativeOne() { + assertEquals(-1.0f, Float16Utils.float16ToFloat((short) 0xBC00)); + } + + @Test + public void testSmallestPositiveSubnormal() { + final float result = Float16Utils.float16ToFloat((short) 0x0001); + assertTrue(result > 0.0f); + assertEquals(5.9604645E-8f, result); + } + + @Test + public void testLargestSubnormal() { + final float result = Float16Utils.float16ToFloat((short) 0x03FF); + assertTrue(result > 0.0f); + assertEquals(6.097555E-5f, result); + } + + @Test + public void testSmallestPositiveNormal() { + final float result = Float16Utils.float16ToFloat((short) 0x0400); + assertTrue(result > 0.0f); + assertEquals(6.1035156E-5f, result); + } + + @Test + public void testMaxFiniteValue() { + assertEquals(65504.0f, Float16Utils.float16ToFloat((short) 0x7BFF)); + } + + @Test + public void testNegativeMaxFiniteValue() { + assertEquals(-65504.0f, Float16Utils.float16ToFloat((short) 0xFBFF)); + } + + @Test + public void testPositiveInfinity() { + assertEquals(Float.POSITIVE_INFINITY, Float16Utils.float16ToFloat((short) 0x7C00)); + } + + @Test + public void testNegativeInfinity() { + assertEquals(Float.NEGATIVE_INFINITY, Float16Utils.float16ToFloat((short) 0xFC00)); + } + + @Test + public void testNaN() { + assertTrue(Float.isNaN(Float16Utils.float16ToFloat((short) 0x7E00))); + assertEquals(0x7FC00000, Float.floatToRawIntBits(Float16Utils.float16ToFloat((short) 0x7E00))); + } + + @Test + public void testSmallestNaN() { + final float result = Float16Utils.float16ToFloat((short) 0x7C01); + assertTrue(Float.isNaN(result)); + assertEquals(0x7F800000 | (0x001 << 13), Float.floatToRawIntBits(result)); + } + + @Test + public void testNegativeNaN() { + final float result = Float16Utils.float16ToFloat((short) 0xFE00); + assertTrue(Float.isNaN(result)); + assertTrue(Float.floatToRawIntBits(result) < 0); + } + + @Test + public void testFloatToFloat16RoundTrip() { + final short[] patterns = { + (short) 0x0000, (short) 0x8000, + (short) 0x3C00, (short) 0xBC00, + (short) 0x4000, + (short) 0x0001, (short) 0x03FF, (short) 0x0400, + (short) 0x7BFF, (short) 0xFBFF, + (short) 0x7C00, (short) 0xFC00, + }; + + for (final short pattern : patterns) { + final float value = Float16Utils.float16ToFloat(pattern); + if (!Float.isNaN(value)) { + assertEquals(pattern, Float16Utils.floatToFloat16(value), + String.format("Round-trip failed for pattern 0x%04X", pattern & 0xFFFF)); + } + } + } + + @Test + public void testFloatToFloat16NaN() { + final short result = Float16Utils.floatToFloat16(Float.NaN); + final int bits = result & 0xFFFF; + assertEquals(0x1F, (bits >>> 10) & 0x1F); + assertTrue((bits & 0x3FF) != 0); + } + + @Test + public void testFloatToFloat16Overflow() { + assertEquals((short) 0x7C00, Float16Utils.floatToFloat16(Float.MAX_VALUE)); + assertEquals((short) 0x7C00, Float16Utils.floatToFloat16(100000.0f)); + } + + @Test + public void testFloatToFloat16NegativeOverflow() { + assertEquals((short) 0xFC00, Float16Utils.floatToFloat16(-Float.MAX_VALUE)); + } + + @Test + public void testFloatToFloat16Underflow() { + assertEquals((short) 0x0000, Float16Utils.floatToFloat16(1.0E-10f)); + } + + @Test + public void testFloatToFloat16Infinities() { + assertEquals((short) 0x7C00, Float16Utils.floatToFloat16(Float.POSITIVE_INFINITY)); + assertEquals((short) 0xFC00, Float16Utils.floatToFloat16(Float.NEGATIVE_INFINITY)); + } + + @Test + public void testFloatToFloat16Subnormal() { + final short result = Float16Utils.floatToFloat16(5.9604645E-8f); + assertEquals((short) 0x0001, result); + } + + @Test + public void testOneThirdApproximation() { + final float result = Float16Utils.float16ToFloat((short) 0x3555); + assertEquals(0.33325195f, result); + } + + @Test + public void testRoundToNearestEvenTieRoundsDown() { + // 1.0 in float16 = 0x3C00, next representable = 1.0009765625 = 0x3C01 + // Exact midpoint between 1.0 and 1.0009765625: 1.00048828125 + // 1.0 has even mantissa (0), so tie rounds down to 1.0 + assertEquals((short) 0x3C00, Float16Utils.floatToFloat16(1.00048828125f)); + } + + @Test + public void testRoundToNearestEvenTieRoundsUp() { + // 1.0009765625 in float16 = 0x3C01 (odd mantissa), next = 1.001953125 = 0x3C02 + // Exact midpoint: 1.0014648437500 + // 0x3C01 has odd mantissa (1), so tie rounds up to 0x3C02 + assertEquals((short) 0x3C02, Float16Utils.floatToFloat16(1.0014648437500f)); + } + + @Test + public void testRoundUpWhenAboveMidpoint() { + // Just above the midpoint between 1.0 and 1.0009765625 should round up + assertEquals((short) 0x3C01, Float16Utils.floatToFloat16(1.0005f)); + } + + @Test + public void testRoundDownWhenBelowMidpoint() { + // Just below the midpoint between 1.0 and 1.0009765625 should round down + assertEquals((short) 0x3C00, Float16Utils.floatToFloat16(1.0004f)); + } + + @Test + public void testFloatToFloat16RoundingOverflowToInfinity() { + // halfExponent=30, mantissa rounds up from 0x3FF to 0x400, carrying into exponent → infinity + assertEquals((short) 0x7C00, Float16Utils.floatToFloat16(65520.0f)); + assertEquals((short) 0xFC00, Float16Utils.floatToFloat16(-65520.0f)); + } + + @Test + public void testFloatToFloat16Float32Subnormal() { + assertEquals((short) 0x0000, Float16Utils.floatToFloat16(Float.MIN_VALUE)); + assertEquals((short) 0x8000, Float16Utils.floatToFloat16(-Float.MIN_VALUE)); + } + + @Test + public void testFloatToFloat16NegativeSubnormal() { + assertEquals((short) 0x8001, Float16Utils.floatToFloat16(-5.9604645E-8f)); + } +} diff --git a/asdf-core/src/test/java/org/asdfformat/asdf/testing/CoreReferenceFileType.java b/asdf-core/src/test/java/org/asdfformat/asdf/testing/CoreReferenceFileType.java index be1ac4a..fdcaf66 100644 --- a/asdf-core/src/test/java/org/asdfformat/asdf/testing/CoreReferenceFileType.java +++ b/asdf-core/src/test/java/org/asdfformat/asdf/testing/CoreReferenceFileType.java @@ -5,6 +5,9 @@ public enum CoreReferenceFileType implements ReferenceFile { NDARRAY_COMPRESSED_ZLIB, + NDARRAY_FLOAT16_1D_BLOCK_BIG, + NDARRAY_FLOAT16_1D_BLOCK_LITTLE, + NDARRAY_FLOAT16_1D_INLINE, NDARRAY_FLOAT64_1D_BLOCK_BIG, NDARRAY_FLOAT64_1D_BLOCK_LITTLE, NDARRAY_FLOAT64_1D_INLINE, diff --git a/asdf-core/src/test/resources/reference-file-scripts/ndarray_float16_1d_block_big.py b/asdf-core/src/test/resources/reference-file-scripts/ndarray_float16_1d_block_big.py new file mode 100644 index 0000000..df5371a --- /dev/null +++ b/asdf-core/src/test/resources/reference-file-scripts/ndarray_float16_1d_block_big.py @@ -0,0 +1,14 @@ +af["arr"] = np.array( + [ + -np.finfo(np.float16).max, + np.finfo(np.float16).max, + np.finfo(np.float16).smallest_subnormal, + 0, + np.nan, + np.inf, + -np.inf, + np.float16(3.14), + np.float16(-3.14), + ], + dtype=np.dtype(">e") +) diff --git a/asdf-core/src/test/resources/reference-file-scripts/ndarray_float16_1d_block_little.py b/asdf-core/src/test/resources/reference-file-scripts/ndarray_float16_1d_block_little.py new file mode 100644 index 0000000..fb7efe9 --- /dev/null +++ b/asdf-core/src/test/resources/reference-file-scripts/ndarray_float16_1d_block_little.py @@ -0,0 +1,14 @@ +af["arr"] = np.array( + [ + -np.finfo(np.float16).max, + np.finfo(np.float16).max, + np.finfo(np.float16).smallest_subnormal, + 0, + np.nan, + np.inf, + -np.inf, + np.float16(3.14), + np.float16(-3.14), + ], + dtype=np.dtype("