Skip to content

feat(java): add float support#3254

Open
mengnankkkk wants to merge 24 commits intoapache:mainfrom
mengnankkkk:hotfix/add_float16
Open

feat(java): add float support#3254
mengnankkkk wants to merge 24 commits intoapache:mainfrom
mengnankkkk:hotfix/add_float16

Conversation

@mengnankkkk
Copy link
Contributor

@mengnankkkk mengnankkkk commented Feb 3, 2026

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 <chaokunyang@apache.org>
@chaokunyang
Copy link
Collaborator

Could you also address those issues?

  1. XLANG support for new Float16 types is broken at runtime.
    Float16/Float16List serializers are added (PrimitiveSerializers.java, PrimitiveListSerializers.java), but internal xlang type registration is missing in resolver bootstrap (ClassResolver.java). In xlang mode it falls back to unsupported-class path and fails at deserialize time.

    • java/fory-core/src/main/java/org/apache/fory/serializer/PrimitiveSerializers.java:381
    • java/fory-core/src/main/java/org/apache/fory/serializer/collection/PrimitiveListSerializers.java:595
    • java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java:262
    • java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java:295
    • java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java:1305
  2. Float16 subnormal decode is numerically wrong.
    In float16BitsToFloat, subnormal conversion uses an incorrect shift/exponent formula.

    • java/fory-core/src/main/java/org/apache/fory/type/Float16.java:169
    • java/fory-core/src/main/java/org/apache/fory/type/Float16.java:173
      Repro: Float16.fromBits((short)0x0001).floatValue() returns 1.7881393E-7, expected 5.9604645E-8 (2^-24).
  3. Float16[] serializer crashes on legal arrays containing null.
    The array fast path dereferences element directly (value[i].toBits()), so new Float16[]{Float16.ONE, null} throws NPE.

    • java/fory-core/src/main/java/org/apache/fory/serializer/ArraySerializers.java:901
  4. Type-id mapping regression for existing float kinds.
    Types.getClassForTypeId now maps FLOAT8 to Float16, and BFLOAT16 mapping was removed (falls through to null).

    • java/fory-core/src/main/java/org/apache/fory/type/Types.java:450
    • java/fory-core/src/main/java/org/apache/fory/type/Types.java:452
  5. Generated Java equals/hashCode contract can break for float16.
    Generator uses equalsValue for FLOAT16 equality, but default hash path remains Objects.hash(...). equalsValue treats +0 and -0 as equal, while Float16.hashCode is bit-based.

    • compiler/fory_compiler/generators/java.py:1384
    • compiler/fory_compiler/generators/java.py:1467
    • java/fory-core/src/main/java/org/apache/fory/type/Float16.java:270
    • java/fory-core/src/main/java/org/apache/fory/type/Float16.java:334

private void writeNullable(MemoryBuffer buffer, Float16[] value, int length) {
int bitmapSize = (length + 7) >>> 3;
int payloadSize = Integer.BYTES + bitmapSize + length * 2;
int encodedSize = (payloadSize << 1) | NULLABLE_ENCODING_FLAG;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes the FLOAT16_ARRAY wire layout when nulls are present, which will break cross-language readers that expect the normal length+payload format for type 53.

In xlang mode, other runtimes decode FLOAT16_ARRAY as plain [byte_length][2-byte elements]. Here we switch to an alternate encoded-size scheme for nullable arrays, so non-Java runtimes will misread the payload. Could we keep FLOAT16_ARRAY encoding stable and handle nullable cases another way (for example, fallback to LIST encoding when null is present)?

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;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since FLOAT16 is now a basic dispatch id, FieldSkipper also needs a skip branch for it.

Right now FieldSkipper has no DispatchId.FLOAT16 case. In compatibility reads, skipping an unknown Float16 field will throw Unexpected basic dispatchId instead of advancing by 2 bytes. Please add FLOAT16 to the 2-byte skip branch.

resolver.registerInternalSerializer(URI.class, new URISerializer(fory));
resolver.registerInternalSerializer(Pattern.class, new RegexSerializer(fory));
resolver.registerInternalSerializer(UUID.class, new UUIDSerializer(fory));
resolver.registerInternalSerializer(Float16.class, new Float16Serializer(fory));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Float16 gets registered twice (primitive serializer path and general serializers path).

This overrides the earlier registration and makes it harder to reason about which serializer is intended. It would be cleaner to keep a single registration point for Float16.

if kind in (PrimitiveKind.FLOAT32,):
if kind in (PrimitiveKind.FLOAT16,):
comparisons.append(
f"({field_name} == null ? that.{field_name} == null : (that.{field_name} != null && {field_name}.equalsValue(that.{field_name})))"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated equals/hashCode for float16 now use numeric semantics, but Float16.equals/hashCode are bitwise.

With this change, +0 and -0 compare equal in generated messages, and NaN is not equal to NaN, while Float16 itself uses raw-bit equality. That mismatch is surprising in user code. Can we align generated message behavior with Float16.equals/hashCode (or make the rule explicit and consistent across runtimes)?

return true;
}

public boolean add(float value) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Float16List primitive methods still allocate Float16 objects on the hot path.

add(float), set(float), and getFloat(int) all go through Float16.valueOf/fromBits, so these paths allocate even though this is a primitive-style container. A static bits conversion helper would keep these methods allocation-free.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Java] add float16 support to java

2 participants