This is an automated email from the ASF dual-hosted git repository.
chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git
The following commit(s) were added to refs/heads/main by this push:
new ede9b6475 feat(java): add float support (#3254)
ede9b6475 is described below
commit ede9b64750b2f03a238928bea3d081f2b7744b89
Author: zhou yong kang <[email protected]>
AuthorDate: Wed Mar 25 06:33:37 2026 +0800
feat(java): add float support (#3254)
## Why?
## What does this PR do?
## Related issues
Close #3205
## Does this PR introduce any user-facing change?
- [ ] Does this PR introduce any public API change?
- [ ] Does this PR introduce any binary protocol compatibility change?
## Benchmark
---------
Co-authored-by: Shawn Yang <[email protected]>
Co-authored-by: chaokunyang <[email protected]>
---
compiler/fory_compiler/generators/java.py | 59 ++--
.../fory_compiler/tests/test_generated_code.py | 36 ++
docs/specification/xlang_type_mapping.md | 4 +-
.../fory/builder/BaseObjectCodecBuilder.java | 25 +-
.../org/apache/fory/collection/Float16List.java | 158 +++++++++
.../main/java/org/apache/fory/meta/FieldTypes.java | 6 +
.../org/apache/fory/resolver/ClassResolver.java | 14 +-
.../org/apache/fory/resolver/XtypeResolver.java | 18 +
.../fory/serializer/AbstractObjectSerializer.java | 11 +
.../apache/fory/serializer/ArraySerializers.java | 63 ++++
.../org/apache/fory/serializer/FieldGroups.java | 20 +-
.../org/apache/fory/serializer/FieldSkipper.java | 1 +
.../apache/fory/serializer/Float16Serializer.java | 41 +++
.../fory/serializer/PrimitiveSerializers.java | 18 +
.../collection/PrimitiveListSerializers.java | 39 +++
.../main/java/org/apache/fory/type/DispatchId.java | 24 +-
.../main/java/org/apache/fory/type/Float16.java | 357 +++++++++++++++++++
.../main/java/org/apache/fory/type/TypeUtils.java | 2 +
.../src/main/java/org/apache/fory/type/Types.java | 4 +-
.../fory-core/native-image.properties | 6 +
.../fory-core/reflection-config.json | 2 +
.../apache/fory/collection/PrimitiveListsTest.java | 32 ++
.../fory/serializer/Float16SerializerTest.java | 378 +++++++++++++++++++++
.../java/org/apache/fory/type/Float16Test.java | 321 +++++++++++++++++
.../test/java/org/apache/fory/type/TypesTest.java | 33 ++
25 files changed, 1621 insertions(+), 51 deletions(-)
diff --git a/compiler/fory_compiler/generators/java.py
b/compiler/fory_compiler/generators/java.py
index a4a576c62..7deca57e8 100644
--- a/compiler/fory_compiler/generators/java.py
+++ b/compiler/fory_compiler/generators/java.py
@@ -226,7 +226,7 @@ class JavaGenerator(BaseGenerator):
PrimitiveKind.UINT64: "long",
PrimitiveKind.VAR_UINT64: "long",
PrimitiveKind.TAGGED_UINT64: "long",
- PrimitiveKind.FLOAT16: "float",
+ PrimitiveKind.FLOAT16: "Float16",
PrimitiveKind.FLOAT32: "float",
PrimitiveKind.FLOAT64: "double",
PrimitiveKind.STRING: "String",
@@ -255,7 +255,7 @@ class JavaGenerator(BaseGenerator):
PrimitiveKind.UINT64: "Long",
PrimitiveKind.VAR_UINT64: "Long",
PrimitiveKind.TAGGED_UINT64: "Long",
- PrimitiveKind.FLOAT16: "Float",
+ PrimitiveKind.FLOAT16: "Float16",
PrimitiveKind.FLOAT32: "Float",
PrimitiveKind.FLOAT64: "Double",
PrimitiveKind.ANY: "Object",
@@ -278,12 +278,12 @@ class JavaGenerator(BaseGenerator):
PrimitiveKind.UINT64: "long[]",
PrimitiveKind.VAR_UINT64: "long[]",
PrimitiveKind.TAGGED_UINT64: "long[]",
- PrimitiveKind.FLOAT16: "float[]",
+ PrimitiveKind.FLOAT16: "Float16[]",
PrimitiveKind.FLOAT32: "float[]",
PrimitiveKind.FLOAT64: "double[]",
}
- # Primitive list types for repeated integer fields (default mode)
+ # Primitive list types for repeated fields when Java should use compact
specialized storage.
PRIMITIVE_LIST_MAP = {
PrimitiveKind.INT8: "Int8List",
PrimitiveKind.INT16: "Int16List",
@@ -299,6 +299,7 @@ class JavaGenerator(BaseGenerator):
PrimitiveKind.UINT64: "Uint64List",
PrimitiveKind.VAR_UINT64: "Uint64List",
PrimitiveKind.TAGGED_UINT64: "Uint64List",
+ PrimitiveKind.FLOAT16: "Float16List",
}
def generate(self) -> List[GeneratedFile]:
@@ -1139,19 +1140,16 @@ class JavaGenerator(BaseGenerator):
return field_type.name
elif isinstance(field_type, ListType):
- # Use primitive arrays for numeric types, or primitive lists by
default for integers
+ # Use specialized primitive lists when available, otherwise
primitive arrays.
if isinstance(field_type.element_type, PrimitiveType):
- if (
- field_type.element_type.kind in self.PRIMITIVE_ARRAY_MAP
- and not element_optional
- and not element_ref
- ):
- if (
- field_type.element_type.kind in self.PRIMITIVE_LIST_MAP
- and not self.java_array(field)
- ):
- return
self.PRIMITIVE_LIST_MAP[field_type.element_type.kind]
- return
self.PRIMITIVE_ARRAY_MAP[field_type.element_type.kind]
+ kind = field_type.element_type.kind
+ if not element_optional and not element_ref:
+ if kind == PrimitiveKind.FLOAT16:
+ return self.PRIMITIVE_LIST_MAP[kind]
+ if kind in self.PRIMITIVE_LIST_MAP and not
self.java_array(field):
+ return self.PRIMITIVE_LIST_MAP[kind]
+ if kind in self.PRIMITIVE_ARRAY_MAP:
+ return self.PRIMITIVE_ARRAY_MAP[kind]
element_type = self.generate_type(field_type.element_type, True)
if self.is_ref_target_type(field_type.element_type):
ref_annotation = "@Ref" if element_ref else
"@Ref(enable=false)"
@@ -1184,24 +1182,29 @@ class JavaGenerator(BaseGenerator):
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
+ # Specialized primitive lists/arrays don't need java.util.List
imports.
if isinstance(field_type.element_type, PrimitiveType):
- if (
- field_type.element_type.kind in self.PRIMITIVE_ARRAY_MAP
- and not element_optional
- and not element_ref
- ):
- if (
- field_type.element_type.kind in self.PRIMITIVE_LIST_MAP
- and not self.java_array(field)
- ):
+ kind = field_type.element_type.kind
+ if not element_optional and not element_ref:
+ if kind == PrimitiveKind.FLOAT16:
+ imports.add(
+ "org.apache.fory.collection."
+ + self.PRIMITIVE_LIST_MAP[kind]
+ )
+ return
+ if kind in self.PRIMITIVE_LIST_MAP and not
self.java_array(field):
imports.add(
"org.apache.fory.collection."
- +
self.PRIMITIVE_LIST_MAP[field_type.element_type.kind]
+ + self.PRIMITIVE_LIST_MAP[kind]
)
- return # No import needed for primitive arrays or
primitive lists
+ return
+ if kind in self.PRIMITIVE_ARRAY_MAP:
+ self.collect_type_imports(field_type.element_type,
imports)
+ return
imports.add("java.util.List")
if self.is_ref_target_type(field_type.element_type):
imports.add("org.apache.fory.annotation.Ref")
diff --git a/compiler/fory_compiler/tests/test_generated_code.py
b/compiler/fory_compiler/tests/test_generated_code.py
index 8fff024e3..06d0ed6bf 100644
--- a/compiler/fory_compiler/tests/test_generated_code.py
+++ b/compiler/fory_compiler/tests/test_generated_code.py
@@ -503,6 +503,42 @@ def test_generated_code_tree_ref_options_equivalent():
assert "SharedWeak<TreeNode>" in cpp_output
+def test_java_float16_equals_hash_contract_generation():
+ schema = parse_fdl(
+ dedent(
+ """
+ package gen;
+
+ message Float16Contract {
+ float16 f16 = 1;
+ optional float16 opt_f16 = 2;
+ }
+ """
+ )
+ )
+ java_output = render_files(generate_files(schema, JavaGenerator))
+ assert "Objects.equals(f16, that.f16)" in java_output
+ assert "Objects.equals(optF16, that.optF16)" in java_output
+ assert "return Objects.hash(f16, optF16);" in java_output
+
+
+def test_java_repeated_float16_generation_uses_float16_list():
+ schema = parse_fdl(
+ dedent(
+ """
+ package gen;
+
+ message RepeatedFloat16 {
+ list<float16> vals = 1;
+ }
+ """
+ )
+ )
+ java_output = render_files(generate_files(schema, JavaGenerator))
+ assert "import org.apache.fory.collection.Float16List;" in java_output
+ assert "private Float16List vals;" in java_output
+
+
def test_go_bfloat16_generation():
idl = dedent(
"""
diff --git a/docs/specification/xlang_type_mapping.md
b/docs/specification/xlang_type_mapping.md
index 533661b3f..7e458189f 100644
--- a/docs/specification/xlang_type_mapping.md
+++ b/docs/specification/xlang_type_mapping.md
@@ -66,7 +66,7 @@ When reading type IDs:
| var_uint64 | 14 | long/Long | int/pyfory.uint64
| Type.varUInt64() | uint64_t | uint64
| u64 |
| tagged_uint64 | 15 | long/Long |
int/pyfory.tagged_uint64 | Type.taggedUInt64() | uint64_t
| uint64 | u64 |
| float8 | 16 | / | /
| / | / | /
| / |
-| float16 | 17 | float/Float |
float/pyfory.float16 | Type.float16() | fory::float16_t
| fory.float16 | fory::f16 |
+| float16 | 17 | Float16 |
float/pyfory.float16 | Type.float16() | fory::float16_t
| fory.float16 | fory::f16 |
| bfloat16 | 18 | / | /
| / | / | /
| / |
| float32 | 19 | float/Float |
float/pyfory.float32 | Type.float32() | float
| float32 | f32 |
| float64 | 20 | double/Double |
float/pyfory.float64 | Type.float64() | double
| float64 | f64 |
@@ -100,7 +100,7 @@ When reading type IDs:
| uint32_array | 50 | long[] | ndarray(uint32)
| / | `uint32_t[n]/vector<T>` | `[n]uint32/[]T`
| `Vec<u32>` |
| uint64_array | 51 | long[] | ndarray(uint64)
| / | `uint64_t[n]/vector<T>` | `[n]uint64/[]T`
| `Vec<u64>` |
| float8_array | 52 | / | /
| / | / | /
| / |
-| float16_array | 53 | float[] | ndarray(float16)
| / | `fory::float16_t[n]/vector<T>` |
`[n]float16/[]T` | `Vec<fory::f16>` |
+| float16_array | 53 | Float16List | ndarray(float16)
| / | `fory::float16_t[n]/vector<T>` |
`[n]float16/[]T` | `Vec<fory::f16>` |
| bfloat16_array | 54 | / | /
| / | / | /
| / |
| float32_array | 55 | float[] | ndarray(float32)
| / | `float[n]/vector<T>` |
`[n]float32/[]T` | `Vec<f32>` |
| float64_array | 56 | double[] | ndarray(float64)
| / | `double[n]/vector<T>` |
`[n]float64/[]T` | `Vec<f64>` |
diff --git
a/java/fory-core/src/main/java/org/apache/fory/builder/BaseObjectCodecBuilder.java
b/java/fory-core/src/main/java/org/apache/fory/builder/BaseObjectCodecBuilder.java
index de0769166..032f2ccd6 100644
---
a/java/fory-core/src/main/java/org/apache/fory/builder/BaseObjectCodecBuilder.java
+++
b/java/fory-core/src/main/java/org/apache/fory/builder/BaseObjectCodecBuilder.java
@@ -108,6 +108,7 @@ import org.apache.fory.codegen.Expression.ListExpression;
import org.apache.fory.codegen.Expression.Literal;
import org.apache.fory.codegen.Expression.Reference;
import org.apache.fory.codegen.Expression.Return;
+import org.apache.fory.codegen.Expression.StaticInvoke;
import org.apache.fory.codegen.Expression.Variable;
import org.apache.fory.codegen.Expression.While;
import org.apache.fory.codegen.ExpressionUtils;
@@ -137,6 +138,7 @@ import
org.apache.fory.serializer.collection.CollectionLikeSerializer;
import org.apache.fory.serializer.collection.MapLikeSerializer;
import org.apache.fory.type.Descriptor;
import org.apache.fory.type.DispatchId;
+import org.apache.fory.type.Float16;
import org.apache.fory.type.GenericType;
import org.apache.fory.type.TypeUtils;
import org.apache.fory.type.Types;
@@ -451,7 +453,7 @@ public abstract class BaseObjectCodecBuilder extends
CodecBuilder {
Expression inputObject, Expression buffer, Descriptor descriptor,
Expression serializer) {
TypeRef<?> typeRef = descriptor.getTypeRef();
Class<?> clz = getRawType(typeRef);
- if (isPrimitive(clz) || isBoxed(clz)) {
+ if (isPrimitiveLikeDescriptor(descriptor, clz)) {
return serializePrimitiveField(inputObject, buffer, descriptor);
} else {
if (clz == String.class) {
@@ -505,6 +507,8 @@ public abstract class BaseObjectCodecBuilder extends
CodecBuilder {
return new Invoke(buffer, "writeFloat32", inputObject);
case DispatchId.FLOAT64:
return new Invoke(buffer, "writeFloat64", inputObject);
+ case DispatchId.FLOAT16:
+ return new Invoke(buffer, "writeInt16", new Invoke(inputObject,
"toBits", SHORT_TYPE));
default:
throw new IllegalStateException("Unsupported dispatchId: " +
dispatchId);
}
@@ -662,9 +666,19 @@ public abstract class BaseObjectCodecBuilder extends
CodecBuilder {
}
protected int getNumericDescriptorDispatchId(Descriptor descriptor) {
+ int dispatchId =
+ descriptorDispatchId.computeIfAbsent(descriptor, d ->
DispatchId.getDispatchId(fory, d));
Class<?> rawType = descriptor.getRawType();
- Preconditions.checkArgument(TypeUtils.unwrap(rawType).isPrimitive());
- return descriptorDispatchId.computeIfAbsent(descriptor, d ->
DispatchId.getDispatchId(fory, d));
+ Preconditions.checkArgument(
+ TypeUtils.unwrap(rawType).isPrimitive() || dispatchId ==
DispatchId.FLOAT16);
+ return dispatchId;
+ }
+
+ private boolean isPrimitiveLikeDescriptor(Descriptor descriptor, Class<?>
rawType) {
+ if (isPrimitive(rawType) || isBoxed(rawType)) {
+ return true;
+ }
+ return rawType == Float16.class;
}
/**
@@ -2005,7 +2019,7 @@ public abstract class BaseObjectCodecBuilder extends
CodecBuilder {
Expression buffer, Descriptor descriptor, Expression serializer) {
TypeRef<?> typeRef = descriptor.getTypeRef();
Class<?> cls = getRawType(typeRef);
- if (isPrimitive(cls) || isBoxed(cls)) {
+ if (isPrimitiveLikeDescriptor(descriptor, cls)) {
return deserializePrimitiveField(buffer, descriptor);
} else {
if (cls == String.class) {
@@ -2078,6 +2092,9 @@ public abstract class BaseObjectCodecBuilder extends
CodecBuilder {
return isPrimitive
? readFloat64(buffer)
: new Invoke(buffer, readFloat64Func(), DOUBLE_TYPE);
+ case DispatchId.FLOAT16:
+ return new StaticInvoke(
+ Float16.class, "fromBits", TypeRef.of(Float16.class),
readInt16(buffer));
default:
throw new IllegalStateException("Unsupported dispatchId: " +
dispatchId);
}
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 000000000..9c37ff2f1
--- /dev/null
+++ b/java/fory-core/src/main/java/org/apache/fory/collection/Float16List.java
@@ -0,0 +1,158 @@
+/*
+ * 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<Float16> 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.toBits(value);
+ }
+
+ @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.toBits(value);
+ modCount++;
+ return true;
+ }
+
+ public float getFloat(int index) {
+ checkIndex(index);
+ return Float16.toFloat(array[index]);
+ }
+
+ 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);
+ }
+ }
+}
diff --git a/java/fory-core/src/main/java/org/apache/fory/meta/FieldTypes.java
b/java/fory-core/src/main/java/org/apache/fory/meta/FieldTypes.java
index b683c976e..1edb55d87 100644
--- a/java/fory-core/src/main/java/org/apache/fory/meta/FieldTypes.java
+++ b/java/fory-core/src/main/java/org/apache/fory/meta/FieldTypes.java
@@ -33,6 +33,7 @@ import java.lang.reflect.Field;
import java.util.Objects;
import org.apache.fory.annotation.ForyField;
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;
@@ -54,6 +55,7 @@ import org.apache.fory.resolver.TypeResolver;
import org.apache.fory.resolver.XtypeResolver;
import org.apache.fory.serializer.UnknownClass;
import org.apache.fory.type.Descriptor;
+import org.apache.fory.type.Float16;
import org.apache.fory.type.GenericType;
import org.apache.fory.type.TypeUtils;
import org.apache.fory.type.Types;
@@ -644,6 +646,8 @@ public class FieldTypes {
return long[].class;
case Types.FLOAT32_ARRAY:
return float[].class;
+ case Types.FLOAT16_ARRAY:
+ return Float16[].class;
case Types.FLOAT64_ARRAY:
return double[].class;
default:
@@ -673,6 +677,8 @@ public class FieldTypes {
return Uint64List.class;
case Types.FLOAT32_ARRAY:
return Float32List.class;
+ case Types.FLOAT16_ARRAY:
+ return Float16List.class;
case Types.FLOAT64_ARRAY:
return Float64List.class;
default:
diff --git
a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java
b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java
index 0369866e5..75a85a916 100644
--- a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java
+++ b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java
@@ -78,6 +78,7 @@ import org.apache.fory.annotation.ForyField;
import org.apache.fory.annotation.Internal;
import org.apache.fory.builder.JITContext;
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;
@@ -147,6 +148,7 @@ import org.apache.fory.serializer.shim.ProtobufDispatcher;
import org.apache.fory.serializer.shim.ShimDispatcher;
import org.apache.fory.type.Descriptor;
import org.apache.fory.type.DescriptorGrouper;
+import org.apache.fory.type.Float16;
import org.apache.fory.type.GenericType;
import org.apache.fory.type.TypeUtils;
import org.apache.fory.type.Types;
@@ -265,6 +267,7 @@ public class ClassResolver extends TypeResolver {
registerInternal(Float.class, Types.FLOAT32);
registerInternal(Long.class, Types.INT64);
registerInternal(Double.class, Types.FLOAT64);
+ registerInternal(Float16.class, Types.FLOAT16);
registerInternal(String.class, Types.STRING);
registerInternal(Uint8.class, Types.UINT8);
registerInternal(Uint16.class, Types.UINT16);
@@ -278,6 +281,7 @@ public class ClassResolver extends TypeResolver {
registerInternal(float[].class, PRIMITIVE_FLOAT_ARRAY_ID);
registerInternal(long[].class, PRIMITIVE_LONG_ARRAY_ID);
registerInternal(double[].class, PRIMITIVE_DOUBLE_ARRAY_ID);
+ registerInternal(Float16[].class);
registerInternal(String[].class, STRING_ARRAY_ID);
registerInternal(Object[].class, OBJECT_ARRAY_ID);
registerInternal(BoolList.class, Types.BOOL_ARRAY);
@@ -291,6 +295,7 @@ public class ClassResolver extends TypeResolver {
registerInternal(Uint64List.class, Types.UINT64_ARRAY);
registerInternal(Float32List.class, Types.FLOAT32_ARRAY);
registerInternal(Float64List.class, Types.FLOAT64_ARRAY);
+ registerInternal(Float16List.class, Types.FLOAT16_ARRAY);
registerInternal(ArrayList.class, ARRAYLIST_ID);
registerInternal(HashMap.class, HASHMAP_ID);
registerInternal(HashSet.class, HASHSET_ID);
@@ -675,7 +680,8 @@ public class ClassResolver extends TypeResolver {
if (classId < typeIdToTypeInfo.length && typeIdToTypeInfo[classId] !=
null) {
throw new IllegalArgumentException(
String.format(
- "Class %s with id %s has been registered, registering class
%s with same id are not allowed.",
+ "Class %s with id %s has been registered, registering class
%s with same id are"
+ + " not allowed.",
typeIdToTypeInfo[classId].getCls(), classId, cls.getName()));
}
} else {
@@ -683,7 +689,8 @@ public class ClassResolver extends TypeResolver {
if (existingInfo != null) {
throw new IllegalArgumentException(
String.format(
- "Class %s with id %s has been registered, registering class
%s with same id are not allowed.",
+ "Class %s with id %s has been registered, registering class
%s with same id are"
+ + " not allowed.",
existingInfo.getCls(), classId, cls.getName()));
}
}
@@ -692,7 +699,8 @@ public class ClassResolver extends TypeResolver {
|| extRegistry.registeredClasses.inverse().containsKey(cls)) {
throw new IllegalArgumentException(
String.format(
- "Class %s with name %s has been registered, registering class %s
with same name are not allowed.",
+ "Class %s with name %s has been registered, registering class %s
with same name are"
+ + " not allowed.",
extRegistry.registeredClasses.get(name), name, cls));
}
}
diff --git
a/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java
b/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java
index 0ca109846..79622ee40 100644
--- a/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java
+++ b/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java
@@ -56,6 +56,7 @@ import org.apache.fory.Fory;
import org.apache.fory.annotation.ForyField;
import org.apache.fory.annotation.Internal;
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;
@@ -109,6 +110,7 @@ import
org.apache.fory.serializer.collection.MapSerializers.XlangMapSerializer;
import org.apache.fory.serializer.collection.PrimitiveListSerializers;
import org.apache.fory.type.Descriptor;
import org.apache.fory.type.DescriptorGrouper;
+import org.apache.fory.type.Float16;
import org.apache.fory.type.GenericType;
import org.apache.fory.type.Generics;
import org.apache.fory.type.TypeUtils;
@@ -493,6 +495,12 @@ public class XtypeResolver extends TypeResolver {
* typename bytes.
*/
private int determineTypeIdForClass(Class<?> type) {
+ if (type == Float16.class) {
+ return Types.FLOAT16;
+ }
+ if (type == Float16[].class || type == Float16List.class) {
+ return Types.FLOAT16_ARRAY;
+ }
if (type.isArray()) {
Class<?> componentType = type.getComponentType();
if (componentType.isPrimitive()) {
@@ -854,6 +862,10 @@ public class XtypeResolver extends TypeResolver {
Types.FLOAT32, Float.class, new
PrimitiveSerializers.FloatSerializer(fory, Float.class));
registerType(
Types.FLOAT32, float.class, new
PrimitiveSerializers.FloatSerializer(fory, float.class));
+ registerType(
+ Types.FLOAT16,
+ Float16.class,
+ new PrimitiveSerializers.Float16Serializer(fory, Float16.class));
registerType(
Types.FLOAT64, Double.class, new
PrimitiveSerializers.DoubleSerializer(fory, Double.class));
registerType(
@@ -907,6 +919,8 @@ public class XtypeResolver extends TypeResolver {
Types.FLOAT32_ARRAY, float[].class, new
ArraySerializers.FloatArraySerializer(fory));
registerType(
Types.FLOAT64_ARRAY, double[].class, new
ArraySerializers.DoubleArraySerializer(fory));
+ registerType(
+ Types.FLOAT16_ARRAY, Float16[].class, new
ArraySerializers.Float16ArraySerializer(fory));
// Primitive lists
registerType(
@@ -941,6 +955,10 @@ public class XtypeResolver extends TypeResolver {
Types.FLOAT64_ARRAY,
Float64List.class,
new PrimitiveListSerializers.Float64ListSerializer(fory));
+ registerType(
+ Types.FLOAT16_ARRAY,
+ Float16List.class,
+ new PrimitiveListSerializers.Float16ListSerializer(fory));
// Collections
registerType(Types.LIST, ArrayList.class, new ArrayListSerializer(fory));
diff --git
a/java/fory-core/src/main/java/org/apache/fory/serializer/AbstractObjectSerializer.java
b/java/fory-core/src/main/java/org/apache/fory/serializer/AbstractObjectSerializer.java
index 13ae09934..4bdc829b9 100644
---
a/java/fory-core/src/main/java/org/apache/fory/serializer/AbstractObjectSerializer.java
+++
b/java/fory-core/src/main/java/org/apache/fory/serializer/AbstractObjectSerializer.java
@@ -43,6 +43,7 @@ import
org.apache.fory.serializer.FieldGroups.SerializationFieldInfo;
import org.apache.fory.type.Descriptor;
import org.apache.fory.type.DescriptorGrouper;
import org.apache.fory.type.DispatchId;
+import org.apache.fory.type.Float16;
import org.apache.fory.type.Generics;
import org.apache.fory.type.unsigned.Uint16;
import org.apache.fory.type.unsigned.Uint32;
@@ -450,6 +451,9 @@ public abstract class AbstractObjectSerializer<T> extends
Serializer<T> {
case DispatchId.FLOAT64:
buffer.writeFloat64((Double) fieldValue);
return;
+ case DispatchId.FLOAT16:
+ buffer.writeInt16(((Float16) fieldValue).toBits());
+ return;
default:
writeField(fory, typeResolver, refResolver, fieldInfo, RefMode.NONE,
buffer, fieldValue);
}
@@ -708,6 +712,8 @@ public abstract class AbstractObjectSerializer<T> extends
Serializer<T> {
return buffer.readFloat32();
case DispatchId.FLOAT64:
return buffer.readFloat64();
+ case DispatchId.FLOAT16:
+ return Float16.fromBits(buffer.readInt16());
case DispatchId.STRING:
return fory.readString(buffer);
default:
@@ -928,6 +934,9 @@ public abstract class AbstractObjectSerializer<T> extends
Serializer<T> {
case DispatchId.FLOAT64:
fieldAccessor.putObject(targetObject, buffer.readFloat64());
return;
+ case DispatchId.FLOAT16:
+ fieldAccessor.putObject(targetObject,
Float16.fromBits(buffer.readInt16()));
+ return;
case DispatchId.STRING:
fieldAccessor.putObject(targetObject, fory.readString(buffer));
return;
@@ -1085,6 +1094,7 @@ public abstract class AbstractObjectSerializer<T> extends
Serializer<T> {
case DispatchId.TAGGED_UINT64:
case DispatchId.FLOAT32:
case DispatchId.FLOAT64:
+ case DispatchId.FLOAT16:
case DispatchId.STRING:
Platform.putObject(newObj, fieldOffset, Platform.getObject(originObj,
fieldOffset));
break;
@@ -1147,6 +1157,7 @@ public abstract class AbstractObjectSerializer<T> extends
Serializer<T> {
case DispatchId.VAR_UINT64:
case DispatchId.TAGGED_UINT64:
case DispatchId.FLOAT64:
+ case DispatchId.FLOAT16:
case DispatchId.STRING:
return Platform.getObject(targetObject, fieldOffset);
default:
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 0df826a8a..98e18b44a 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.TypeInfoHolder;
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;
@@ -868,6 +869,67 @@ public class ArraySerializers {
}
}
+ public static final class Float16ArraySerializer extends
PrimitiveArraySerializer<Float16[]> {
+ public Float16ArraySerializer(Fory fory) {
+ super(fory, Float16[].class);
+ }
+
+ @Override
+ public void write(MemoryBuffer buffer, Float16[] value) {
+ int length = value.length;
+ for (int i = 0; i < length; i++) {
+ if (value[i] == null) {
+ throw new IllegalArgumentException(
+ "Float16[] doesn't support null elements at index " + i);
+ }
+ }
+ writeNonNull(buffer, value, length);
+ }
+
+ private void writeNonNull(MemoryBuffer buffer, Float16[] value, int
length) {
+ int size = length * 2;
+ buffer.writeVarUint32Small7(size);
+
+ if (Platform.IS_LITTLE_ENDIAN) {
+ 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);
+ } else {
+ for (int i = 0; i < length; i++) {
+ buffer.writeInt16(value[i].toBits());
+ }
+ }
+ }
+
+ @Override
+ public Float16[] copy(Float16[] originArray) {
+ return Arrays.copyOf(originArray, originArray.length);
+ }
+
+ @Override
+ public Float16[] read(MemoryBuffer buffer) {
+ int size = buffer.readVarUint32Small7();
+ int numElements = size / 2;
+ Float16[] values = new Float16[numElements];
+ if (Platform.IS_LITTLE_ENDIAN) {
+ int readerIndex = buffer.readerIndex();
+ buffer.checkReadableBytes(size);
+ for (int i = 0; i < numElements; i++) {
+ values[i] = Float16.fromBits(buffer._unsafeGetInt16(readerIndex + i
* 2));
+ }
+ buffer._increaseReaderIndexUnsafe(size);
+ } else {
+ for (int i = 0; i < numElements; i++) {
+ values[i] = Float16.fromBits(buffer.readInt16());
+ }
+ }
+ return values;
+ }
+ }
+
public static final class StringArraySerializer extends Serializer<String[]>
{
private final StringSerializer stringSerializer;
private final ForyArrayAsListSerializer collectionSerializer;
@@ -992,6 +1054,7 @@ public class ArraySerializers {
resolver.registerInternalSerializer(double[].class, new
DoubleArraySerializer(fory));
resolver.registerInternalSerializer(
Double[].class, new ObjectArraySerializer<>(fory, Double[].class));
+ 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/FieldGroups.java
b/java/fory-core/src/main/java/org/apache/fory/serializer/FieldGroups.java
index 44329547a..d42e8e520 100644
--- a/java/fory-core/src/main/java/org/apache/fory/serializer/FieldGroups.java
+++ b/java/fory-core/src/main/java/org/apache/fory/serializer/FieldGroups.java
@@ -82,11 +82,23 @@ public class FieldGroups {
public static FieldGroups buildFieldInfos(Fory fory, DescriptorGrouper
grouper) {
// When a type is both Collection/Map and final, add it to collection/map
fields to keep
// consistent with jit.
- Collection<Descriptor> primitives = grouper.getPrimitiveDescriptors();
- Collection<Descriptor> boxed = grouper.getBoxedDescriptors();
+ List<Descriptor> primitives = new
ArrayList<>(grouper.getPrimitiveDescriptors());
+ List<Descriptor> boxed = new ArrayList<>(grouper.getBoxedDescriptors());
Collection<Descriptor> buildIn = grouper.getBuildInDescriptors();
+ List<Descriptor> regularBuildIn = new ArrayList<>(buildIn.size());
+ for (Descriptor d : buildIn) {
+ if (DispatchId.getDispatchId(fory, d) == DispatchId.FLOAT16) {
+ if (d.isNullable()) {
+ boxed.add(d);
+ } else {
+ primitives.add(d);
+ }
+ } else {
+ regularBuildIn.add(d);
+ }
+ }
SerializationFieldInfo[] allBuildIn =
- new SerializationFieldInfo[primitives.size() + boxed.size() +
buildIn.size()];
+ new SerializationFieldInfo[primitives.size() + boxed.size() +
regularBuildIn.size()];
int cnt = 0;
for (Descriptor d : primitives) {
allBuildIn[cnt++] = new SerializationFieldInfo(fory, d);
@@ -94,7 +106,7 @@ public class FieldGroups {
for (Descriptor d : boxed) {
allBuildIn[cnt++] = new SerializationFieldInfo(fory, d);
}
- for (Descriptor d : buildIn) {
+ for (Descriptor d : regularBuildIn) {
allBuildIn[cnt++] = new SerializationFieldInfo(fory, d);
}
cnt = 0;
diff --git
a/java/fory-core/src/main/java/org/apache/fory/serializer/FieldSkipper.java
b/java/fory-core/src/main/java/org/apache/fory/serializer/FieldSkipper.java
index 11efa00dc..830bbac9f 100644
--- a/java/fory-core/src/main/java/org/apache/fory/serializer/FieldSkipper.java
+++ b/java/fory-core/src/main/java/org/apache/fory/serializer/FieldSkipper.java
@@ -78,6 +78,7 @@ public class FieldSkipper {
case DispatchId.INT16:
case DispatchId.UINT16:
case DispatchId.EXT_UINT16:
+ case DispatchId.FLOAT16:
buffer.increaseReaderIndex(2);
break;
case DispatchId.INT32:
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 000000000..e92aef336
--- /dev/null
+++
b/java/fory-core/src/main/java/org/apache/fory/serializer/Float16Serializer.java
@@ -0,0 +1,41 @@
+/*
+ * 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<Float16> {
+
+ 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());
+ }
+}
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 74dfc54e6..8b32b626b 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.MemoryBuffer;
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. */
@@ -299,6 +300,22 @@ public class PrimitiveSerializers {
}
}
+ public static final class Float16Serializer extends
CrossLanguageCompatibleSerializer<Float16> {
+ public Float16Serializer(Fory fory, Class<?> cls) {
+ super(fory, (Class) cls, false, true);
+ }
+
+ @Override
+ public void write(MemoryBuffer buffer, Float16 value) {
+ buffer.writeInt16(value.toBits());
+ }
+
+ @Override
+ public Float16 read(MemoryBuffer buffer) {
+ return Float16.fromBits(buffer.readInt16());
+ }
+ }
+
public static void registerDefaultSerializers(Fory fory) {
// primitive types will be boxed.
TypeResolver resolver = fory.getTypeResolver();
@@ -318,5 +335,6 @@ public class PrimitiveSerializers {
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(Float16.class, new
Float16Serializer(fory, Float16.class));
}
}
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 d86556bb1..0ae18c5a9 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 @@ package org.apache.fory.serializer.collection;
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;
@@ -539,6 +540,43 @@ public class PrimitiveListSerializers {
}
}
+ public static final class Float16ListSerializer
+ extends Serializers.CrossLanguageCompatibleSerializer<Float16List> {
+ public Float16ListSerializer(Fory fory) {
+ super(fory, Float16List.class, false, true);
+ }
+
+ @Override
+ public void write(MemoryBuffer buffer, 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 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 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 +592,6 @@ public class PrimitiveListSerializers {
resolver.registerInternalSerializer(Uint64List.class, new
Uint64ListSerializer(fory));
resolver.registerInternalSerializer(Float32List.class, new
Float32ListSerializer(fory));
resolver.registerInternalSerializer(Float64List.class, new
Float64ListSerializer(fory));
+ resolver.registerInternalSerializer(Float16List.class, new
Float16ListSerializer(fory));
}
}
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 d154e3352..04cd5e64a 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
@@ -49,19 +49,23 @@ public class DispatchId {
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 = 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);
Class<?> rawType = d.getTypeRef().getRawType();
+ if (rawType == Float16.class) {
+ return FLOAT16;
+ }
if (fory.isCrossLanguage()) {
return adjustUnsignedDispatchId(typeId, rawType,
xlangTypeIdToDispatchId(typeId));
} else {
@@ -101,6 +105,8 @@ public class DispatchId {
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 000000000..c76eaa362
--- /dev/null
+++ b/java/fory-core/src/main/java/org/apache/fory/type/Float16.java
@@ -0,0 +1,357 @@
+/*
+ * 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;
+
+public final class Float16 extends Number implements Comparable<Float16>,
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;
+ 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;
+ private static final short BITS_MIN_VALUE = (short) 0x0001;
+
+ 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);
+
+ public static final Float16 ONE = new Float16(BITS_ONE);
+
+ public static final Float16 MAX_VALUE = new Float16(BITS_MAX);
+
+ public static final Float16 MIN_NORMAL = new Float16(BITS_MIN_NORMAL);
+
+ public static final Float16 MIN_VALUE = new Float16(BITS_MIN_VALUE);
+
+ public static final int SIZE_BITS = 16;
+
+ public static final int SIZE_BYTES = 2;
+
+ private final short bits;
+
+ private Float16(short bits) {
+ this.bits = bits;
+ }
+
+ public static Float16 fromBits(short bits) {
+ return new Float16(bits);
+ }
+
+ public static Float16 valueOf(float value) {
+ return new Float16(floatToFloat16Bits(value));
+ }
+
+ public static short toBits(float value) {
+ return floatToFloat16Bits(value);
+ }
+
+ public short toBits() {
+ return bits;
+ }
+
+ public static float toFloat(short bits) {
+ return float16BitsToFloat(bits);
+ }
+
+ public float toFloat() {
+ return floatValue();
+ }
+
+ 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;
+
+ if (exp == 0xFF) {
+ outExp = 0x1F;
+ if (mant != 0) {
+ outMant = 0x200 | ((mant >>> 13) & 0x1FF);
+ if (outMant == 0x200) {
+ outMant = 0x201;
+ }
+ } else {
+ outMant = 0;
+ }
+ } else if (exp == 0) {
+ outExp = 0;
+ outMant = 0;
+ } else {
+ int newExp = exp - 127 + 15;
+
+ if (newExp >= 31) {
+ outExp = 0x1F;
+ outMant = 0;
+ } else if (newExp <= 0) {
+ 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;
+ }
+ }
+ }
+ }
+ }
+
+ return (short) (outSign | (outExp << 10) | outMant);
+ }
+
+ 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;
+
+ if (exp == 0x1F) {
+ outBits |= 0xFF << 23;
+ if (mant != 0) {
+ outBits |= mant << 13;
+ }
+ } else if (exp == 0) {
+ if (mant != 0) {
+ int shift = Integer.numberOfLeadingZeros(mant) - 21;
+ 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);
+ }
+
+ 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;
+ }
+
+ public boolean isFinite() {
+ return (bits & EXP_MASK) != EXP_MASK;
+ }
+
+ public boolean isZero() {
+ return (bits & (EXP_MASK | MANT_MASK)) == 0;
+ }
+
+ 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;
+ }
+
+ public boolean signbit() {
+ return (bits & SIGN_MASK) != 0;
+ }
+
+ public Float16 add(Float16 other) {
+ return valueOf(floatValue() + other.floatValue());
+ }
+
+ public Float16 subtract(Float16 other) {
+ return valueOf(floatValue() - other.floatValue());
+ }
+
+ public Float16 multiply(Float16 other) {
+ return valueOf(floatValue() * other.floatValue());
+ }
+
+ public Float16 divide(Float16 other) {
+ return valueOf(floatValue() / other.floatValue());
+ }
+
+ public Float16 negate() {
+ return fromBits((short) (bits ^ SIGN_MASK));
+ }
+
+ public Float16 abs() {
+ return fromBits((short) (bits & ~SIGN_MASK));
+ }
+
+ @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();
+ }
+
+ public boolean isNumericEqual(Float16 other) {
+ if (isNaN() || other.isNaN()) {
+ return false;
+ }
+ if (isZero() && other.isZero()) {
+ return true;
+ }
+ return bits == other.bits;
+ }
+
+ public boolean equalsValue(Float16 other) {
+ return isNumericEqual(other);
+ }
+
+ 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) {
+ if (a.bits == b.bits) {
+ return 0;
+ }
+ int compare = Float.compare(a.floatValue(), b.floatValue());
+ if (compare != 0) {
+ return compare;
+ }
+ return Integer.compare(a.bits & 0xFFFF, b.bits & 0xFFFF);
+ }
+
+ public static Float16 parse(String s) {
+ return valueOf(Float.parseFloat(s));
+ }
+
+ @Override
+ public int compareTo(Float16 other) {
+ return compare(this, other);
+ }
+
+ @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/TypeUtils.java
b/java/fory-core/src/main/java/org/apache/fory/type/TypeUtils.java
index 5896cbbca..caa8b3ab9 100644
--- a/java/fory-core/src/main/java/org/apache/fory/type/TypeUtils.java
+++ b/java/fory-core/src/main/java/org/apache/fory/type/TypeUtils.java
@@ -71,6 +71,7 @@ import java.util.WeakHashMap;
import java.util.stream.Collectors;
import org.apache.fory.annotation.Ref;
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.IdentityMap;
@@ -682,6 +683,7 @@ public class TypeUtils {
|| cls == Uint16List.class
|| cls == Uint32List.class
|| cls == Uint64List.class
+ || cls == Float16List.class
|| cls == Float32List.class
|| cls == Float64List.class;
}
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 f964eada0..344982ff9 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
@@ -448,8 +448,10 @@ public class Types {
case TAGGED_UINT64:
return Long.class;
case FLOAT8:
- case FLOAT16:
case BFLOAT16:
+ return Float.class;
+ case FLOAT16:
+ return Float16.class;
case FLOAT32:
return Float.class;
case FLOAT64:
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 dfea5f83f..e2de4c6d5 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,8 @@
# 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,\
+ org.apache.fory.serializer.PrimitiveSerializers$Float16Serializer,\
com.google.common.base.Equivalence$Equals,\
com.google.common.base.Equivalence$Identity,\
com.google.common.base.Equivalence,\
@@ -207,6 +209,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,\
@@ -305,6 +308,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,\
@@ -323,6 +327,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,\
@@ -509,6 +514,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,\
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 505934bf9..8abf88f30 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
@@ -7,3 +7,5 @@
"allPublicMethods": true
}
]
+
+
diff --git
a/java/fory-core/src/test/java/org/apache/fory/collection/PrimitiveListsTest.java
b/java/fory-core/src/test/java/org/apache/fory/collection/PrimitiveListsTest.java
index 5893a8e02..8b472da5f 100644
---
a/java/fory-core/src/test/java/org/apache/fory/collection/PrimitiveListsTest.java
+++
b/java/fory-core/src/test/java/org/apache/fory/collection/PrimitiveListsTest.java
@@ -21,6 +21,7 @@ package org.apache.fory.collection;
import static org.testng.Assert.*;
+import org.apache.fory.type.Float16;
import org.apache.fory.type.unsigned.Uint16;
import org.apache.fory.type.unsigned.Uint32;
import org.apache.fory.type.unsigned.Uint64;
@@ -242,6 +243,37 @@ public class PrimitiveListsTest {
expectThrows(NullPointerException.class, () -> list.set(0, (Double) null));
}
+ @Test
+ public void float16ListSupportsPrimitiveOps() {
+ Float16List list = new Float16List();
+ assertEquals(list.size(), 0);
+ short[] initial = list.getArray();
+
+ for (int i = 0; i < 12; i++) {
+ list.add((float) i + 0.5f);
+ }
+ assertEquals(list.size(), 12);
+ assertNotSame(initial, list.getArray());
+
+ list.add(3, Float16.valueOf(42.25f));
+ assertEquals(list.getFloat(3), 42.25f, 0.0f);
+ assertEquals(list.getFloat(4), 3.5f, 0.0f);
+
+ Float16 prev = list.set(0, Float16.valueOf(-5.25f));
+ assertEquals(prev.toBits(), Float16.toBits(0.5f));
+ list.set(0, 11.75f);
+ assertEquals(list.getFloat(0), 11.75f, 0.0f);
+ assertEquals(list.getShort(0), Float16.toBits(11.75f));
+
+ short[] copy = list.copyArray();
+ assertEquals(copy.length, list.size());
+ assertEquals(copy[3], list.getShort(3));
+ assertNotSame(copy, list.getArray());
+
+ expectThrows(NullPointerException.class, () -> list.add((Float16) null));
+ expectThrows(NullPointerException.class, () -> list.set(0, (Float16)
null));
+ }
+
@Test
public void uint8ListSupportsPrimitiveOps() {
Uint8List list = new Uint8List();
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 000000000..f0d6842f9
--- /dev/null
+++
b/java/fory-core/src/test/java/org/apache/fory/serializer/Float16SerializerTest.java
@@ -0,0 +1,378 @@
+/*
+ * 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.collection.Float16List;
+import org.apache.fory.config.Language;
+import org.apache.fory.exception.SerializationException;
+import org.apache.fory.type.Float16;
+import org.testng.annotations.Test;
+
+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 testFloat16ArraySerializationWithNullElements() {
+ Fory fory =
Fory.builder().withLanguage(Language.JAVA).requireClassRegistration(false).build();
+
+ Float16[] array =
+ new Float16[] {Float16.ONE, null, Float16.valueOf(-2.5f), null,
Float16.MIN_VALUE};
+ SerializationException ex =
+ expectThrows(SerializationException.class, () ->
fory.serialize(array));
+ assertTrue(ex.getMessage().contains("Float16[] doesn't support null
elements"));
+ }
+
+ @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());
+ }
+ }
+
+ @Data
+ @AllArgsConstructor
+ public static class StructWithFloat16List {
+ Float16List f16List;
+ }
+
+ @Test
+ public void testStructWithFloat16ListField() {
+ Fory fory =
Fory.builder().withLanguage(Language.JAVA).requireClassRegistration(false).build();
+ fory.register(StructWithFloat16List.class);
+
+ StructWithFloat16List obj = new StructWithFloat16List(buildFloat16List());
+ byte[] bytes = fory.serialize(obj);
+ StructWithFloat16List result = (StructWithFloat16List)
fory.deserialize(bytes);
+
+ assertFloat16ListBits(obj.f16List, result.f16List);
+ }
+
+ @Test
+ public void testFloat16ListTopLevelSerialization() {
+ Fory fory =
Fory.builder().withLanguage(Language.JAVA).requireClassRegistration(false).build();
+
+ Float16List list = buildFloat16List();
+ byte[] bytes = fory.serialize(list);
+ Float16List result = (Float16List) fory.deserialize(bytes);
+
+ assertFloat16ListBits(list, result);
+ }
+
+ @Test
+ public void testFloat16XlangTopLevelSerialization() {
+ Fory fory =
+ Fory.builder()
+ .withLanguage(Language.XLANG)
+ .withRefTracking(true)
+ .requireClassRegistration(false)
+ .build();
+
+ Float16 scalar = Float16.valueOf(-1.75f);
+ byte[] bytes = fory.serialize(scalar);
+ Float16 scalarResult = (Float16) fory.deserialize(bytes);
+ assertEquals(scalarResult.toBits(), scalar.toBits());
+
+ Float16[] array = new Float16[] {Float16.ONE, Float16.valueOf(-0.5f),
Float16.MIN_VALUE};
+ bytes = fory.serialize(array);
+ Float16[] arrayResult = (Float16[]) fory.deserialize(bytes);
+ assertEquals(arrayResult.length, array.length);
+ for (int i = 0; i < array.length; i++) {
+ assertEquals(arrayResult[i].toBits(), array[i].toBits(), "Index " + i +
" should match");
+ }
+
+ Float16List list = buildFloat16List();
+ bytes = fory.serialize(list);
+ Object listResult = fory.deserialize(bytes);
+ if (listResult instanceof Float16List) {
+ assertFloat16ListBits(list, (Float16List) listResult);
+ } else {
+ Float16[] arrayResultFromList = (Float16[]) listResult;
+ assertEquals(arrayResultFromList.length, list.size());
+ for (int i = 0; i < arrayResultFromList.length; i++) {
+ assertEquals(
+ arrayResultFromList[i].toBits(), list.getShort(i), "Index " + i +
" should match");
+ }
+ }
+ }
+
+ @Test
+ public void testStructWithFloat16ListFieldInXlang() {
+ Fory fory =
+ Fory.builder()
+ .withLanguage(Language.XLANG)
+ .withRefTracking(true)
+ .requireClassRegistration(false)
+ .build();
+ fory.register(StructWithFloat16List.class);
+
+ StructWithFloat16List obj = new StructWithFloat16List(buildFloat16List());
+ byte[] bytes = fory.serialize(obj);
+ StructWithFloat16List result = (StructWithFloat16List)
fory.deserialize(bytes);
+ assertFloat16ListBits(obj.f16List, result.f16List);
+ }
+
+ @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());
+
+ 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,
+ (short) 0x8000,
+ (short) 0x3c00,
+ (short) 0xbc00,
+ (short) 0x7bff,
+ (short) 0x0001,
+ (short) 0x0400,
+ (short) 0x7c00,
+ (short) 0xfc00,
+ (short) 0x7e00
+ };
+
+ 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));
+ }
+ }
+ }
+
+ private static Float16List buildFloat16List() {
+ return new Float16List(
+ new short[] {
+ Float16.ZERO.toBits(),
+ Float16.ONE.toBits(),
+ Float16.valueOf(2.5f).toBits(),
+ Float16.valueOf(-3.25f).toBits(),
+ Float16.NaN.toBits(),
+ Float16.POSITIVE_INFINITY.toBits(),
+ Float16.NEGATIVE_ZERO.toBits()
+ });
+ }
+
+ private static void assertFloat16ListBits(Float16List expected, Float16List
actual) {
+ assertEquals(expected.size(), actual.size());
+ for (int i = 0; i < expected.size(); i++) {
+ assertEquals(expected.getShort(i), actual.getShort(i), "Index " + i + "
should match");
+ }
+ }
+}
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 000000000..23f573e3d
--- /dev/null
+++ b/java/fory-core/src/test/java/org/apache/fory/type/Float16Test.java
@@ -0,0 +1,321 @@
+/*
+ * 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;
+
+public class Float16Test {
+
+ @Test
+ public void testSpecialValues() {
+ assertTrue(Float16.NaN.isNaN());
+ assertFalse(Float16.NaN.equalsValue(Float16.NaN));
+ assertFalse(Float16.NaN.isFinite());
+ assertFalse(Float16.NaN.isInfinite());
+
+ 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());
+
+ assertTrue(Float16.ZERO.isZero());
+ assertTrue(Float16.NEGATIVE_ZERO.isZero());
+ assertTrue(Float16.ZERO.equalsValue(Float16.NEGATIVE_ZERO));
+ assertFalse(Float16.ZERO.signbit());
+ assertTrue(Float16.NEGATIVE_ZERO.signbit());
+
+ 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());
+ 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);
+ }
+
+ @Test
+ public void testBoundaryValues() {
+ Float16 max = Float16.valueOf(65504.0f);
+ assertEquals(0x7bff, max.toBits());
+ assertEquals(65504.0f, max.floatValue(), 0.0f);
+ assertTrue(max.isNormal());
+ assertTrue(max.isFinite());
+
+ Float16 minNormal = Float16.valueOf((float) Math.pow(2, -14));
+ assertEquals(0x0400, minNormal.toBits());
+ assertTrue(minNormal.isNormal());
+ assertFalse(minNormal.isSubnormal());
+
+ Float16 minSubnormal = Float16.valueOf((float) Math.pow(2, -24));
+ assertEquals(0x0001, minSubnormal.toBits());
+ assertTrue(minSubnormal.isSubnormal());
+ assertFalse(minSubnormal.isNormal());
+ }
+
+ @Test
+ public void testSubnormalDecodeFromBits() {
+ assertEquals(Float16.fromBits((short) 0x0001).floatValue(), 5.9604645E-8f,
0.0f);
+ assertEquals(Float16.fromBits((short) 0x03FF).floatValue(), 6.097555E-5f,
0.0f);
+ }
+
+ @Test
+ public void testOverflowUnderflow() {
+ 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() {
+ Float16 rounded = Float16.valueOf(1.0009765625f);
+ assertTrue(Math.abs(rounded.floatValue() - 1.0009765625f) < 0.01f);
+ }
+
+ @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;
+
+ assertTrue(one.compareTo(two) < 0);
+ assertTrue(two.compareTo(one) > 0);
+ assertEquals(0, one.compareTo(Float16.ONE));
+
+ assertTrue(one.equalsValue(Float16.ONE));
+ assertFalse(nan.equalsValue(nan));
+ assertTrue(Float16.ZERO.equalsValue(Float16.NEGATIVE_ZERO));
+
+ 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 testCompareDistinguishesNanPayloads() {
+ Float16 nan1 = Float16.fromBits((short) 0x7e01);
+ Float16 nan2 = Float16.fromBits((short) 0x7e02);
+
+ assertNotEquals(nan1, nan2);
+ assertTrue(Float16.compare(nan1, nan2) < 0);
+ assertTrue(Float16.compare(nan2, nan1) > 0);
+ assertEquals(0, Float16.compare(nan1, nan1));
+ }
+
+ @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
+ public void testClassification() {
+ assertTrue(Float16.ONE.isNormal());
+ assertFalse(Float16.ZERO.isNormal());
+ assertFalse(Float16.NaN.isNormal());
+ assertFalse(Float16.POSITIVE_INFINITY.isNormal());
+
+ Float16 subnormal = Float16.valueOf((float) Math.pow(2, -24));
+ assertTrue(subnormal.isSubnormal());
+ assertFalse(Float16.ONE.isSubnormal());
+
+ assertTrue(Float16.ONE.isFinite());
+ assertTrue(Float16.ZERO.isFinite());
+ assertFalse(Float16.NaN.isFinite());
+ assertFalse(Float16.POSITIVE_INFINITY.isFinite());
+
+ 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() {
+ assertEquals(Float16.ONE.hashCode(), Float16.valueOf(1.0f).hashCode());
+
+ assertNotEquals(Float16.ONE.hashCode(), Float16.valueOf(2.0f).hashCode());
+ }
+
+ @Test
+ public void testNumberConversions() {
+ Float16 f16 = Float16.valueOf(3.14f);
+
+ assertEquals(3.14f, f16.floatValue(), 0.01f);
+
+ assertEquals(3.14, f16.doubleValue(), 0.01);
+
+ assertEquals(3, f16.intValue());
+
+ assertEquals(3L, f16.longValue());
+
+ assertEquals((byte) 3, f16.byteValue());
+
+ assertEquals((short) 3, f16.shortValue());
+ }
+
+ @Test
+ public void testAllBitPatterns() {
+ 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++;
+ assertTrue(h2.isNaN(), "NaN should remain NaN after round-trip");
+ } else {
+ 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)");
+
+ 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);
+ }
+}
diff --git a/java/fory-core/src/test/java/org/apache/fory/type/TypesTest.java
b/java/fory-core/src/test/java/org/apache/fory/type/TypesTest.java
new file mode 100644
index 000000000..b59b35ebf
--- /dev/null
+++ b/java/fory-core/src/test/java/org/apache/fory/type/TypesTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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.assertSame;
+
+import org.testng.annotations.Test;
+
+public class TypesTest {
+ @Test
+ public void testGetClassForFloatTypeIds() {
+ assertSame(Types.getClassForTypeId(Types.FLOAT8), Float.class);
+ assertSame(Types.getClassForTypeId(Types.FLOAT16), Float16.class);
+ assertSame(Types.getClassForTypeId(Types.BFLOAT16), Float.class);
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]