From 46e0e29d6d7a64d55052bb351eaf294b8392f5f1 Mon Sep 17 00:00:00 2001 From: zhou yong kang Date: Mon, 2 Feb 2026 14:36:49 +0800 Subject: [PATCH 01/16] fix: init --- compiler/fory_compiler/generators/java.py | 12 +- .../fory/serializer/ArraySerializers.java | 48 ++ .../fory/serializer/PrimitiveSerializers.java | 20 + .../java/org/apache/fory/type/DispatchId.java | 31 +- .../java/org/apache/fory/type/Float16.java | 451 ++++++++++++++++++ .../main/java/org/apache/fory/type/Types.java | 1 + .../serializer/Float16SerializerTest.java | 269 +++++++++++ .../org/apache/fory/type/Float16Test.java | 316 ++++++++++++ 8 files changed, 1130 insertions(+), 18 deletions(-) create mode 100644 java/fory-core/src/main/java/org/apache/fory/type/Float16.java create mode 100644 java/fory-core/src/test/java/org/apache/fory/serializer/Float16SerializerTest.java create mode 100644 java/fory-core/src/test/java/org/apache/fory/type/Float16Test.java diff --git a/compiler/fory_compiler/generators/java.py b/compiler/fory_compiler/generators/java.py index 11c479e10d..26b5e3ffc9 100644 --- a/compiler/fory_compiler/generators/java.py +++ b/compiler/fory_compiler/generators/java.py @@ -226,7 +226,7 @@ def generate_bytes_methods(self, class_name: str) -> List[str]: PrimitiveKind.UINT64: "long", PrimitiveKind.VAR_UINT64: "long", PrimitiveKind.TAGGED_UINT64: "long", - PrimitiveKind.FLOAT16: "float", + PrimitiveKind.FLOAT16: "org.apache.fory.type.Float16", PrimitiveKind.FLOAT32: "float", PrimitiveKind.FLOAT64: "double", PrimitiveKind.STRING: "String", @@ -255,7 +255,7 @@ def generate_bytes_methods(self, class_name: str) -> List[str]: PrimitiveKind.UINT64: "Long", PrimitiveKind.VAR_UINT64: "Long", PrimitiveKind.TAGGED_UINT64: "Long", - PrimitiveKind.FLOAT16: "Float", + PrimitiveKind.FLOAT16: "org.apache.fory.type.Float16", PrimitiveKind.FLOAT32: "Float", PrimitiveKind.FLOAT64: "Double", PrimitiveKind.ANY: "Object", @@ -278,7 +278,7 @@ def generate_bytes_methods(self, class_name: str) -> List[str]: PrimitiveKind.UINT64: "long[]", PrimitiveKind.VAR_UINT64: "long[]", PrimitiveKind.TAGGED_UINT64: "long[]", - PrimitiveKind.FLOAT16: "float[]", + PrimitiveKind.FLOAT16: "org.apache.fory.type.Float16[]", PrimitiveKind.FLOAT32: "float[]", PrimitiveKind.FLOAT64: "double[]", } @@ -1370,7 +1370,11 @@ def generate_equals_method(self, message: Message) -> List[str]: ) elif isinstance(field.field_type, PrimitiveType): kind = field.field_type.kind - if kind in (PrimitiveKind.FLOAT32,): + if kind in (PrimitiveKind.FLOAT16,): + comparisons.append( + f"{field_name}.equalsValue(that.{field_name})" + ) + elif kind in (PrimitiveKind.FLOAT32,): comparisons.append( f"Float.compare({field_name}, that.{field_name}) == 0" ) diff --git a/java/fory-core/src/main/java/org/apache/fory/serializer/ArraySerializers.java b/java/fory-core/src/main/java/org/apache/fory/serializer/ArraySerializers.java index 68a0e654dc..aaa78b4848 100644 --- a/java/fory-core/src/main/java/org/apache/fory/serializer/ArraySerializers.java +++ b/java/fory-core/src/main/java/org/apache/fory/serializer/ArraySerializers.java @@ -881,6 +881,52 @@ private void readFloat64BySwapEndian(MemoryBuffer buffer, double[] values, int n } } + public static final class Float16ArraySerializer + extends PrimitiveArraySerializer { + + public Float16ArraySerializer(Fory fory) { + super(fory, org.apache.fory.type.Float16[].class); + } + + @Override + public void write(MemoryBuffer buffer, org.apache.fory.type.Float16[] value) { + int length = value.length; + int size = length * 2; + buffer.writeVarUint32Small7(size); + + int writerIndex = buffer.writerIndex(); + buffer.ensure(writerIndex + size); + + for (int i = 0; i < length; i++) { + buffer._unsafePutInt16(writerIndex + i * 2, value[i].toBits()); + } + buffer._unsafeWriterIndex(writerIndex + size); + } + + @Override + public org.apache.fory.type.Float16[] copy(org.apache.fory.type.Float16[] originArray) { + return Arrays.copyOf(originArray, originArray.length); + } + + @Override + public org.apache.fory.type.Float16[] read(MemoryBuffer buffer) { + int size = buffer.readVarUint32Small7(); + int numElements = size / 2; + org.apache.fory.type.Float16[] values = new org.apache.fory.type.Float16[numElements]; + + int readerIndex = buffer.readerIndex(); + buffer.checkReadableBytes(size); + + for (int i = 0; i < numElements; i++) { + values[i] = + org.apache.fory.type.Float16.fromBits(buffer._unsafeGetInt16(readerIndex + i * 2)); + } + buffer._increaseReaderIndexUnsafe(size); + + return values; + } + } + public static final class StringArraySerializer extends Serializer { private final StringSerializer stringSerializer; private final ForyArrayAsListSerializer collectionSerializer; @@ -1008,6 +1054,8 @@ public static void registerDefaultSerializers(Fory fory) { resolver.registerInternalSerializer(double[].class, new DoubleArraySerializer(fory)); resolver.registerInternalSerializer( Double[].class, new ObjectArraySerializer<>(fory, Double[].class)); + resolver.registerInternalSerializer( + org.apache.fory.type.Float16[].class, new Float16ArraySerializer(fory)); resolver.registerInternalSerializer(boolean[].class, new BooleanArraySerializer(fory)); resolver.registerInternalSerializer( Boolean[].class, new ObjectArraySerializer<>(fory, Boolean[].class)); diff --git a/java/fory-core/src/main/java/org/apache/fory/serializer/PrimitiveSerializers.java b/java/fory-core/src/main/java/org/apache/fory/serializer/PrimitiveSerializers.java index 3c44fc5e1a..5a567f2372 100644 --- a/java/fory-core/src/main/java/org/apache/fory/serializer/PrimitiveSerializers.java +++ b/java/fory-core/src/main/java/org/apache/fory/serializer/PrimitiveSerializers.java @@ -342,6 +342,23 @@ public Double read(MemoryBuffer buffer) { } } + public static final class Float16Serializer + extends CrossLanguageCompatibleSerializer { + public Float16Serializer(Fory fory, Class cls) { + super(fory, (Class) cls, false, true); + } + + @Override + public void write(MemoryBuffer buffer, org.apache.fory.type.Float16 value) { + buffer.writeInt16(value.toBits()); + } + + @Override + public org.apache.fory.type.Float16 read(MemoryBuffer buffer) { + return org.apache.fory.type.Float16.fromBits(buffer.readInt16()); + } + } + public static void registerDefaultSerializers(Fory fory) { // primitive types will be boxed. TypeResolver resolver = fory.getTypeResolver(); @@ -361,5 +378,8 @@ public static void registerDefaultSerializers(Fory fory) { resolver.registerInternalSerializer(Long.class, new LongSerializer(fory, Long.class)); resolver.registerInternalSerializer(Float.class, new FloatSerializer(fory, Float.class)); resolver.registerInternalSerializer(Double.class, new DoubleSerializer(fory, Double.class)); + resolver.registerInternalSerializer( + org.apache.fory.type.Float16.class, + new Float16Serializer(fory, org.apache.fory.type.Float16.class)); } } diff --git a/java/fory-core/src/main/java/org/apache/fory/type/DispatchId.java b/java/fory-core/src/main/java/org/apache/fory/type/DispatchId.java index d154e3352d..fa98a48d1f 100644 --- a/java/fory-core/src/main/java/org/apache/fory/type/DispatchId.java +++ b/java/fory-core/src/main/java/org/apache/fory/type/DispatchId.java @@ -44,20 +44,21 @@ public class DispatchId { public static final int TAGGED_INT64 = 9; public static final int FLOAT32 = 10; public static final int FLOAT64 = 11; - public static final int UINT8 = 12; - public static final int UINT16 = 13; - public static final int UINT32 = 14; - public static final int VAR_UINT32 = 15; - public static final int UINT64 = 16; - public static final int VAR_UINT64 = 17; - public static final int TAGGED_UINT64 = 18; - public static final int EXT_UINT8 = 19; - public static final int EXT_UINT16 = 20; - public static final int EXT_UINT32 = 21; - public static final int EXT_VAR_UINT32 = 22; - public static final int EXT_UINT64 = 23; - public static final int EXT_VAR_UINT64 = 24; - public static final int STRING = 25; + public static final int FLOAT16 = 12; + public static final int UINT8 = 13; + public static final int UINT16 = 14; + public static final int UINT32 = 15; + public static final int VAR_UINT32 = 16; + public static final int UINT64 = 17; + public static final int VAR_UINT64 = 18; + public static final int TAGGED_UINT64 = 19; + public static final int EXT_UINT8 = 20; + public static final int EXT_UINT16 = 21; + public static final int EXT_UINT32 = 22; + public static final int EXT_VAR_UINT32 = 23; + public static final int EXT_UINT64 = 24; + public static final int EXT_VAR_UINT64 = 25; + public static final int STRING = 26; public static int getDispatchId(Fory fory, Descriptor d) { int typeId = Types.getDescriptorTypeId(fory, d); @@ -101,6 +102,8 @@ private static int xlangTypeIdToDispatchId(int typeId) { return VAR_UINT64; case Types.TAGGED_UINT64: return TAGGED_UINT64; + case Types.FLOAT16: + return FLOAT16; case Types.FLOAT32: return FLOAT32; case Types.FLOAT64: diff --git a/java/fory-core/src/main/java/org/apache/fory/type/Float16.java b/java/fory-core/src/main/java/org/apache/fory/type/Float16.java new file mode 100644 index 0000000000..f051a8d145 --- /dev/null +++ b/java/fory-core/src/main/java/org/apache/fory/type/Float16.java @@ -0,0 +1,451 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fory.type; + +import java.io.Serializable; + +/** + * IEEE 754 binary16 半精度浮点数(16位). + * + *

格式:1位符号 + 5位指数 + 10位尾数 + * + *

    + *
  • 指数偏移:15 + *
  • 范围:±65504(最大正规数) + *
  • 最小正规数:2^-14 ≈ 6.104e-5 + *
  • 最小次正规数:2^-24 ≈ 5.96e-8 + *
+ * + *

此类是不可变的,线程安全。所有算术运算通过提升到 float32 执行,然后舍入回 float16。 + * + *

转换使用 IEEE 754 round-to-nearest-even 舍入模式。 + */ +public final class Float16 extends Number implements Comparable, Serializable { + private static final long serialVersionUID = 1L; + + // ========== 位掩码常量 ========== + private static final int SIGN_MASK = 0x8000; + private static final int EXP_MASK = 0x7C00; + private static final int MANT_MASK = 0x03FF; + + // ========== 特殊值位模式 ========== + private static final short BITS_NAN = (short) 0x7E00; // 标准静默 NaN + private static final short BITS_POS_INF = (short) 0x7C00; // +Inf + private static final short BITS_NEG_INF = (short) 0xFC00; // -Inf + private static final short BITS_NEG_ZERO = (short) 0x8000; // -0 + private static final short BITS_MAX = (short) 0x7BFF; // 65504 + private static final short BITS_ONE = (short) 0x3C00; // 1.0 + private static final short BITS_MIN_NORMAL = (short) 0x0400; // 2^-14 + private static final short BITS_MIN_VALUE = (short) 0x0001; // 最小次正规数 + + // ========== 公共常量 ========== + /** 非数字(Not-a-Number)值. */ + public static final Float16 NaN = new Float16(BITS_NAN); + + /** 正无穷大. */ + public static final Float16 POSITIVE_INFINITY = new Float16(BITS_POS_INF); + + /** 负无穷大. */ + public static final Float16 NEGATIVE_INFINITY = new Float16(BITS_NEG_INF); + + /** 正零. */ + public static final Float16 ZERO = new Float16((short) 0); + + /** 负零. */ + public static final Float16 NEGATIVE_ZERO = new Float16(BITS_NEG_ZERO); + + /** 值为 1.0 的常量. */ + public static final Float16 ONE = new Float16(BITS_ONE); + + /** 最大有限值 65504. */ + public static final Float16 MAX_VALUE = new Float16(BITS_MAX); + + /** 最小正规数 2^-14. */ + public static final Float16 MIN_NORMAL = new Float16(BITS_MIN_NORMAL); + + /** 最小正值(次正规数)2^-24. */ + public static final Float16 MIN_VALUE = new Float16(BITS_MIN_VALUE); + + /** Float16 的位数. */ + public static final int SIZE_BITS = 16; + + /** Float16 的字节数. */ + public static final int SIZE_BYTES = 2; + + // ========== 存储字段 ========== + private final short bits; + + // ========== 构造方法 ========== + + private Float16(short bits) { + this.bits = bits; + } + + /** + * 从位模式创建 Float16. + * + * @param bits IEEE 754 binary16 位模式 + * @return Float16 实例 + */ + public static Float16 fromBits(short bits) { + return new Float16(bits); + } + + /** + * 从 float 值创建 Float16(带舍入). + * + *

使用 IEEE 754 round-to-nearest-even 舍入模式。超出范围的值会舍入到 ±Infinity。 + * + * @param value float 值 + * @return Float16 实例 + */ + public static Float16 valueOf(float value) { + return new Float16(floatToFloat16Bits(value)); + } + + /** + * 获取位模式. + * + * @return IEEE 754 binary16 位模式 + */ + public short toBits() { + return bits; + } + + // ========== IEEE 754 转换算法 ========== + + /** + * 将 float32 转换为 float16 位模式。使用 round-to-nearest-even 舍入模式。 + * + *

参考 Go 实现:go/fory/float16/float16.go + */ + private static short floatToFloat16Bits(float f32) { + int bits32 = Float.floatToRawIntBits(f32); + int sign = (bits32 >>> 31) & 0x1; + int exp = (bits32 >>> 23) & 0xFF; + int mant = bits32 & 0x7FFFFF; + + int outSign = sign << 15; + int outExp; + int outMant; + + // 特殊值:NaN 或 Inf + if (exp == 0xFF) { + outExp = 0x1F; + if (mant != 0) { + // NaN:保留尾数高位,确保至少有一位 + outMant = 0x200 | ((mant >>> 13) & 0x1FF); + if (outMant == 0x200) { + outMant = 0x201; // 确保至少一位非零 + } + } else { + // Inf + outMant = 0; + } + } + // 零或次正规 float32 + else if (exp == 0) { + outExp = 0; + outMant = 0; + } + // 正规数 + else { + int newExp = exp - 127 + 15; + + // 上溢到 Inf + if (newExp >= 31) { + outExp = 0x1F; + outMant = 0; + } + // 下溢到次正规或零 + else if (newExp <= 0) { + // 隐式 1 + 尾数 + int fullMant = mant | 0x800000; + int shift = 1 - newExp; + int netShift = 13 + shift; + + if (netShift >= 24) { + // 太小,变为零 + outExp = 0; + outMant = 0; + } else { + outExp = 0; + // 舍入到最近偶数 + int roundBit = (fullMant >>> (netShift - 1)) & 1; + int sticky = fullMant & ((1 << (netShift - 1)) - 1); + outMant = fullMant >>> netShift; + + if (roundBit == 1 && (sticky != 0 || (outMant & 1) == 1)) { + outMant++; + } + } + } + // 正规范围 + else { + outExp = newExp; + outMant = mant >>> 13; + + // 舍入到最近偶数 + int roundBit = (mant >>> 12) & 1; + int sticky = mant & 0xFFF; + + if (roundBit == 1 && (sticky != 0 || (outMant & 1) == 1)) { + outMant++; + if (outMant > 0x3FF) { + // 尾数溢出,增加指数 + outMant = 0; + outExp++; + if (outExp >= 31) { + outExp = 0x1F; // 溢出到 Inf + } + } + } + } + } + + return (short) (outSign | (outExp << 10) | outMant); + } + + /** + * 将 float16 位模式转换为 float32. + * + *

此转换对所有 float16 值都是精确的。 + */ + private static float float16BitsToFloat(short bits16) { + int bits = bits16 & 0xFFFF; + int sign = (bits >>> 15) & 0x1; + int exp = (bits >>> 10) & 0x1F; + int mant = bits & 0x3FF; + + int outBits = sign << 31; + + // NaN 或 Inf + if (exp == 0x1F) { + outBits |= 0xFF << 23; + if (mant != 0) { + // NaN:提升尾数 + outBits |= mant << 13; + } + } + // 零或次正规 + else if (exp == 0) { + if (mant == 0) { + // 有符号零 + // outBits 已经正确 + } else { + // 次正规:归一化 + // 值 = (-1)^S * 2^-14 * (mant / 1024) + // 需要找到隐式 1 的位置 + int shift = Integer.numberOfLeadingZeros(mant) - 22; // 32 - 10 + mant = (mant << shift) & 0x3FF; + int newExp = 1 - 15 - shift + 127; + outBits |= newExp << 23; + outBits |= mant << 13; + } + } + // 正规数 + else { + outBits |= (exp - 15 + 127) << 23; + outBits |= mant << 13; + } + + return Float.intBitsToFloat(outBits); + } + + // ========== 分类方法 ========== + + /** 判断是否为 NaN. */ + public boolean isNaN() { + return (bits & EXP_MASK) == EXP_MASK && (bits & MANT_MASK) != 0; + } + + /** 判断是否为无穷大(正或负). */ + public boolean isInfinite() { + return (bits & EXP_MASK) == EXP_MASK && (bits & MANT_MASK) == 0; + } + + /** 判断是否为有限值(非 NaN 且非无穷大). */ + public boolean isFinite() { + return (bits & EXP_MASK) != EXP_MASK; + } + + /** 判断是否为零(正零或负零). */ + public boolean isZero() { + return (bits & (EXP_MASK | MANT_MASK)) == 0; + } + + /** 判断是否为正规数(非零、非次正规、非无穷大、非 NaN). */ + public boolean isNormal() { + int exp = bits & EXP_MASK; + return exp != 0 && exp != EXP_MASK; + } + + /** 判断是否为次正规数. */ + public boolean isSubnormal() { + return (bits & EXP_MASK) == 0 && (bits & MANT_MASK) != 0; + } + + /** 判断符号位(true 表示负数或负零). */ + public boolean signbit() { + return (bits & SIGN_MASK) != 0; + } + + // ========== 算术运算(通过 float32 提升)========== + + /** + * 加法运算. + * + * @param other 另一个 Float16 值 + * @return 相加结果 + */ + public Float16 add(Float16 other) { + return valueOf(floatValue() + other.floatValue()); + } + + /** + * 减法运算. + * + * @param other 另一个 Float16 值 + * @return 相减结果 + */ + public Float16 subtract(Float16 other) { + return valueOf(floatValue() - other.floatValue()); + } + + /** + * 乘法运算. + * + * @param other 另一个 Float16 值 + * @return 相乘结果 + */ + public Float16 multiply(Float16 other) { + return valueOf(floatValue() * other.floatValue()); + } + + /** + * 除法运算. + * + * @param other 另一个 Float16 值 + * @return 相除结果 + */ + public Float16 divide(Float16 other) { + return valueOf(floatValue() / other.floatValue()); + } + + /** + * 取反运算. + * + * @return 取反结果 + */ + public Float16 negate() { + return fromBits((short) (bits ^ SIGN_MASK)); + } + + /** + * 绝对值运算. + * + * @return 绝对值 + */ + public Float16 abs() { + return fromBits((short) (bits & ~SIGN_MASK)); + } + + // ========== Number 实现 ========== + + @Override + public float floatValue() { + return float16BitsToFloat(bits); + } + + @Override + public double doubleValue() { + return floatValue(); + } + + @Override + public int intValue() { + return (int) floatValue(); + } + + @Override + public long longValue() { + return (long) floatValue(); + } + + @Override + public byte byteValue() { + return (byte) floatValue(); + } + + @Override + public short shortValue() { + return (short) floatValue(); + } + + // ========== 比较方法 ========== + + /** + * 值相等比较(IEEE 754 语义:NaN != NaN,+0 == -0). + * + * @param other 另一个 Float16 值 + * @return 是否数值相等 + */ + public boolean equalsValue(Float16 other) { + if (isNaN() || other.isNaN()) { + return false; + } + if (isZero() && other.isZero()) { + return true; + } + return bits == other.bits; + } + + @Override + public int compareTo(Float16 other) { + return Float.compare(floatValue(), other.floatValue()); + } + + /** + * 对象相等比较(位模式相等). + * + *

注意:此方法使用位模式相等,与 {@link #equalsValue(Float16)} 不同。 对于 IEEE 754 数值相等,请使用 equalsValue()。 + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Float16)) { + return false; + } + Float16 other = (Float16) obj; + return bits == other.bits; + } + + @Override + public int hashCode() { + return Short.hashCode(bits); + } + + @Override + public String toString() { + return Float.toString(floatValue()); + } +} diff --git a/java/fory-core/src/main/java/org/apache/fory/type/Types.java b/java/fory-core/src/main/java/org/apache/fory/type/Types.java index 568ee5d86f..d6e845c29f 100644 --- a/java/fory-core/src/main/java/org/apache/fory/type/Types.java +++ b/java/fory-core/src/main/java/org/apache/fory/type/Types.java @@ -416,6 +416,7 @@ public static Class getClassForTypeId(int typeId) { case TAGGED_UINT64: return Long.class; case FLOAT16: + return org.apache.fory.type.Float16.class; case FLOAT32: return Float.class; case FLOAT64: diff --git a/java/fory-core/src/test/java/org/apache/fory/serializer/Float16SerializerTest.java b/java/fory-core/src/test/java/org/apache/fory/serializer/Float16SerializerTest.java new file mode 100644 index 0000000000..8b74e241bb --- /dev/null +++ b/java/fory-core/src/test/java/org/apache/fory/serializer/Float16SerializerTest.java @@ -0,0 +1,269 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fory.serializer; + +import static org.testng.Assert.*; + +import lombok.AllArgsConstructor; +import lombok.Data; +import org.apache.fory.Fory; +import org.apache.fory.ForyTestBase; +import org.apache.fory.config.Language; +import org.apache.fory.type.Float16; +import org.testng.annotations.Test; + +/** 测试 Float16 序列化器。 */ +public class Float16SerializerTest extends ForyTestBase { + + @Test + public void testFloat16Serialization() { + Fory fory = Fory.builder().withLanguage(Language.JAVA).requireClassRegistration(false).build(); + + // 测试特殊值 - 直接序列化和反序列化 + byte[] bytes = fory.serialize(Float16.NaN); + Float16 result = (Float16) fory.deserialize(bytes); + assertTrue(result.isNaN()); + + bytes = fory.serialize(Float16.POSITIVE_INFINITY); + result = (Float16) fory.deserialize(bytes); + assertTrue(result.isInfinite() && !result.signbit()); + + bytes = fory.serialize(Float16.NEGATIVE_INFINITY); + result = (Float16) fory.deserialize(bytes); + assertTrue(result.isInfinite() && result.signbit()); + + bytes = fory.serialize(Float16.ZERO); + result = (Float16) fory.deserialize(bytes); + assertEquals(Float16.ZERO.toBits(), result.toBits()); + + bytes = fory.serialize(Float16.ONE); + result = (Float16) fory.deserialize(bytes); + assertEquals(Float16.ONE.toBits(), result.toBits()); + + // 测试正常值 + bytes = fory.serialize(Float16.valueOf(1.5f)); + result = (Float16) fory.deserialize(bytes); + assertEquals(1.5f, result.floatValue(), 0.01f); + } + + @Test + public void testFloat16ArraySerialization() { + Fory fory = Fory.builder().withLanguage(Language.JAVA).requireClassRegistration(false).build(); + + Float16[] array = + new Float16[] { + Float16.ZERO, + Float16.ONE, + Float16.valueOf(2.5f), + Float16.valueOf(-1.5f), + Float16.NaN, + Float16.POSITIVE_INFINITY, + Float16.NEGATIVE_INFINITY, + Float16.MAX_VALUE, + Float16.MIN_VALUE + }; + + byte[] bytes = fory.serialize(array); + Float16[] result = (Float16[]) fory.deserialize(bytes); + + assertEquals(array.length, result.length); + for (int i = 0; i < array.length; i++) { + if (array[i].isNaN()) { + assertTrue(result[i].isNaN(), "Index " + i + " should be NaN"); + } else { + assertEquals( + array[i].toBits(), + result[i].toBits(), + "Index " + + i + + " bits should match: expected 0x" + + Integer.toHexString(array[i].toBits() & 0xFFFF) + + " but got 0x" + + Integer.toHexString(result[i].toBits() & 0xFFFF)); + } + } + } + + @Test + public void testFloat16EmptyArray() { + Fory fory = Fory.builder().withLanguage(Language.JAVA).requireClassRegistration(false).build(); + + Float16[] empty = new Float16[0]; + byte[] bytes = fory.serialize(empty); + Float16[] result = (Float16[]) fory.deserialize(bytes); + assertEquals(0, result.length); + } + + @Test + public void testFloat16LargeArray() { + Fory fory = Fory.builder().withLanguage(Language.JAVA).requireClassRegistration(false).build(); + + Float16[] large = new Float16[1000]; + for (int i = 0; i < large.length; i++) { + large[i] = Float16.valueOf((float) i); + } + + byte[] bytes = fory.serialize(large); + Float16[] result = (Float16[]) fory.deserialize(bytes); + + assertEquals(large.length, result.length); + for (int i = 0; i < large.length; i++) { + assertEquals( + large[i].floatValue(), result[i].floatValue(), 0.1f, "Index " + i + " should match"); + } + } + + @Data + @AllArgsConstructor + public static class StructWithFloat16 { + Float16 f16Field; + Float16 f16Field2; + } + + @Test + public void testStructWithFloat16Field() { + Fory fory = Fory.builder().withLanguage(Language.JAVA).requireClassRegistration(false).build(); + fory.register(StructWithFloat16.class); + + StructWithFloat16 obj = new StructWithFloat16(Float16.valueOf(1.5f), Float16.valueOf(-2.5f)); + + byte[] bytes = fory.serialize(obj); + StructWithFloat16 result = (StructWithFloat16) fory.deserialize(bytes); + + assertEquals(obj.f16Field.toBits(), result.f16Field.toBits()); + assertEquals(obj.f16Field2.toBits(), result.f16Field2.toBits()); + } + + @Data + @AllArgsConstructor + public static class StructWithFloat16Array { + Float16[] f16Array; + } + + @Test + public void testStructWithFloat16Array() { + Fory fory = Fory.builder().withLanguage(Language.JAVA).requireClassRegistration(false).build(); + fory.register(StructWithFloat16Array.class); + + Float16[] array = new Float16[] {Float16.ONE, Float16.valueOf(2.0f), Float16.valueOf(3.0f)}; + StructWithFloat16Array obj = new StructWithFloat16Array(array); + + byte[] bytes = fory.serialize(obj); + StructWithFloat16Array result = (StructWithFloat16Array) fory.deserialize(bytes); + + assertEquals(obj.f16Array.length, result.f16Array.length); + for (int i = 0; i < obj.f16Array.length; i++) { + assertEquals(obj.f16Array[i].toBits(), result.f16Array[i].toBits()); + } + } + + @Test + public void testFloat16WithNullableField() { + Fory fory = Fory.builder().withLanguage(Language.JAVA).requireClassRegistration(false).build(); + + @Data + @AllArgsConstructor + class StructWithNullableFloat16 { + Float16 nullableField; + } + + fory.register(StructWithNullableFloat16.class); + + // 非空值 + StructWithNullableFloat16 obj1 = new StructWithNullableFloat16(Float16.valueOf(1.5f)); + byte[] bytes = fory.serialize(obj1); + StructWithNullableFloat16 result = (StructWithNullableFloat16) fory.deserialize(bytes); + assertEquals(obj1.nullableField.toBits(), result.nullableField.toBits()); + + // null 值 + StructWithNullableFloat16 obj2 = new StructWithNullableFloat16(null); + bytes = fory.serialize(obj2); + result = (StructWithNullableFloat16) fory.deserialize(bytes); + assertNull(result.nullableField); + } + + @Test + public void testFloat16SpecialValuesInStruct() { + Fory fory = Fory.builder().withLanguage(Language.JAVA).requireClassRegistration(false).build(); + + @Data + @AllArgsConstructor + class StructWithSpecialValues { + Float16 nan; + Float16 posInf; + Float16 negInf; + Float16 zero; + Float16 negZero; + } + + fory.register(StructWithSpecialValues.class); + + StructWithSpecialValues obj = + new StructWithSpecialValues( + Float16.NaN, + Float16.POSITIVE_INFINITY, + Float16.NEGATIVE_INFINITY, + Float16.ZERO, + Float16.NEGATIVE_ZERO); + + byte[] bytes = fory.serialize(obj); + StructWithSpecialValues result = (StructWithSpecialValues) fory.deserialize(bytes); + + assertTrue(result.nan.isNaN()); + assertTrue(result.posInf.isInfinite() && !result.posInf.signbit()); + assertTrue(result.negInf.isInfinite() && result.negInf.signbit()); + assertTrue(result.zero.isZero() && !result.zero.signbit()); + assertTrue(result.negZero.isZero() && result.negZero.signbit()); + } + + @Test + public void testFloat16BitPatternPreservation() { + Fory fory = Fory.builder().withLanguage(Language.JAVA).requireClassRegistration(false).build(); + + // 测试位模式是否精确保留 + short[] testBits = { + (short) 0x0000, // +0 + (short) 0x8000, // -0 + (short) 0x3c00, // 1.0 + (short) 0xbc00, // -1.0 + (short) 0x7bff, // max + (short) 0x0001, // min subnormal + (short) 0x0400, // min normal + (short) 0x7c00, // +Inf + (short) 0xfc00, // -Inf + (short) 0x7e00 // NaN + }; + + for (short bits : testBits) { + Float16 original = Float16.fromBits(bits); + byte[] bytes = fory.serialize(original); + Float16 result = (Float16) fory.deserialize(bytes); + + if (original.isNaN()) { + assertTrue(result.isNaN(), "NaN should remain NaN"); + } else { + assertEquals( + original.toBits(), + result.toBits(), + "Bit pattern should be preserved for 0x" + Integer.toHexString(bits & 0xFFFF)); + } + } + } +} diff --git a/java/fory-core/src/test/java/org/apache/fory/type/Float16Test.java b/java/fory-core/src/test/java/org/apache/fory/type/Float16Test.java new file mode 100644 index 0000000000..d9f324df83 --- /dev/null +++ b/java/fory-core/src/test/java/org/apache/fory/type/Float16Test.java @@ -0,0 +1,316 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fory.type; + +import static org.testng.Assert.*; + +import org.testng.annotations.Test; + +/** 测试 Float16 类的 IEEE 754 binary16 实现。 */ +public class Float16Test { + + @Test + public void testSpecialValues() { + // NaN + assertTrue(Float16.NaN.isNaN()); + assertFalse(Float16.NaN.equalsValue(Float16.NaN)); // NaN != NaN + assertFalse(Float16.NaN.isFinite()); + assertFalse(Float16.NaN.isInfinite()); + + // Infinity + assertTrue(Float16.POSITIVE_INFINITY.isInfinite()); + assertTrue(Float16.NEGATIVE_INFINITY.isInfinite()); + assertFalse(Float16.POSITIVE_INFINITY.signbit()); + assertTrue(Float16.NEGATIVE_INFINITY.signbit()); + assertFalse(Float16.POSITIVE_INFINITY.isFinite()); + + // Zero + assertTrue(Float16.ZERO.isZero()); + assertTrue(Float16.NEGATIVE_ZERO.isZero()); + assertTrue(Float16.ZERO.equalsValue(Float16.NEGATIVE_ZERO)); // +0 == -0 + assertFalse(Float16.ZERO.signbit()); + assertTrue(Float16.NEGATIVE_ZERO.signbit()); + + // One + assertFalse(Float16.ONE.isZero()); + assertTrue(Float16.ONE.isNormal()); + assertEquals(1.0f, Float16.ONE.floatValue(), 0.0f); + } + + @Test + public void testConversionBasic() { + // 零值 + assertEquals((short) 0x0000, Float16.valueOf(0.0f).toBits()); + assertEquals((short) 0x8000, Float16.valueOf(-0.0f).toBits()); + + // 正常值 + assertEquals((short) 0x3c00, Float16.valueOf(1.0f).toBits()); // 1.0 + assertEquals((short) 0xbc00, Float16.valueOf(-1.0f).toBits()); // -1.0 + assertEquals((short) 0x4000, Float16.valueOf(2.0f).toBits()); // 2.0 + + // 往返转换 + assertEquals(1.0f, Float16.valueOf(1.0f).floatValue(), 0.0f); + assertEquals(-1.0f, Float16.valueOf(-1.0f).floatValue(), 0.0f); + assertEquals(2.0f, Float16.valueOf(2.0f).floatValue(), 0.0f); + } + + @Test + public void testBoundaryValues() { + // 最大值 65504 + Float16 max = Float16.valueOf(65504.0f); + assertEquals(0x7bff, max.toBits()); + assertEquals(65504.0f, max.floatValue(), 0.0f); + assertTrue(max.isNormal()); + assertTrue(max.isFinite()); + + // 最小正规数 2^-14 + Float16 minNormal = Float16.valueOf((float) Math.pow(2, -14)); + assertEquals(0x0400, minNormal.toBits()); + assertTrue(minNormal.isNormal()); + assertFalse(minNormal.isSubnormal()); + + // 最小次正规数 2^-24 + Float16 minSubnormal = Float16.valueOf((float) Math.pow(2, -24)); + assertEquals(0x0001, minSubnormal.toBits()); + assertTrue(minSubnormal.isSubnormal()); + assertFalse(minSubnormal.isNormal()); + } + + @Test + public void testOverflowUnderflow() { + // 上溢到 Inf + Float16 overflow = Float16.valueOf(70000.0f); + assertTrue(overflow.isInfinite()); + assertFalse(overflow.signbit()); + + Float16 negOverflow = Float16.valueOf(-70000.0f); + assertTrue(negOverflow.isInfinite()); + assertTrue(negOverflow.signbit()); + + // 下溢到零 + Float16 underflow = Float16.valueOf((float) Math.pow(2, -30)); + assertTrue(underflow.isZero()); + } + + @Test + public void testRoundingToNearestEven() { + // 测试 round-to-nearest-even 舍入模式 + // 1.0009765625 在 float16 中应该舍入到 1.0 + Float16 rounded = Float16.valueOf(1.0009765625f); + // Float16 的精度有限,1.0009765625 会舍入到最接近的可表示值 + // 实际上 float16 的精度约为 0.001,所以这个值会保留一些精度 + assertTrue(Math.abs(rounded.floatValue() - 1.0009765625f) < 0.01f); + + // 测试 ties-to-even:恰好在两个 float16 值中间时,舍入到偶数 + // 这需要精确的测试用例,暂时跳过详细测试 + } + + @Test + public void testArithmetic() { + Float16 one = Float16.ONE; + Float16 two = Float16.valueOf(2.0f); + Float16 three = Float16.valueOf(3.0f); + + // 加法 + assertEquals(3.0f, one.add(two).floatValue(), 0.01f); + assertEquals(5.0f, two.add(three).floatValue(), 0.01f); + + // 减法 + assertEquals(-1.0f, one.subtract(two).floatValue(), 0.01f); + assertEquals(1.0f, three.subtract(two).floatValue(), 0.01f); + + // 乘法 + assertEquals(2.0f, one.multiply(two).floatValue(), 0.01f); + assertEquals(6.0f, two.multiply(three).floatValue(), 0.01f); + + // 除法 + assertEquals(0.5f, one.divide(two).floatValue(), 0.01f); + assertEquals(1.5f, three.divide(two).floatValue(), 0.01f); + + // 取反 + assertEquals(-1.0f, one.negate().floatValue(), 0.0f); + assertEquals(1.0f, one.negate().negate().floatValue(), 0.0f); + + // 绝对值 + assertEquals(1.0f, one.abs().floatValue(), 0.0f); + assertEquals(1.0f, one.negate().abs().floatValue(), 0.0f); + } + + @Test + public void testComparison() { + Float16 one = Float16.ONE; + Float16 two = Float16.valueOf(2.0f); + Float16 nan = Float16.NaN; + + // compareTo + assertTrue(one.compareTo(two) < 0); + assertTrue(two.compareTo(one) > 0); + assertEquals(0, one.compareTo(Float16.ONE)); + + // equalsValue (IEEE 754 语义) + assertTrue(one.equalsValue(Float16.ONE)); + assertFalse(nan.equalsValue(nan)); // NaN != NaN + assertTrue(Float16.ZERO.equalsValue(Float16.NEGATIVE_ZERO)); // +0 == -0 + + // equals (位模式相等) + assertTrue(one.equals(Float16.ONE)); + assertFalse(Float16.ZERO.equals(Float16.NEGATIVE_ZERO)); // 位模式不同 + } + + @Test + public void testClassification() { + // isNormal + assertTrue(Float16.ONE.isNormal()); + assertFalse(Float16.ZERO.isNormal()); + assertFalse(Float16.NaN.isNormal()); + assertFalse(Float16.POSITIVE_INFINITY.isNormal()); + + // isSubnormal + Float16 subnormal = Float16.valueOf((float) Math.pow(2, -24)); + assertTrue(subnormal.isSubnormal()); + assertFalse(Float16.ONE.isSubnormal()); + + // isFinite + assertTrue(Float16.ONE.isFinite()); + assertTrue(Float16.ZERO.isFinite()); + assertFalse(Float16.NaN.isFinite()); + assertFalse(Float16.POSITIVE_INFINITY.isFinite()); + + // signbit + assertFalse(Float16.ONE.signbit()); + assertTrue(Float16.valueOf(-1.0f).signbit()); + assertFalse(Float16.ZERO.signbit()); + assertTrue(Float16.NEGATIVE_ZERO.signbit()); + } + + @Test + public void testToString() { + assertEquals("1.0", Float16.ONE.toString()); + assertEquals("2.0", Float16.valueOf(2.0f).toString()); + assertTrue(Float16.NaN.toString().contains("NaN")); + assertTrue(Float16.POSITIVE_INFINITY.toString().contains("Infinity")); + } + + @Test + public void testHashCode() { + // 相同值应该有相同的 hashCode + assertEquals(Float16.ONE.hashCode(), Float16.valueOf(1.0f).hashCode()); + + // 不同值应该有不同的 hashCode(大概率) + assertNotEquals(Float16.ONE.hashCode(), Float16.valueOf(2.0f).hashCode()); + } + + @Test + public void testNumberConversions() { + Float16 f16 = Float16.valueOf(3.14f); + + // floatValue + assertEquals(3.14f, f16.floatValue(), 0.01f); + + // doubleValue + assertEquals(3.14, f16.doubleValue(), 0.01); + + // intValue + assertEquals(3, f16.intValue()); + + // longValue + assertEquals(3L, f16.longValue()); + + // byteValue + assertEquals((byte) 3, f16.byteValue()); + + // shortValue + assertEquals((short) 3, f16.shortValue()); + } + + @Test + public void testAllBitPatterns() { + // 测试所有 65536 个可能的位模式 + // 验证非 NaN 值可以精确往返转换 + int nanCount = 0; + int normalCount = 0; + int subnormalCount = 0; + int zeroCount = 0; + int infCount = 0; + + for (int bits = 0; bits <= 0xFFFF; bits++) { + Float16 h = Float16.fromBits((short) bits); + float f = h.floatValue(); + Float16 h2 = Float16.valueOf(f); + + if (h.isNaN()) { + nanCount++; + // NaN 应该保持为 NaN + assertTrue(h2.isNaN(), "NaN should remain NaN after round-trip"); + } else { + // 非 NaN 值应该精确往返(或者至少保持相同的值) + if (!h.equals(h2)) { + // 允许一些舍入误差,但值应该接近 + assertEquals( + h.floatValue(), + h2.floatValue(), + 0.0001f, + String.format("Round-trip failed for bits 0x%04x", bits)); + } + + if (h.isNormal()) { + normalCount++; + } else if (h.isSubnormal()) { + subnormalCount++; + } else if (h.isZero()) { + zeroCount++; + } else if (h.isInfinite()) { + infCount++; + } + } + } + + // 验证统计信息合理 + assertTrue(nanCount > 0, "Should have NaN values"); + assertTrue(normalCount > 0, "Should have normal values"); + assertTrue(subnormalCount > 0, "Should have subnormal values"); + assertEquals(2, zeroCount, "Should have exactly 2 zeros (+0 and -0)"); + assertEquals(2, infCount, "Should have exactly 2 infinities (+Inf and -Inf)"); + + // 总数应该是 65536 + int total = nanCount + normalCount + subnormalCount + zeroCount + infCount; + assertEquals(65536, total, "Total should be 65536"); + } + + @Test + public void testConstants() { + // 验证常量的位模式 + assertEquals((short) 0x7e00, Float16.NaN.toBits()); + assertEquals((short) 0x7c00, Float16.POSITIVE_INFINITY.toBits()); + assertEquals((short) 0xfc00, Float16.NEGATIVE_INFINITY.toBits()); + assertEquals((short) 0x0000, Float16.ZERO.toBits()); + assertEquals((short) 0x8000, Float16.NEGATIVE_ZERO.toBits()); + assertEquals((short) 0x3c00, Float16.ONE.toBits()); + assertEquals((short) 0x7bff, Float16.MAX_VALUE.toBits()); + assertEquals((short) 0x0400, Float16.MIN_NORMAL.toBits()); + assertEquals((short) 0x0001, Float16.MIN_VALUE.toBits()); + } + + @Test + public void testSizeConstants() { + assertEquals(16, Float16.SIZE_BITS); + assertEquals(2, Float16.SIZE_BYTES); + } +} From a610788fe9d2314fd85e8ddc780b17af742e4f43 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Tue, 3 Feb 2026 19:08:34 +0800 Subject: [PATCH 02/16] fix: remove --- .../java/org/apache/fory/type/Float16.java | 159 +----------------- .../serializer/Float16SerializerTest.java | 26 ++- .../org/apache/fory/type/Float16Test.java | 65 +------ 3 files changed, 26 insertions(+), 224 deletions(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/type/Float16.java b/java/fory-core/src/main/java/org/apache/fory/type/Float16.java index f051a8d145..0e1b692756 100644 --- a/java/fory-core/src/main/java/org/apache/fory/type/Float16.java +++ b/java/fory-core/src/main/java/org/apache/fory/type/Float16.java @@ -21,121 +21,64 @@ import java.io.Serializable; -/** - * IEEE 754 binary16 半精度浮点数(16位). - * - *

格式:1位符号 + 5位指数 + 10位尾数 - * - *

    - *
  • 指数偏移:15 - *
  • 范围:±65504(最大正规数) - *
  • 最小正规数:2^-14 ≈ 6.104e-5 - *
  • 最小次正规数:2^-24 ≈ 5.96e-8 - *
- * - *

此类是不可变的,线程安全。所有算术运算通过提升到 float32 执行,然后舍入回 float16。 - * - *

转换使用 IEEE 754 round-to-nearest-even 舍入模式。 - */ + public final class Float16 extends Number implements Comparable, Serializable { private static final long serialVersionUID = 1L; - // ========== 位掩码常量 ========== private static final int SIGN_MASK = 0x8000; private static final int EXP_MASK = 0x7C00; private static final int MANT_MASK = 0x03FF; - // ========== 特殊值位模式 ========== - private static final short BITS_NAN = (short) 0x7E00; // 标准静默 NaN + private static final short BITS_NAN = (short) 0x7E00; private static final short BITS_POS_INF = (short) 0x7C00; // +Inf private static final short BITS_NEG_INF = (short) 0xFC00; // -Inf private static final short BITS_NEG_ZERO = (short) 0x8000; // -0 private static final short BITS_MAX = (short) 0x7BFF; // 65504 private static final short BITS_ONE = (short) 0x3C00; // 1.0 - private static final short BITS_MIN_NORMAL = (short) 0x0400; // 2^-14 - private static final short BITS_MIN_VALUE = (short) 0x0001; // 最小次正规数 + private static final short BITS_MIN_NORMAL = (short) 0x0400; + private static final short BITS_MIN_VALUE = (short) 0x0001; - // ========== 公共常量 ========== - /** 非数字(Not-a-Number)值. */ public static final Float16 NaN = new Float16(BITS_NAN); - /** 正无穷大. */ public static final Float16 POSITIVE_INFINITY = new Float16(BITS_POS_INF); - /** 负无穷大. */ public static final Float16 NEGATIVE_INFINITY = new Float16(BITS_NEG_INF); - /** 正零. */ public static final Float16 ZERO = new Float16((short) 0); - /** 负零. */ public static final Float16 NEGATIVE_ZERO = new Float16(BITS_NEG_ZERO); - /** 值为 1.0 的常量. */ public static final Float16 ONE = new Float16(BITS_ONE); - /** 最大有限值 65504. */ public static final Float16 MAX_VALUE = new Float16(BITS_MAX); - /** 最小正规数 2^-14. */ public static final Float16 MIN_NORMAL = new Float16(BITS_MIN_NORMAL); - /** 最小正值(次正规数)2^-24. */ public static final Float16 MIN_VALUE = new Float16(BITS_MIN_VALUE); - /** Float16 的位数. */ public static final int SIZE_BITS = 16; - /** Float16 的字节数. */ public static final int SIZE_BYTES = 2; - // ========== 存储字段 ========== private final short bits; - // ========== 构造方法 ========== private Float16(short bits) { this.bits = bits; } - /** - * 从位模式创建 Float16. - * - * @param bits IEEE 754 binary16 位模式 - * @return Float16 实例 - */ public static Float16 fromBits(short bits) { return new Float16(bits); } - /** - * 从 float 值创建 Float16(带舍入). - * - *

使用 IEEE 754 round-to-nearest-even 舍入模式。超出范围的值会舍入到 ±Infinity。 - * - * @param value float 值 - * @return Float16 实例 - */ public static Float16 valueOf(float value) { return new Float16(floatToFloat16Bits(value)); } - /** - * 获取位模式. - * - * @return IEEE 754 binary16 位模式 - */ public short toBits() { return bits; } - // ========== IEEE 754 转换算法 ========== - - /** - * 将 float32 转换为 float16 位模式。使用 round-to-nearest-even 舍入模式。 - * - *

参考 Go 实现:go/fory/float16/float16.go - */ private static short floatToFloat16Bits(float f32) { int bits32 = Float.floatToRawIntBits(f32); int sign = (bits32 >>> 31) & 0x1; @@ -146,48 +89,38 @@ private static short floatToFloat16Bits(float f32) { int outExp; int outMant; - // 特殊值:NaN 或 Inf if (exp == 0xFF) { outExp = 0x1F; if (mant != 0) { - // NaN:保留尾数高位,确保至少有一位 outMant = 0x200 | ((mant >>> 13) & 0x1FF); if (outMant == 0x200) { - outMant = 0x201; // 确保至少一位非零 + outMant = 0x201; } } else { - // Inf outMant = 0; } } - // 零或次正规 float32 else if (exp == 0) { outExp = 0; outMant = 0; } - // 正规数 else { int newExp = exp - 127 + 15; - // 上溢到 Inf if (newExp >= 31) { outExp = 0x1F; outMant = 0; } - // 下溢到次正规或零 else if (newExp <= 0) { - // 隐式 1 + 尾数 int fullMant = mant | 0x800000; int shift = 1 - newExp; int netShift = 13 + shift; if (netShift >= 24) { - // 太小,变为零 outExp = 0; outMant = 0; } else { outExp = 0; - // 舍入到最近偶数 int roundBit = (fullMant >>> (netShift - 1)) & 1; int sticky = fullMant & ((1 << (netShift - 1)) - 1); outMant = fullMant >>> netShift; @@ -197,23 +130,20 @@ else if (newExp <= 0) { } } } - // 正规范围 else { outExp = newExp; outMant = mant >>> 13; - // 舍入到最近偶数 int roundBit = (mant >>> 12) & 1; int sticky = mant & 0xFFF; if (roundBit == 1 && (sticky != 0 || (outMant & 1) == 1)) { outMant++; if (outMant > 0x3FF) { - // 尾数溢出,增加指数 outMant = 0; outExp++; if (outExp >= 31) { - outExp = 0x1F; // 溢出到 Inf + outExp = 0x1F; } } } @@ -223,11 +153,7 @@ else if (newExp <= 0) { return (short) (outSign | (outExp << 10) | outMant); } - /** - * 将 float16 位模式转换为 float32. - * - *

此转换对所有 float16 值都是精确的。 - */ + private static float float16BitsToFloat(short bits16) { int bits = bits16 & 0xFFFF; int sign = (bits >>> 15) & 0x1; @@ -236,31 +162,22 @@ private static float float16BitsToFloat(short bits16) { int outBits = sign << 31; - // NaN 或 Inf if (exp == 0x1F) { outBits |= 0xFF << 23; if (mant != 0) { - // NaN:提升尾数 outBits |= mant << 13; } } - // 零或次正规 else if (exp == 0) { if (mant == 0) { - // 有符号零 - // outBits 已经正确 } else { - // 次正规:归一化 - // 值 = (-1)^S * 2^-14 * (mant / 1024) - // 需要找到隐式 1 的位置 - int shift = Integer.numberOfLeadingZeros(mant) - 22; // 32 - 10 + int shift = Integer.numberOfLeadingZeros(mant) - 22; mant = (mant << shift) & 0x3FF; int newExp = 1 - 15 - shift + 127; outBits |= newExp << 23; outBits |= mant << 13; } } - // 正规数 else { outBits |= (exp - 15 + 127) << 23; outBits |= mant << 13; @@ -269,106 +186,59 @@ else if (exp == 0) { return Float.intBitsToFloat(outBits); } - // ========== 分类方法 ========== - - /** 判断是否为 NaN. */ public boolean isNaN() { return (bits & EXP_MASK) == EXP_MASK && (bits & MANT_MASK) != 0; } - /** 判断是否为无穷大(正或负). */ public boolean isInfinite() { return (bits & EXP_MASK) == EXP_MASK && (bits & MANT_MASK) == 0; } - /** 判断是否为有限值(非 NaN 且非无穷大). */ public boolean isFinite() { return (bits & EXP_MASK) != EXP_MASK; } - /** 判断是否为零(正零或负零). */ public boolean isZero() { return (bits & (EXP_MASK | MANT_MASK)) == 0; } - /** 判断是否为正规数(非零、非次正规、非无穷大、非 NaN). */ public boolean isNormal() { int exp = bits & EXP_MASK; return exp != 0 && exp != EXP_MASK; } - /** 判断是否为次正规数. */ public boolean isSubnormal() { return (bits & EXP_MASK) == 0 && (bits & MANT_MASK) != 0; } - /** 判断符号位(true 表示负数或负零). */ public boolean signbit() { return (bits & SIGN_MASK) != 0; } - // ========== 算术运算(通过 float32 提升)========== - - /** - * 加法运算. - * - * @param other 另一个 Float16 值 - * @return 相加结果 - */ public Float16 add(Float16 other) { return valueOf(floatValue() + other.floatValue()); } - /** - * 减法运算. - * - * @param other 另一个 Float16 值 - * @return 相减结果 - */ public Float16 subtract(Float16 other) { return valueOf(floatValue() - other.floatValue()); } - /** - * 乘法运算. - * - * @param other 另一个 Float16 值 - * @return 相乘结果 - */ public Float16 multiply(Float16 other) { return valueOf(floatValue() * other.floatValue()); } - /** - * 除法运算. - * - * @param other 另一个 Float16 值 - * @return 相除结果 - */ public Float16 divide(Float16 other) { return valueOf(floatValue() / other.floatValue()); } - /** - * 取反运算. - * - * @return 取反结果 - */ public Float16 negate() { return fromBits((short) (bits ^ SIGN_MASK)); } - /** - * 绝对值运算. - * - * @return 绝对值 - */ public Float16 abs() { return fromBits((short) (bits & ~SIGN_MASK)); } - // ========== Number 实现 ========== - @Override public float floatValue() { return float16BitsToFloat(bits); @@ -399,14 +269,6 @@ public short shortValue() { return (short) floatValue(); } - // ========== 比较方法 ========== - - /** - * 值相等比较(IEEE 754 语义:NaN != NaN,+0 == -0). - * - * @param other 另一个 Float16 值 - * @return 是否数值相等 - */ public boolean equalsValue(Float16 other) { if (isNaN() || other.isNaN()) { return false; @@ -422,11 +284,6 @@ public int compareTo(Float16 other) { return Float.compare(floatValue(), other.floatValue()); } - /** - * 对象相等比较(位模式相等). - * - *

注意:此方法使用位模式相等,与 {@link #equalsValue(Float16)} 不同。 对于 IEEE 754 数值相等,请使用 equalsValue()。 - */ @Override public boolean equals(Object obj) { if (this == obj) { diff --git a/java/fory-core/src/test/java/org/apache/fory/serializer/Float16SerializerTest.java b/java/fory-core/src/test/java/org/apache/fory/serializer/Float16SerializerTest.java index 8b74e241bb..b9ab353e46 100644 --- a/java/fory-core/src/test/java/org/apache/fory/serializer/Float16SerializerTest.java +++ b/java/fory-core/src/test/java/org/apache/fory/serializer/Float16SerializerTest.java @@ -29,14 +29,12 @@ import org.apache.fory.type.Float16; import org.testng.annotations.Test; -/** 测试 Float16 序列化器。 */ public class Float16SerializerTest extends ForyTestBase { @Test public void testFloat16Serialization() { Fory fory = Fory.builder().withLanguage(Language.JAVA).requireClassRegistration(false).build(); - // 测试特殊值 - 直接序列化和反序列化 byte[] bytes = fory.serialize(Float16.NaN); Float16 result = (Float16) fory.deserialize(bytes); assertTrue(result.isNaN()); @@ -57,7 +55,6 @@ public void testFloat16Serialization() { result = (Float16) fory.deserialize(bytes); assertEquals(Float16.ONE.toBits(), result.toBits()); - // 测试正常值 bytes = fory.serialize(Float16.valueOf(1.5f)); result = (Float16) fory.deserialize(bytes); assertEquals(1.5f, result.floatValue(), 0.01f); @@ -186,13 +183,11 @@ class StructWithNullableFloat16 { fory.register(StructWithNullableFloat16.class); - // 非空值 StructWithNullableFloat16 obj1 = new StructWithNullableFloat16(Float16.valueOf(1.5f)); byte[] bytes = fory.serialize(obj1); StructWithNullableFloat16 result = (StructWithNullableFloat16) fory.deserialize(bytes); assertEquals(obj1.nullableField.toBits(), result.nullableField.toBits()); - // null 值 StructWithNullableFloat16 obj2 = new StructWithNullableFloat16(null); bytes = fory.serialize(obj2); result = (StructWithNullableFloat16) fory.deserialize(bytes); @@ -237,18 +232,17 @@ class StructWithSpecialValues { public void testFloat16BitPatternPreservation() { Fory fory = Fory.builder().withLanguage(Language.JAVA).requireClassRegistration(false).build(); - // 测试位模式是否精确保留 short[] testBits = { - (short) 0x0000, // +0 - (short) 0x8000, // -0 - (short) 0x3c00, // 1.0 - (short) 0xbc00, // -1.0 - (short) 0x7bff, // max - (short) 0x0001, // min subnormal - (short) 0x0400, // min normal - (short) 0x7c00, // +Inf - (short) 0xfc00, // -Inf - (short) 0x7e00 // NaN + (short) 0x0000, + (short) 0x8000, + (short) 0x3c00, + (short) 0xbc00, + (short) 0x7bff, + (short) 0x0001, + (short) 0x0400, + (short) 0x7c00, + (short) 0xfc00, + (short) 0x7e00 }; for (short bits : testBits) { diff --git a/java/fory-core/src/test/java/org/apache/fory/type/Float16Test.java b/java/fory-core/src/test/java/org/apache/fory/type/Float16Test.java index d9f324df83..821b2eadff 100644 --- a/java/fory-core/src/test/java/org/apache/fory/type/Float16Test.java +++ b/java/fory-core/src/test/java/org/apache/fory/type/Float16Test.java @@ -23,32 +23,27 @@ import org.testng.annotations.Test; -/** 测试 Float16 类的 IEEE 754 binary16 实现。 */ public class Float16Test { @Test public void testSpecialValues() { - // NaN assertTrue(Float16.NaN.isNaN()); - assertFalse(Float16.NaN.equalsValue(Float16.NaN)); // NaN != NaN + assertFalse(Float16.NaN.equalsValue(Float16.NaN)); assertFalse(Float16.NaN.isFinite()); assertFalse(Float16.NaN.isInfinite()); - // Infinity assertTrue(Float16.POSITIVE_INFINITY.isInfinite()); assertTrue(Float16.NEGATIVE_INFINITY.isInfinite()); assertFalse(Float16.POSITIVE_INFINITY.signbit()); assertTrue(Float16.NEGATIVE_INFINITY.signbit()); assertFalse(Float16.POSITIVE_INFINITY.isFinite()); - // Zero assertTrue(Float16.ZERO.isZero()); assertTrue(Float16.NEGATIVE_ZERO.isZero()); - assertTrue(Float16.ZERO.equalsValue(Float16.NEGATIVE_ZERO)); // +0 == -0 + assertTrue(Float16.ZERO.equalsValue(Float16.NEGATIVE_ZERO)); assertFalse(Float16.ZERO.signbit()); assertTrue(Float16.NEGATIVE_ZERO.signbit()); - // One assertFalse(Float16.ONE.isZero()); assertTrue(Float16.ONE.isNormal()); assertEquals(1.0f, Float16.ONE.floatValue(), 0.0f); @@ -56,16 +51,13 @@ public void testSpecialValues() { @Test public void testConversionBasic() { - // 零值 assertEquals((short) 0x0000, Float16.valueOf(0.0f).toBits()); assertEquals((short) 0x8000, Float16.valueOf(-0.0f).toBits()); - // 正常值 - assertEquals((short) 0x3c00, Float16.valueOf(1.0f).toBits()); // 1.0 - assertEquals((short) 0xbc00, Float16.valueOf(-1.0f).toBits()); // -1.0 - assertEquals((short) 0x4000, Float16.valueOf(2.0f).toBits()); // 2.0 + assertEquals((short) 0x3c00, Float16.valueOf(1.0f).toBits()); + assertEquals((short) 0xbc00, Float16.valueOf(-1.0f).toBits()); + assertEquals((short) 0x4000, Float16.valueOf(2.0f).toBits()); - // 往返转换 assertEquals(1.0f, Float16.valueOf(1.0f).floatValue(), 0.0f); assertEquals(-1.0f, Float16.valueOf(-1.0f).floatValue(), 0.0f); assertEquals(2.0f, Float16.valueOf(2.0f).floatValue(), 0.0f); @@ -73,20 +65,17 @@ public void testConversionBasic() { @Test public void testBoundaryValues() { - // 最大值 65504 Float16 max = Float16.valueOf(65504.0f); assertEquals(0x7bff, max.toBits()); assertEquals(65504.0f, max.floatValue(), 0.0f); assertTrue(max.isNormal()); assertTrue(max.isFinite()); - // 最小正规数 2^-14 Float16 minNormal = Float16.valueOf((float) Math.pow(2, -14)); assertEquals(0x0400, minNormal.toBits()); assertTrue(minNormal.isNormal()); assertFalse(minNormal.isSubnormal()); - // 最小次正规数 2^-24 Float16 minSubnormal = Float16.valueOf((float) Math.pow(2, -24)); assertEquals(0x0001, minSubnormal.toBits()); assertTrue(minSubnormal.isSubnormal()); @@ -95,7 +84,6 @@ public void testBoundaryValues() { @Test public void testOverflowUnderflow() { - // 上溢到 Inf Float16 overflow = Float16.valueOf(70000.0f); assertTrue(overflow.isInfinite()); assertFalse(overflow.signbit()); @@ -104,22 +92,14 @@ public void testOverflowUnderflow() { assertTrue(negOverflow.isInfinite()); assertTrue(negOverflow.signbit()); - // 下溢到零 Float16 underflow = Float16.valueOf((float) Math.pow(2, -30)); assertTrue(underflow.isZero()); } @Test public void testRoundingToNearestEven() { - // 测试 round-to-nearest-even 舍入模式 - // 1.0009765625 在 float16 中应该舍入到 1.0 Float16 rounded = Float16.valueOf(1.0009765625f); - // Float16 的精度有限,1.0009765625 会舍入到最接近的可表示值 - // 实际上 float16 的精度约为 0.001,所以这个值会保留一些精度 assertTrue(Math.abs(rounded.floatValue() - 1.0009765625f) < 0.01f); - - // 测试 ties-to-even:恰好在两个 float16 值中间时,舍入到偶数 - // 这需要精确的测试用例,暂时跳过详细测试 } @Test @@ -128,27 +108,21 @@ public void testArithmetic() { Float16 two = Float16.valueOf(2.0f); Float16 three = Float16.valueOf(3.0f); - // 加法 assertEquals(3.0f, one.add(two).floatValue(), 0.01f); assertEquals(5.0f, two.add(three).floatValue(), 0.01f); - // 减法 assertEquals(-1.0f, one.subtract(two).floatValue(), 0.01f); assertEquals(1.0f, three.subtract(two).floatValue(), 0.01f); - // 乘法 assertEquals(2.0f, one.multiply(two).floatValue(), 0.01f); assertEquals(6.0f, two.multiply(three).floatValue(), 0.01f); - // 除法 assertEquals(0.5f, one.divide(two).floatValue(), 0.01f); assertEquals(1.5f, three.divide(two).floatValue(), 0.01f); - // 取反 assertEquals(-1.0f, one.negate().floatValue(), 0.0f); assertEquals(1.0f, one.negate().negate().floatValue(), 0.0f); - // 绝对值 assertEquals(1.0f, one.abs().floatValue(), 0.0f); assertEquals(1.0f, one.negate().abs().floatValue(), 0.0f); } @@ -159,41 +133,34 @@ public void testComparison() { Float16 two = Float16.valueOf(2.0f); Float16 nan = Float16.NaN; - // compareTo assertTrue(one.compareTo(two) < 0); assertTrue(two.compareTo(one) > 0); assertEquals(0, one.compareTo(Float16.ONE)); - // equalsValue (IEEE 754 语义) assertTrue(one.equalsValue(Float16.ONE)); - assertFalse(nan.equalsValue(nan)); // NaN != NaN - assertTrue(Float16.ZERO.equalsValue(Float16.NEGATIVE_ZERO)); // +0 == -0 + assertFalse(nan.equalsValue(nan)); + assertTrue(Float16.ZERO.equalsValue(Float16.NEGATIVE_ZERO)); - // equals (位模式相等) assertTrue(one.equals(Float16.ONE)); - assertFalse(Float16.ZERO.equals(Float16.NEGATIVE_ZERO)); // 位模式不同 + assertFalse(Float16.ZERO.equals(Float16.NEGATIVE_ZERO)); } @Test public void testClassification() { - // isNormal assertTrue(Float16.ONE.isNormal()); assertFalse(Float16.ZERO.isNormal()); assertFalse(Float16.NaN.isNormal()); assertFalse(Float16.POSITIVE_INFINITY.isNormal()); - // isSubnormal Float16 subnormal = Float16.valueOf((float) Math.pow(2, -24)); assertTrue(subnormal.isSubnormal()); assertFalse(Float16.ONE.isSubnormal()); - // isFinite assertTrue(Float16.ONE.isFinite()); assertTrue(Float16.ZERO.isFinite()); assertFalse(Float16.NaN.isFinite()); assertFalse(Float16.POSITIVE_INFINITY.isFinite()); - // signbit assertFalse(Float16.ONE.signbit()); assertTrue(Float16.valueOf(-1.0f).signbit()); assertFalse(Float16.ZERO.signbit()); @@ -210,10 +177,8 @@ public void testToString() { @Test public void testHashCode() { - // 相同值应该有相同的 hashCode assertEquals(Float16.ONE.hashCode(), Float16.valueOf(1.0f).hashCode()); - // 不同值应该有不同的 hashCode(大概率) assertNotEquals(Float16.ONE.hashCode(), Float16.valueOf(2.0f).hashCode()); } @@ -221,29 +186,21 @@ public void testHashCode() { public void testNumberConversions() { Float16 f16 = Float16.valueOf(3.14f); - // floatValue assertEquals(3.14f, f16.floatValue(), 0.01f); - // doubleValue assertEquals(3.14, f16.doubleValue(), 0.01); - // intValue assertEquals(3, f16.intValue()); - // longValue assertEquals(3L, f16.longValue()); - // byteValue assertEquals((byte) 3, f16.byteValue()); - // shortValue assertEquals((short) 3, f16.shortValue()); } @Test public void testAllBitPatterns() { - // 测试所有 65536 个可能的位模式 - // 验证非 NaN 值可以精确往返转换 int nanCount = 0; int normalCount = 0; int subnormalCount = 0; @@ -257,12 +214,9 @@ public void testAllBitPatterns() { if (h.isNaN()) { nanCount++; - // NaN 应该保持为 NaN assertTrue(h2.isNaN(), "NaN should remain NaN after round-trip"); } else { - // 非 NaN 值应该精确往返(或者至少保持相同的值) if (!h.equals(h2)) { - // 允许一些舍入误差,但值应该接近 assertEquals( h.floatValue(), h2.floatValue(), @@ -282,21 +236,18 @@ public void testAllBitPatterns() { } } - // 验证统计信息合理 assertTrue(nanCount > 0, "Should have NaN values"); assertTrue(normalCount > 0, "Should have normal values"); assertTrue(subnormalCount > 0, "Should have subnormal values"); assertEquals(2, zeroCount, "Should have exactly 2 zeros (+0 and -0)"); assertEquals(2, infCount, "Should have exactly 2 infinities (+Inf and -Inf)"); - // 总数应该是 65536 int total = nanCount + normalCount + subnormalCount + zeroCount + infCount; assertEquals(65536, total, "Total should be 65536"); } @Test public void testConstants() { - // 验证常量的位模式 assertEquals((short) 0x7e00, Float16.NaN.toBits()); assertEquals((short) 0x7c00, Float16.POSITIVE_INFINITY.toBits()); assertEquals((short) 0xfc00, Float16.NEGATIVE_INFINITY.toBits()); From 7fc45b88c0ff00a09ccf5fc33b0a5bbb5e8f1ede Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Tue, 3 Feb 2026 19:16:24 +0800 Subject: [PATCH 03/16] fix: add float16 --- .../java/org/apache/fory/type/Float16.java | 42 ++++++++++++++++++- .../org/apache/fory/type/Float16Test.java | 37 ++++++++++++++++ 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/type/Float16.java b/java/fory-core/src/main/java/org/apache/fory/type/Float16.java index 0e1b692756..ce24f502dd 100644 --- a/java/fory-core/src/main/java/org/apache/fory/type/Float16.java +++ b/java/fory-core/src/main/java/org/apache/fory/type/Float16.java @@ -79,6 +79,10 @@ public short toBits() { return bits; } + public float toFloat() { + return floatValue(); + } + private static short floatToFloat16Bits(float f32) { int bits32 = Float.floatToRawIntBits(f32); int sign = (bits32 >>> 31) & 0x1; @@ -279,9 +283,45 @@ public boolean equalsValue(Float16 other) { return bits == other.bits; } + public boolean lessThan(Float16 other) { + if (isNaN() || other.isNaN()) { + return false; + } + return floatValue() < other.floatValue(); + } + + public boolean lessThanOrEqual(Float16 other) { + if (isNaN() || other.isNaN()) { + return false; + } + return floatValue() <= other.floatValue(); + } + + public boolean greaterThan(Float16 other) { + if (isNaN() || other.isNaN()) { + return false; + } + return floatValue() > other.floatValue(); + } + + public boolean greaterThanOrEqual(Float16 other) { + if (isNaN() || other.isNaN()) { + return false; + } + return floatValue() >= other.floatValue(); + } + + public static int compare(Float16 a, Float16 b) { + return Float.compare(a.floatValue(), b.floatValue()); + } + + public static Float16 parse(String s) { + return valueOf(Float.parseFloat(s)); + } + @Override public int compareTo(Float16 other) { - return Float.compare(floatValue(), other.floatValue()); + return compare(this, other); } @Override diff --git a/java/fory-core/src/test/java/org/apache/fory/type/Float16Test.java b/java/fory-core/src/test/java/org/apache/fory/type/Float16Test.java index 821b2eadff..a3364bfe6d 100644 --- a/java/fory-core/src/test/java/org/apache/fory/type/Float16Test.java +++ b/java/fory-core/src/test/java/org/apache/fory/type/Float16Test.java @@ -143,6 +143,43 @@ public void testComparison() { assertTrue(one.equals(Float16.ONE)); assertFalse(Float16.ZERO.equals(Float16.NEGATIVE_ZERO)); + + assertTrue(one.lessThan(two)); + assertFalse(two.lessThan(one)); + assertFalse(nan.lessThan(one)); + assertFalse(one.lessThan(nan)); + + assertTrue(one.lessThanOrEqual(two)); + assertTrue(one.lessThanOrEqual(Float16.ONE)); + assertFalse(nan.lessThanOrEqual(one)); + + assertTrue(two.greaterThan(one)); + assertFalse(one.greaterThan(two)); + assertFalse(nan.greaterThan(one)); + + assertTrue(two.greaterThanOrEqual(one)); + assertTrue(one.greaterThanOrEqual(Float16.ONE)); + assertFalse(nan.greaterThanOrEqual(one)); + + assertTrue(Float16.compare(one, two) < 0); + assertTrue(Float16.compare(two, one) > 0); + assertEquals(0, Float16.compare(one, Float16.ONE)); + } + + @Test + public void testParse() { + assertEquals(1.0f, Float16.parse("1.0").floatValue(), 0.0f); + assertEquals(2.5f, Float16.parse("2.5").floatValue(), 0.01f); + assertEquals(-3.14f, Float16.parse("-3.14").floatValue(), 0.01f); + assertTrue(Float16.parse("NaN").isNaN()); + assertTrue(Float16.parse("Infinity").isInfinite()); + assertTrue(Float16.parse("-Infinity").isInfinite()); + } + + @Test + public void testToFloat() { + Float16 f16 = Float16.valueOf(3.14f); + assertEquals(f16.floatValue(), f16.toFloat(), 0.0f); } @Test From 65f6ea33ea3ad987b9e8170fe70e508c9531ae9e Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Tue, 3 Feb 2026 19:39:30 +0800 Subject: [PATCH 04/16] fix: add float16serializer --- .../java/org/apache/fory/graalvm/Main.java | 1 + .../fory/serializer/Float16Serializer.java | 51 +++++++++++++++++++ .../apache/fory/serializer/Serializers.java | 1 + .../java/org/apache/fory/type/Float16.java | 3 +- .../fory-core/reflection-config.json | 10 ++++ .../fory-core/serialization-config.json | 3 +- 6 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 java/fory-core/src/main/java/org/apache/fory/serializer/Float16Serializer.java diff --git a/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/Main.java b/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/Main.java index 52631e4ede..b3b09ee771 100644 --- a/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/Main.java +++ b/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/Main.java @@ -43,5 +43,6 @@ public static void main(String[] args) throws Throwable { ArrayExample.main(args); AbstractClassExample.main(args); FeatureTestExample.main(args); + Float16Example.main(args); } } diff --git a/java/fory-core/src/main/java/org/apache/fory/serializer/Float16Serializer.java b/java/fory-core/src/main/java/org/apache/fory/serializer/Float16Serializer.java new file mode 100644 index 0000000000..cf34806ad6 --- /dev/null +++ b/java/fory-core/src/main/java/org/apache/fory/serializer/Float16Serializer.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fory.serializer; + +import org.apache.fory.Fory; +import org.apache.fory.memory.MemoryBuffer; +import org.apache.fory.type.Float16; + +public final class Float16Serializer extends ImmutableSerializer { + + public Float16Serializer(Fory fory) { + super(fory, Float16.class); + } + + @Override + public void write(MemoryBuffer buffer, Float16 value) { + buffer.writeInt16(value.toBits()); + } + + @Override + public Float16 read(MemoryBuffer buffer) { + return Float16.fromBits(buffer.readInt16()); + } + + @Override + public void xwrite(MemoryBuffer buffer, Float16 value) { + buffer.writeInt16(value.toBits()); + } + + @Override + public Float16 xread(MemoryBuffer buffer) { + return Float16.fromBits(buffer.readInt16()); + } +} diff --git a/java/fory-core/src/main/java/org/apache/fory/serializer/Serializers.java b/java/fory-core/src/main/java/org/apache/fory/serializer/Serializers.java index 48654a80df..20654d7eeb 100644 --- a/java/fory-core/src/main/java/org/apache/fory/serializer/Serializers.java +++ b/java/fory-core/src/main/java/org/apache/fory/serializer/Serializers.java @@ -595,6 +595,7 @@ public static void registerDefaultSerializers(Fory fory) { resolver.registerInternalSerializer(URI.class, new URISerializer(fory)); resolver.registerInternalSerializer(Pattern.class, new RegexSerializer(fory)); resolver.registerInternalSerializer(UUID.class, new UUIDSerializer(fory)); + resolver.registerInternalSerializer(org.apache.fory.type.Float16.class, new Float16Serializer(fory)); resolver.registerInternalSerializer(Object.class, new EmptyObjectSerializer(fory)); } } diff --git a/java/fory-core/src/main/java/org/apache/fory/type/Float16.java b/java/fory-core/src/main/java/org/apache/fory/type/Float16.java index ce24f502dd..14c415802b 100644 --- a/java/fory-core/src/main/java/org/apache/fory/type/Float16.java +++ b/java/fory-core/src/main/java/org/apache/fory/type/Float16.java @@ -173,8 +173,7 @@ private static float float16BitsToFloat(short bits16) { } } else if (exp == 0) { - if (mant == 0) { - } else { + if (mant != 0) { int shift = Integer.numberOfLeadingZeros(mant) - 22; mant = (mant << shift) & 0x3FF; int newExp = 1 - 15 - shift + 127; diff --git a/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/reflection-config.json b/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/reflection-config.json index 505934bf94..7c6582e2c5 100644 --- a/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/reflection-config.json +++ b/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/reflection-config.json @@ -5,5 +5,15 @@ "allPublicConstructors": true, "allDeclaredMethods": true, "allPublicMethods": true + }, + { + "name": "org.apache.fory.type.Float16", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredFields": true, + "allPublicFields": true } ] + diff --git a/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/serialization-config.json b/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/serialization-config.json index 6eb32cd7d1..f0d47325bd 100644 --- a/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/serialization-config.json +++ b/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/serialization-config.json @@ -16,5 +16,6 @@ { "name": "java.lang.NullPointerException" }, { "name": "java.lang.IndexOutOfBoundsException" }, { "name": "java.lang.ArrayIndexOutOfBoundsException" }, - { "name": "java.io.IOException" } + { "name": "java.io.IOException" }, + { "name": "org.apache.fory.type.Float16" } ] From 6e37abb05129f1b9ba67db672f641cb78a0dd358 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Tue, 3 Feb 2026 19:58:19 +0800 Subject: [PATCH 05/16] fix: add float16list --- .../java/org/apache/fory/graalvm/Main.java | 1 - .../apache/fory/collection/Float16List.java | 161 ++++++++++++++++++ 2 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 java/fory-core/src/main/java/org/apache/fory/collection/Float16List.java diff --git a/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/Main.java b/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/Main.java index b3b09ee771..52631e4ede 100644 --- a/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/Main.java +++ b/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/Main.java @@ -43,6 +43,5 @@ public static void main(String[] args) throws Throwable { ArrayExample.main(args); AbstractClassExample.main(args); FeatureTestExample.main(args); - Float16Example.main(args); } } diff --git a/java/fory-core/src/main/java/org/apache/fory/collection/Float16List.java b/java/fory-core/src/main/java/org/apache/fory/collection/Float16List.java new file mode 100644 index 0000000000..18fb2ea4e0 --- /dev/null +++ b/java/fory-core/src/main/java/org/apache/fory/collection/Float16List.java @@ -0,0 +1,161 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fory.collection; + +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Objects; +import java.util.RandomAccess; +import org.apache.fory.type.Float16; + + +public final class Float16List extends AbstractList implements RandomAccess { + private static final int DEFAULT_CAPACITY = 10; + + private short[] array; + private int size; + + public Float16List() { + this(DEFAULT_CAPACITY); + } + + public Float16List(int initialCapacity) { + if (initialCapacity < 0) { + throw new IllegalArgumentException("Illegal capacity: " + initialCapacity); + } + this.array = new short[initialCapacity]; + this.size = 0; + } + + public Float16List(short[] array) { + this.array = array; + this.size = array.length; + } + + @Override + public Float16 get(int index) { + checkIndex(index); + return Float16.fromBits(array[index]); + } + + @Override + public int size() { + return size; + } + + @Override + public Float16 set(int index, Float16 element) { + checkIndex(index); + Objects.requireNonNull(element, "element"); + short prev = array[index]; + array[index] = element.toBits(); + return Float16.fromBits(prev); + } + + public void set(int index, short bits) { + checkIndex(index); + array[index] = bits; + } + + public void set(int index, float value) { + checkIndex(index); + array[index] = Float16.valueOf(value).toBits(); + } + + @Override + public void add(int index, Float16 element) { + checkPositionIndex(index); + ensureCapacity(size + 1); + System.arraycopy(array, index, array, index + 1, size - index); + array[index] = element.toBits(); + size++; + modCount++; + } + + @Override + public boolean add(Float16 element) { + Objects.requireNonNull(element, "element"); + ensureCapacity(size + 1); + array[size++] = element.toBits(); + modCount++; + return true; + } + + public boolean add(short bits) { + ensureCapacity(size + 1); + array[size++] = bits; + modCount++; + return true; + } + + public boolean add(float value) { + ensureCapacity(size + 1); + array[size++] = Float16.valueOf(value).toBits(); + modCount++; + return true; + } + + public float getFloat(int index) { + checkIndex(index); + return Float16.fromBits(array[index]).toFloat(); + } + + public short getShort(int index) { + checkIndex(index); + return array[index]; + } + + + public boolean hasArray() { + return array != null; + } + + + public short[] getArray() { + return array; + } + + public short[] copyArray() { + return Arrays.copyOf(array, size); + } + + private void ensureCapacity(int minCapacity) { + if (array.length >= minCapacity) { + return; + } + int newCapacity = array.length + (array.length >> 1) + 1; + if (newCapacity < minCapacity) { + newCapacity = minCapacity; + } + array = Arrays.copyOf(array, newCapacity); + } + + private void checkIndex(int index) { + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); + } + } + + private void checkPositionIndex(int index) { + if (index < 0 || index > size) { + throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); + } + } +} From f5ba942dd056548cc8d2b798a72124e01bb6a30f Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Tue, 3 Feb 2026 20:08:11 +0800 Subject: [PATCH 06/16] fix: add native --- .../collection/PrimitiveListSerializers.java | 39 +++++++++++++++++++ .../fory-core/native-image.properties | 3 ++ 2 files changed, 42 insertions(+) diff --git a/java/fory-core/src/main/java/org/apache/fory/serializer/collection/PrimitiveListSerializers.java b/java/fory-core/src/main/java/org/apache/fory/serializer/collection/PrimitiveListSerializers.java index d86556bb1f..fff9e28d7c 100644 --- a/java/fory-core/src/main/java/org/apache/fory/serializer/collection/PrimitiveListSerializers.java +++ b/java/fory-core/src/main/java/org/apache/fory/serializer/collection/PrimitiveListSerializers.java @@ -539,6 +539,43 @@ public Float64List read(MemoryBuffer buffer) { } } + public static final class Float16ListSerializer + extends Serializers.CrossLanguageCompatibleSerializer { + public Float16ListSerializer(Fory fory) { + super(fory, org.apache.fory.collection.Float16List.class, false, true); + } + + @Override + public void write(MemoryBuffer buffer, org.apache.fory.collection.Float16List value) { + int size = value.size(); + int byteSize = size * 2; + buffer.writeVarUint32Small7(byteSize); + short[] array = value.getArray(); + if (Platform.IS_LITTLE_ENDIAN) { + buffer.writePrimitiveArray(array, Platform.SHORT_ARRAY_OFFSET, byteSize); + } else { + for (int i = 0; i < size; i++) { + buffer.writeInt16(array[i]); + } + } + } + + @Override + public org.apache.fory.collection.Float16List read(MemoryBuffer buffer) { + int byteSize = buffer.readVarUint32Small7(); + int size = byteSize / 2; + short[] array = new short[size]; + if (Platform.IS_LITTLE_ENDIAN) { + buffer.readToUnsafe(array, Platform.SHORT_ARRAY_OFFSET, byteSize); + } else { + for (int i = 0; i < size; i++) { + array[i] = buffer.readInt16(); + } + } + return new org.apache.fory.collection.Float16List(array); + } + } + public static void registerDefaultSerializers(Fory fory) { // Note: Classes are already registered in ClassResolver.initialize() // We only need to register serializers here @@ -554,5 +591,7 @@ public static void registerDefaultSerializers(Fory fory) { resolver.registerInternalSerializer(Uint64List.class, new Uint64ListSerializer(fory)); resolver.registerInternalSerializer(Float32List.class, new Float32ListSerializer(fory)); resolver.registerInternalSerializer(Float64List.class, new Float64ListSerializer(fory)); + resolver.registerInternalSerializer( + org.apache.fory.collection.Float16List.class, new Float16ListSerializer(fory)); } } diff --git a/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties b/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties index cf6e873456..e80835ecc5 100644 --- a/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties +++ b/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties @@ -207,6 +207,7 @@ Args=--initialize-at-build-time=org.apache.fory.memory.MemoryBuffer,\ org.apache.fory.codegen.Expression$Variable,\ org.apache.fory.codegen.JaninoUtils,\ org.apache.fory.collection.ClassValueCache,\ + org.apache.fory.collection.Float16List,\ org.apache.fory.collection.ForyObjectMap,\ org.apache.fory.collection.IdentityMap,\ org.apache.fory.collection.IdentityObjectIntMap,\ @@ -311,6 +312,7 @@ Args=--initialize-at-build-time=org.apache.fory.memory.MemoryBuffer,\ org.apache.fory.serializer.ArraySerializers$ObjectArraySerializer,\ org.apache.fory.serializer.ArraySerializers$ShortArraySerializer,\ org.apache.fory.serializer.ArraySerializers$StringArraySerializer,\ + org.apache.fory.serializer.ArraySerializers$Float16ArraySerializer,\ org.apache.fory.serializer.ArraySerializers,\ org.apache.fory.serializer.UnsignedSerializers,\ org.apache.fory.serializer.UnsignedSerializers$Uint8Serializer,\ @@ -329,6 +331,7 @@ Args=--initialize-at-build-time=org.apache.fory.memory.MemoryBuffer,\ org.apache.fory.serializer.collection.PrimitiveListSerializers$Uint64ListSerializer,\ org.apache.fory.serializer.collection.PrimitiveListSerializers$Float32ListSerializer,\ org.apache.fory.serializer.collection.PrimitiveListSerializers$Float64ListSerializer,\ + org.apache.fory.serializer.collection.PrimitiveListSerializers$Float16ListSerializer,\ org.apache.fory.serializer.BufferSerializers$ByteBufferSerializer,\ org.apache.fory.serializer.MetaSharedSerializer,\ org.apache.fory.serializer.MetaSharedLayerSerializer,\ From ad1858681ab087775b3991f8351bf72b244b1480 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Tue, 3 Feb 2026 20:19:51 +0800 Subject: [PATCH 07/16] Update native-image.properties --- .../org.apache.fory/fory-core/native-image.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties b/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties index e80835ecc5..c23ffc9187 100644 --- a/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties +++ b/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties @@ -517,6 +517,7 @@ Args=--initialize-at-build-time=org.apache.fory.memory.MemoryBuffer,\ org.apache.fory.type.Descriptor$1,\ org.apache.fory.type.Descriptor,\ org.apache.fory.type.DescriptorGrouper,\ + org.apache.fory.type.Float16,\ org.apache.fory.type.GenericType,\ org.apache.fory.type.Generics,\ org.apache.fory.type.Type,\ From 5cfb49f8e2d29b39f28ed5ff00bb185c306356d3 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Tue, 3 Feb 2026 20:26:18 +0800 Subject: [PATCH 08/16] fix: add --- .../org.apache.fory/fory-core/reflection-config.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/reflection-config.json b/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/reflection-config.json index 7c6582e2c5..e1e6985175 100644 --- a/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/reflection-config.json +++ b/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/reflection-config.json @@ -14,6 +14,15 @@ "allPublicMethods": true, "allDeclaredFields": true, "allPublicFields": true + }, + { + "name": "org.apache.fory.collection.Float16List", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredFields": true, + "allPublicFields": true } ] From 2772ff2e42fc63162c0965533ab02e95413dab04 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Tue, 3 Feb 2026 20:35:20 +0800 Subject: [PATCH 09/16] Update native-image.properties --- .../org.apache.fory/fory-core/native-image.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties b/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties index c23ffc9187..474daf3472 100644 --- a/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties +++ b/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties @@ -19,6 +19,7 @@ # The unsafe offset get on build time may be different from runtime Args=--initialize-at-build-time=org.apache.fory.memory.MemoryBuffer,\ org.apache.fory.serializer.struct.Fingerprint,\ + org.apache.fory.serializer.Float16Serializer,\ com.google.common.base.Equivalence$Equals,\ com.google.common.base.Equivalence$Identity,\ com.google.common.base.Equivalence,\ From f6f38d30e826512d63787584d31b110a087847cf Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Tue, 3 Feb 2026 20:41:46 +0800 Subject: [PATCH 10/16] Update native-image.properties --- .../org.apache.fory/fory-core/native-image.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties b/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties index 474daf3472..8a7ce35688 100644 --- a/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties +++ b/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties @@ -20,6 +20,7 @@ Args=--initialize-at-build-time=org.apache.fory.memory.MemoryBuffer,\ org.apache.fory.serializer.struct.Fingerprint,\ org.apache.fory.serializer.Float16Serializer,\ + org.apache.fory.serializer.PrimitiveSerializers$Float16Serializer,\ com.google.common.base.Equivalence$Equals,\ com.google.common.base.Equivalence$Identity,\ com.google.common.base.Equivalence,\ From 578c05e3facc591f59df990112cb36d03ac7a6d6 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Tue, 3 Feb 2026 20:49:07 +0800 Subject: [PATCH 11/16] fix: ci --- .../apache/fory/collection/Float16List.java | 3 --- .../apache/fory/serializer/Serializers.java | 3 ++- .../collection/PrimitiveListSerializers.java | 3 ++- .../java/org/apache/fory/type/Float16.java | 21 ++++++------------- 4 files changed, 10 insertions(+), 20 deletions(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/collection/Float16List.java b/java/fory-core/src/main/java/org/apache/fory/collection/Float16List.java index 18fb2ea4e0..bf9c75a24f 100644 --- a/java/fory-core/src/main/java/org/apache/fory/collection/Float16List.java +++ b/java/fory-core/src/main/java/org/apache/fory/collection/Float16List.java @@ -25,7 +25,6 @@ import java.util.RandomAccess; import org.apache.fory.type.Float16; - public final class Float16List extends AbstractList implements RandomAccess { private static final int DEFAULT_CAPACITY = 10; @@ -122,12 +121,10 @@ public short getShort(int index) { return array[index]; } - public boolean hasArray() { return array != null; } - public short[] getArray() { return array; } diff --git a/java/fory-core/src/main/java/org/apache/fory/serializer/Serializers.java b/java/fory-core/src/main/java/org/apache/fory/serializer/Serializers.java index 20654d7eeb..aec627ebac 100644 --- a/java/fory-core/src/main/java/org/apache/fory/serializer/Serializers.java +++ b/java/fory-core/src/main/java/org/apache/fory/serializer/Serializers.java @@ -595,7 +595,8 @@ public static void registerDefaultSerializers(Fory fory) { resolver.registerInternalSerializer(URI.class, new URISerializer(fory)); resolver.registerInternalSerializer(Pattern.class, new RegexSerializer(fory)); resolver.registerInternalSerializer(UUID.class, new UUIDSerializer(fory)); - resolver.registerInternalSerializer(org.apache.fory.type.Float16.class, new Float16Serializer(fory)); + resolver.registerInternalSerializer( + org.apache.fory.type.Float16.class, new Float16Serializer(fory)); resolver.registerInternalSerializer(Object.class, new EmptyObjectSerializer(fory)); } } diff --git a/java/fory-core/src/main/java/org/apache/fory/serializer/collection/PrimitiveListSerializers.java b/java/fory-core/src/main/java/org/apache/fory/serializer/collection/PrimitiveListSerializers.java index fff9e28d7c..61c5567f3f 100644 --- a/java/fory-core/src/main/java/org/apache/fory/serializer/collection/PrimitiveListSerializers.java +++ b/java/fory-core/src/main/java/org/apache/fory/serializer/collection/PrimitiveListSerializers.java @@ -540,7 +540,8 @@ public Float64List read(MemoryBuffer buffer) { } public static final class Float16ListSerializer - extends Serializers.CrossLanguageCompatibleSerializer { + extends Serializers.CrossLanguageCompatibleSerializer< + org.apache.fory.collection.Float16List> { public Float16ListSerializer(Fory fory) { super(fory, org.apache.fory.collection.Float16List.class, false, true); } diff --git a/java/fory-core/src/main/java/org/apache/fory/type/Float16.java b/java/fory-core/src/main/java/org/apache/fory/type/Float16.java index 14c415802b..d429651a2b 100644 --- a/java/fory-core/src/main/java/org/apache/fory/type/Float16.java +++ b/java/fory-core/src/main/java/org/apache/fory/type/Float16.java @@ -21,7 +21,6 @@ import java.io.Serializable; - public final class Float16 extends Number implements Comparable, Serializable { private static final long serialVersionUID = 1L; @@ -62,7 +61,6 @@ public final class Float16 extends Number implements Comparable, Serial private final short bits; - private Float16(short bits) { this.bits = bits; } @@ -103,19 +101,16 @@ private static short floatToFloat16Bits(float f32) { } else { outMant = 0; } - } - else if (exp == 0) { + } else if (exp == 0) { outExp = 0; outMant = 0; - } - else { + } else { int newExp = exp - 127 + 15; if (newExp >= 31) { outExp = 0x1F; outMant = 0; - } - else if (newExp <= 0) { + } else if (newExp <= 0) { int fullMant = mant | 0x800000; int shift = 1 - newExp; int netShift = 13 + shift; @@ -133,8 +128,7 @@ else if (newExp <= 0) { outMant++; } } - } - else { + } else { outExp = newExp; outMant = mant >>> 13; @@ -157,7 +151,6 @@ else if (newExp <= 0) { return (short) (outSign | (outExp << 10) | outMant); } - private static float float16BitsToFloat(short bits16) { int bits = bits16 & 0xFFFF; int sign = (bits >>> 15) & 0x1; @@ -171,8 +164,7 @@ private static float float16BitsToFloat(short bits16) { if (mant != 0) { outBits |= mant << 13; } - } - else if (exp == 0) { + } else if (exp == 0) { if (mant != 0) { int shift = Integer.numberOfLeadingZeros(mant) - 22; mant = (mant << shift) & 0x3FF; @@ -180,8 +172,7 @@ else if (exp == 0) { outBits |= newExp << 23; outBits |= mant << 13; } - } - else { + } else { outBits |= (exp - 15 + 127) << 23; outBits |= mant << 13; } From 0b982950c402ac9be75071116573d5166cea9fab Mon Sep 17 00:00:00 2001 From: zhou yong kang Date: Wed, 4 Feb 2026 21:22:12 +0800 Subject: [PATCH 12/16] Update java/fory-core/src/main/java/org/apache/fory/type/Float16.java Co-authored-by: Shawn Yang --- java/fory-core/src/main/java/org/apache/fory/type/Float16.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/type/Float16.java b/java/fory-core/src/main/java/org/apache/fory/type/Float16.java index d429651a2b..c104c4375b 100644 --- a/java/fory-core/src/main/java/org/apache/fory/type/Float16.java +++ b/java/fory-core/src/main/java/org/apache/fory/type/Float16.java @@ -263,7 +263,7 @@ public short shortValue() { return (short) floatValue(); } - public boolean equalsValue(Float16 other) { + public boolean isNumericEqual(Float16 other) { if (isNaN() || other.isNaN()) { return false; } From 004663d257b3dbb9272cb89a4d21b08056bddf5a Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Wed, 4 Feb 2026 22:02:37 +0800 Subject: [PATCH 13/16] fix: remove something --- .../java/org/apache/fory/type/Float16.java | 4 ++++ .../fory-core/reflection-config.json | 18 +----------------- .../fory-core/serialization-config.json | 3 +-- 3 files changed, 6 insertions(+), 19 deletions(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/type/Float16.java b/java/fory-core/src/main/java/org/apache/fory/type/Float16.java index c104c4375b..d7efd9fed6 100644 --- a/java/fory-core/src/main/java/org/apache/fory/type/Float16.java +++ b/java/fory-core/src/main/java/org/apache/fory/type/Float16.java @@ -273,6 +273,10 @@ public boolean isNumericEqual(Float16 other) { return bits == other.bits; } + public boolean equalsValue(Float16 other) { + return isNumericEqual(other); + } + public boolean lessThan(Float16 other) { if (isNaN() || other.isNaN()) { return false; diff --git a/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/reflection-config.json b/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/reflection-config.json index e1e6985175..97d1028ae4 100644 --- a/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/reflection-config.json +++ b/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/reflection-config.json @@ -6,23 +6,7 @@ "allDeclaredMethods": true, "allPublicMethods": true }, - { - "name": "org.apache.fory.type.Float16", - "allDeclaredConstructors": true, - "allPublicConstructors": true, - "allDeclaredMethods": true, - "allPublicMethods": true, - "allDeclaredFields": true, - "allPublicFields": true - }, - { - "name": "org.apache.fory.collection.Float16List", - "allDeclaredConstructors": true, - "allPublicConstructors": true, - "allDeclaredMethods": true, - "allPublicMethods": true, - "allDeclaredFields": true, - "allPublicFields": true } ] + diff --git a/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/serialization-config.json b/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/serialization-config.json index f0d47325bd..6eb32cd7d1 100644 --- a/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/serialization-config.json +++ b/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/serialization-config.json @@ -16,6 +16,5 @@ { "name": "java.lang.NullPointerException" }, { "name": "java.lang.IndexOutOfBoundsException" }, { "name": "java.lang.ArrayIndexOutOfBoundsException" }, - { "name": "java.io.IOException" }, - { "name": "org.apache.fory.type.Float16" } + { "name": "java.io.IOException" } ] From 65f2667ead0ee788d8d0ef1240b3770c491fd7de Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Wed, 4 Feb 2026 22:42:56 +0800 Subject: [PATCH 14/16] fix: remove --- compiler/fory_compiler/generators/java.py | 8 +++++--- .../fory/serializer/ArraySerializers.java | 20 +++++++++---------- .../fory/serializer/PrimitiveSerializers.java | 14 ++++++------- .../apache/fory/serializer/Serializers.java | 4 ++-- .../collection/PrimitiveListSerializers.java | 15 +++++++------- .../main/java/org/apache/fory/type/Types.java | 3 +-- 6 files changed, 30 insertions(+), 34 deletions(-) diff --git a/compiler/fory_compiler/generators/java.py b/compiler/fory_compiler/generators/java.py index cc8307aebc..29f52c3011 100644 --- a/compiler/fory_compiler/generators/java.py +++ b/compiler/fory_compiler/generators/java.py @@ -226,7 +226,7 @@ def generate_bytes_methods(self, class_name: str) -> List[str]: PrimitiveKind.UINT64: "long", PrimitiveKind.VAR_UINT64: "long", PrimitiveKind.TAGGED_UINT64: "long", - PrimitiveKind.FLOAT16: "org.apache.fory.type.Float16", + PrimitiveKind.FLOAT16: "Float16", PrimitiveKind.FLOAT32: "float", PrimitiveKind.FLOAT64: "double", PrimitiveKind.STRING: "String", @@ -255,7 +255,7 @@ def generate_bytes_methods(self, class_name: str) -> List[str]: PrimitiveKind.UINT64: "Long", PrimitiveKind.VAR_UINT64: "Long", PrimitiveKind.TAGGED_UINT64: "Long", - PrimitiveKind.FLOAT16: "org.apache.fory.type.Float16", + PrimitiveKind.FLOAT16: "Float16", PrimitiveKind.FLOAT32: "Float", PrimitiveKind.FLOAT64: "Double", PrimitiveKind.ANY: "Object", @@ -278,7 +278,7 @@ def generate_bytes_methods(self, class_name: str) -> List[str]: PrimitiveKind.UINT64: "long[]", PrimitiveKind.VAR_UINT64: "long[]", PrimitiveKind.TAGGED_UINT64: "long[]", - PrimitiveKind.FLOAT16: "org.apache.fory.type.Float16[]", + PrimitiveKind.FLOAT16: "Float16[]", PrimitiveKind.FLOAT32: "float[]", PrimitiveKind.FLOAT64: "double[]", } @@ -1165,6 +1165,8 @@ def collect_type_imports( imports.add("java.time.LocalDate") elif field_type.kind == PrimitiveKind.TIMESTAMP: imports.add("java.time.Instant") + elif field_type.kind == PrimitiveKind.FLOAT16: + imports.add("org.apache.fory.type.Float16") elif isinstance(field_type, ListType): # Primitive arrays don't need List import diff --git a/java/fory-core/src/main/java/org/apache/fory/serializer/ArraySerializers.java b/java/fory-core/src/main/java/org/apache/fory/serializer/ArraySerializers.java index c1955a332d..83aa5399c8 100644 --- a/java/fory-core/src/main/java/org/apache/fory/serializer/ArraySerializers.java +++ b/java/fory-core/src/main/java/org/apache/fory/serializer/ArraySerializers.java @@ -33,6 +33,7 @@ import org.apache.fory.resolver.TypeResolver; import org.apache.fory.serializer.collection.CollectionFlags; import org.apache.fory.serializer.collection.ForyArrayAsListSerializer; +import org.apache.fory.type.Float16; import org.apache.fory.type.GenericType; import org.apache.fory.type.TypeUtils; import org.apache.fory.util.Preconditions; @@ -881,15 +882,14 @@ private void readFloat64BySwapEndian(MemoryBuffer buffer, double[] values, int n } } - public static final class Float16ArraySerializer - extends PrimitiveArraySerializer { + public static final class Float16ArraySerializer extends PrimitiveArraySerializer { public Float16ArraySerializer(Fory fory) { - super(fory, org.apache.fory.type.Float16[].class); + super(fory, Float16[].class); } @Override - public void write(MemoryBuffer buffer, org.apache.fory.type.Float16[] value) { + public void write(MemoryBuffer buffer, Float16[] value) { int length = value.length; int size = length * 2; buffer.writeVarUint32Small7(size); @@ -904,22 +904,21 @@ public void write(MemoryBuffer buffer, org.apache.fory.type.Float16[] value) { } @Override - public org.apache.fory.type.Float16[] copy(org.apache.fory.type.Float16[] originArray) { + public Float16[] copy(Float16[] originArray) { return Arrays.copyOf(originArray, originArray.length); } @Override - public org.apache.fory.type.Float16[] read(MemoryBuffer buffer) { + public Float16[] read(MemoryBuffer buffer) { int size = buffer.readVarUint32Small7(); int numElements = size / 2; - org.apache.fory.type.Float16[] values = new org.apache.fory.type.Float16[numElements]; + Float16[] values = new Float16[numElements]; int readerIndex = buffer.readerIndex(); buffer.checkReadableBytes(size); for (int i = 0; i < numElements; i++) { - values[i] = - org.apache.fory.type.Float16.fromBits(buffer._unsafeGetInt16(readerIndex + i * 2)); + values[i] = Float16.fromBits(buffer._unsafeGetInt16(readerIndex + i * 2)); } buffer._increaseReaderIndexUnsafe(size); @@ -1054,8 +1053,7 @@ public static void registerDefaultSerializers(Fory fory) { resolver.registerInternalSerializer(double[].class, new DoubleArraySerializer(fory)); resolver.registerInternalSerializer( Double[].class, new ObjectArraySerializer<>(fory, Double[].class)); - resolver.registerInternalSerializer( - org.apache.fory.type.Float16[].class, new Float16ArraySerializer(fory)); + resolver.registerInternalSerializer(Float16[].class, new Float16ArraySerializer(fory)); resolver.registerInternalSerializer(boolean[].class, new BooleanArraySerializer(fory)); resolver.registerInternalSerializer( Boolean[].class, new ObjectArraySerializer<>(fory, Boolean[].class)); diff --git a/java/fory-core/src/main/java/org/apache/fory/serializer/PrimitiveSerializers.java b/java/fory-core/src/main/java/org/apache/fory/serializer/PrimitiveSerializers.java index 5a567f2372..9cbfba043c 100644 --- a/java/fory-core/src/main/java/org/apache/fory/serializer/PrimitiveSerializers.java +++ b/java/fory-core/src/main/java/org/apache/fory/serializer/PrimitiveSerializers.java @@ -29,6 +29,7 @@ import org.apache.fory.memory.Platform; import org.apache.fory.resolver.TypeResolver; import org.apache.fory.serializer.Serializers.CrossLanguageCompatibleSerializer; +import org.apache.fory.type.Float16; import org.apache.fory.util.Preconditions; /** Serializers for java primitive types. */ @@ -342,20 +343,19 @@ public Double read(MemoryBuffer buffer) { } } - public static final class Float16Serializer - extends CrossLanguageCompatibleSerializer { + public static final class Float16Serializer extends CrossLanguageCompatibleSerializer { public Float16Serializer(Fory fory, Class cls) { super(fory, (Class) cls, false, true); } @Override - public void write(MemoryBuffer buffer, org.apache.fory.type.Float16 value) { + public void write(MemoryBuffer buffer, Float16 value) { buffer.writeInt16(value.toBits()); } @Override - public org.apache.fory.type.Float16 read(MemoryBuffer buffer) { - return org.apache.fory.type.Float16.fromBits(buffer.readInt16()); + public Float16 read(MemoryBuffer buffer) { + return Float16.fromBits(buffer.readInt16()); } } @@ -378,8 +378,6 @@ public static void registerDefaultSerializers(Fory fory) { resolver.registerInternalSerializer(Long.class, new LongSerializer(fory, Long.class)); resolver.registerInternalSerializer(Float.class, new FloatSerializer(fory, Float.class)); resolver.registerInternalSerializer(Double.class, new DoubleSerializer(fory, Double.class)); - resolver.registerInternalSerializer( - org.apache.fory.type.Float16.class, - new Float16Serializer(fory, org.apache.fory.type.Float16.class)); + resolver.registerInternalSerializer(Float16.class, new Float16Serializer(fory, Float16.class)); } } diff --git a/java/fory-core/src/main/java/org/apache/fory/serializer/Serializers.java b/java/fory-core/src/main/java/org/apache/fory/serializer/Serializers.java index 6cde4ad018..8103bc0966 100644 --- a/java/fory-core/src/main/java/org/apache/fory/serializer/Serializers.java +++ b/java/fory-core/src/main/java/org/apache/fory/serializer/Serializers.java @@ -46,6 +46,7 @@ import org.apache.fory.builder.Generated; import org.apache.fory.collection.Tuple2; import org.apache.fory.memory.MemoryBuffer; +import org.apache.fory.type.Float16; import org.apache.fory.memory.Platform; import org.apache.fory.meta.TypeDef; import org.apache.fory.reflect.ReflectionUtils; @@ -595,8 +596,7 @@ public static void registerDefaultSerializers(Fory fory) { resolver.registerInternalSerializer(URI.class, new URISerializer(fory)); resolver.registerInternalSerializer(Pattern.class, new RegexSerializer(fory)); resolver.registerInternalSerializer(UUID.class, new UUIDSerializer(fory)); - resolver.registerInternalSerializer( - org.apache.fory.type.Float16.class, new Float16Serializer(fory)); + resolver.registerInternalSerializer(Float16.class, new Float16Serializer(fory)); resolver.registerInternalSerializer(Object.class, new EmptyObjectSerializer(fory)); } } diff --git a/java/fory-core/src/main/java/org/apache/fory/serializer/collection/PrimitiveListSerializers.java b/java/fory-core/src/main/java/org/apache/fory/serializer/collection/PrimitiveListSerializers.java index 61c5567f3f..0ae18c5a9a 100644 --- a/java/fory-core/src/main/java/org/apache/fory/serializer/collection/PrimitiveListSerializers.java +++ b/java/fory-core/src/main/java/org/apache/fory/serializer/collection/PrimitiveListSerializers.java @@ -21,6 +21,7 @@ import org.apache.fory.Fory; import org.apache.fory.collection.BoolList; +import org.apache.fory.collection.Float16List; import org.apache.fory.collection.Float32List; import org.apache.fory.collection.Float64List; import org.apache.fory.collection.Int16List; @@ -540,14 +541,13 @@ public Float64List read(MemoryBuffer buffer) { } public static final class Float16ListSerializer - extends Serializers.CrossLanguageCompatibleSerializer< - org.apache.fory.collection.Float16List> { + extends Serializers.CrossLanguageCompatibleSerializer { public Float16ListSerializer(Fory fory) { - super(fory, org.apache.fory.collection.Float16List.class, false, true); + super(fory, Float16List.class, false, true); } @Override - public void write(MemoryBuffer buffer, org.apache.fory.collection.Float16List value) { + public void write(MemoryBuffer buffer, Float16List value) { int size = value.size(); int byteSize = size * 2; buffer.writeVarUint32Small7(byteSize); @@ -562,7 +562,7 @@ public void write(MemoryBuffer buffer, org.apache.fory.collection.Float16List va } @Override - public org.apache.fory.collection.Float16List read(MemoryBuffer buffer) { + public Float16List read(MemoryBuffer buffer) { int byteSize = buffer.readVarUint32Small7(); int size = byteSize / 2; short[] array = new short[size]; @@ -573,7 +573,7 @@ public org.apache.fory.collection.Float16List read(MemoryBuffer buffer) { array[i] = buffer.readInt16(); } } - return new org.apache.fory.collection.Float16List(array); + return new Float16List(array); } } @@ -592,7 +592,6 @@ public static void registerDefaultSerializers(Fory fory) { resolver.registerInternalSerializer(Uint64List.class, new Uint64ListSerializer(fory)); resolver.registerInternalSerializer(Float32List.class, new Float32ListSerializer(fory)); resolver.registerInternalSerializer(Float64List.class, new Float64ListSerializer(fory)); - resolver.registerInternalSerializer( - org.apache.fory.collection.Float16List.class, new Float16ListSerializer(fory)); + resolver.registerInternalSerializer(Float16List.class, new Float16ListSerializer(fory)); } } diff --git a/java/fory-core/src/main/java/org/apache/fory/type/Types.java b/java/fory-core/src/main/java/org/apache/fory/type/Types.java index 173baa5978..ed26f9dff2 100644 --- a/java/fory-core/src/main/java/org/apache/fory/type/Types.java +++ b/java/fory-core/src/main/java/org/apache/fory/type/Types.java @@ -449,8 +449,7 @@ public static Class getClassForTypeId(int typeId) { return Long.class; case FLOAT8: case FLOAT16: - return org.apache.fory.type.Float16.class; - case BFLOAT16: + return Float16.class; case FLOAT32: return Float.class; case FLOAT64: From 602aaed44778a69acb38de007e1635d8557422e4 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Wed, 4 Feb 2026 22:47:41 +0800 Subject: [PATCH 15/16] fix: ci --- .../src/main/java/org/apache/fory/serializer/Serializers.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/serializer/Serializers.java b/java/fory-core/src/main/java/org/apache/fory/serializer/Serializers.java index 8103bc0966..13b1bdab6e 100644 --- a/java/fory-core/src/main/java/org/apache/fory/serializer/Serializers.java +++ b/java/fory-core/src/main/java/org/apache/fory/serializer/Serializers.java @@ -46,11 +46,11 @@ import org.apache.fory.builder.Generated; import org.apache.fory.collection.Tuple2; import org.apache.fory.memory.MemoryBuffer; -import org.apache.fory.type.Float16; import org.apache.fory.memory.Platform; import org.apache.fory.meta.TypeDef; import org.apache.fory.reflect.ReflectionUtils; import org.apache.fory.resolver.TypeResolver; +import org.apache.fory.type.Float16; import org.apache.fory.util.ExceptionUtils; import org.apache.fory.util.GraalvmSupport; import org.apache.fory.util.GraalvmSupport.GraalvmSerializerHolder; From 18f899c4c0c26ac7d18c2e19ecb5396ef5791746 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Fri, 13 Feb 2026 20:07:22 +0800 Subject: [PATCH 16/16] fix --- .../main/java/org/apache/fory/type/DispatchId.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/type/DispatchId.java b/java/fory-core/src/main/java/org/apache/fory/type/DispatchId.java index fa98a48d1f..9719d8e755 100644 --- a/java/fory-core/src/main/java/org/apache/fory/type/DispatchId.java +++ b/java/fory-core/src/main/java/org/apache/fory/type/DispatchId.java @@ -44,12 +44,12 @@ public class DispatchId { public static final int TAGGED_INT64 = 9; public static final int FLOAT32 = 10; public static final int FLOAT64 = 11; - public static final int FLOAT16 = 12; - public static final int UINT8 = 13; - public static final int UINT16 = 14; - public static final int UINT32 = 15; - public static final int VAR_UINT32 = 16; - public static final int UINT64 = 17; + public static final int UINT8 = 12; + public static final int UINT16 = 13; + public static final int UINT32 = 14; + public static final int VAR_UINT32 = 15; + public static final int UINT64 = 16; + public static final int FLOAT16 = 17; public static final int VAR_UINT64 = 18; public static final int TAGGED_UINT64 = 19; public static final int EXT_UINT8 = 20;