Skip to content

[Java] add float16 support to java #3205

@chaokunyang

Description

@chaokunyang

Feature Request

Add full IEEE 754 half-precision floating point (float16, binary16) support to Fory/FDL, including a complete Java runtime implementation that uses a strong type Float16 (internally storing short bits), plus exhaustive tests for conversion/rounding/NaN/subnormal semantics.

Is your feature request related to a problem? Please describe

We want to use float16 in FDL to reduce payload size and memory footprint and to interoperate with other ecosystems (ML/graphics/etc.) where half precision is common. Currently Fory supports float32/float64 but not float16.

The main blocker is Java:

  • Java has no native IEEE 754 binary16 (float16) primitive.
  • We need exact IEEE 754 binary16 behavior and cross-language consistency.
  • Java has no operator overloading, so “float32-like usability” must be provided via explicit methods.
  • For type safety and clarity, we want only a single strong type (Float16) to appear in public APIs and generated code (no raw short parameters).

Describe the solution you'd like

1) FDL / Type System

  • Introduce a new primitive type: float16.
  • Treat float16 as a true primitive (like float32/float64), usable in:
    • message fields
    • repeated fields
    • map values (and optionally keys if numeric keys are allowed; if not allowed today, keep consistent)
    • unions (if primitives are allowed)
  • Document the exact definition: IEEE 754 binary16 (“half precision”) per:
    https://en.wikipedia.org/wiki/Half-precision_floating-point_format

2) Wire Format / Serialization Semantics

  • Encode float16 as 2 bytes representing the raw IEEE 754 binary16 bit pattern.
  • Define endianness exactly (match existing float32/float64 endianness rules).
  • NaN/Inf/±0/subnormal must round-trip correctly at the bit level.
    • If the framework canonicalizes NaNs today for float32/float64, specify whether float16 should:
      • preserve payload bits (preferred if feasible), or
      • canonicalize to a single quiet NaN pattern (acceptable but must be documented and consistent across languages).

3) Java Runtime (core requirement): Float16 strong type only

Provide a public strong type Float16. All runtime APIs must accept/return Float16 only (no short parameters in public APIs).

3.1 Type definition
  • Provide an immutable, compact value type:
    • public final class Float16
    • internal storage: private final short bits;
  • Construction should be controlled (private constructor), with explicit factories:
    • public static Float16 fromBits(short bits)
    • public short toBits() (bit access is allowed, but callers still pass Float16 to APIs)
3.2 Conversions (IEEE 754 compliant)
  • public static Float16 fromFloat(float f)
    • Convert float32 -> binary16 using IEEE 754 rules and round-to-nearest, ties-to-even.
    • Must correctly handle:
      • NaN (produce a NaN in half; preserve payload if feasible, otherwise canonicalize; ensure quiet NaN if required)
      • ±Inf
      • ±0 (preserve sign)
      • normalized values
      • subnormals (gradual underflow)
      • overflow -> ±Inf
      • underflow -> subnormal/±0
  • public float toFloat()
    • Convert binary16 -> float32 (exact for all half values).
3.3 Classification (IEEE-consistent)

All methods operate on Float16:

  • public boolean isNaN()
  • public boolean isInfinite() and optionally public boolean isInfinite(int sign) (sign: +1/-1/0)
  • public boolean isZero() (treat +0/-0 as zero)
  • public boolean signbit()
  • public boolean isSubnormal()
  • public boolean isNormal()
  • public boolean isFinite()
3.4 Arithmetic (explicit methods)

Since Java has no operator overloading, provide instance methods returning Float16:

  • public Float16 add(Float16 other)
  • public Float16 sub(Float16 other)
  • public Float16 mul(Float16 other)
  • public Float16 div(Float16 other)
  • public Float16 neg()
  • public Float16 abs()

Optional math parity if needed by users:

  • sqrt, min, max, copySign, floor, ceil, trunc, round, roundToEven

Implementation rule for arithmetic (unless full half-FPU emulation is desired):

  • compute in float32 and round back to half each operation:
    • return Float16.fromFloat(this.toFloat() op other.toFloat());
3.5 Comparisons

Provide IEEE-consistent comparisons as instance/static methods using Float16 only:

  • public boolean equalsValue(Float16 other)
    • NaN != NaN
    • +0 == -0
  • public boolean lessThan(Float16 other) (if NaN involved => false)
  • public boolean lessThanOrEqual(Float16 other)
  • public boolean greaterThan(Float16 other)
  • public boolean greaterThanOrEqual(Float16 other)
  • public static int compare(Float16 a, Float16 b)
    • Define NaN behavior explicitly (unordered). If a total ordering is needed, add:
      • public static int totalOrderCompare(Float16 a, Float16 b) (optional)

Note: Object.equals() / hashCode() should be defined carefully:

  • Option A: Make equals() bitwise (strict bits equality), and provide equalsValue() for IEEE numeric equality.
  • Option B: Make equals() numeric (IEEE rules, NaN != NaN, +0 == -0), but then hashing/collections semantics become tricky.
    Please choose one and document it (recommendation: bitwise equals() + separate numeric methods).
3.6 Formatting / parsing / interop
  • @Override public String toString() (format via Float.toString(toFloat()))
  • Optional:
    • public static Float16 parse(String s) (parse as float, then convert to half)
    • Text/JSON marshal/unmarshal helpers if Fory uses them

4) Java Codegen requirement

  • Generated Java fields for float16 must use Float16 (not short).
  • Repeated float16 should use List<Float16> (or an existing generated container type), unless you have a specialized primitive container strategy for wrappers.
  • Map values for float16 should be Map<K, Float16>.

5) Compiler / Reflection Integration

  • Update the FDL parser/type system so float16 is treated as a primitive type.
  • Ensure reflection/dynamic serialization recognizes Java Float16 as the float16 primitive (distinct from short / int16).
  • Clarify schema evolution:
    • If float16 <-> float32 evolution is allowed, document conversion behavior/rounding; otherwise enforce strict matching.

6) Tests (must be exhaustive)

  1. Conversion tests (Java)

    • ±0, ±Inf, NaN
    • max finite 65504
    • min normal 2^-14
    • min subnormal 2^-24
    • values around rounding boundaries
    • explicit ties-to-even cases (inputs exactly halfway between two representable half values)
    • overflow -> Inf, underflow -> subnormal/0
    • Optional stress: iterate all 65536 half bit patterns:
      • h = Float16.fromBits(bits);
      • h2 = Float16.fromFloat(h.toFloat());
      • Verify bit preservation for all non-NaN values; for NaN validate the chosen policy (preserve payload vs canonicalize).
  2. Serializer/deserializer tests

    • Ensure wire output matches expected 16-bit patterns for known values (via Float16.toBits()).
    • Round-trip for messages containing float16 fields, repeated float16 fields, maps with float16 values, optional fields, etc.
  3. Cross-language golden tests

    • Can be implemented in a future PR; must validate binary compatibility and NaN policy consistency.

Describe alternatives you've considered

  1. Store float16 as float in Java and convert to Float16 only during serialization.
  • Rejected: changes in-memory footprint, delays rounding to serialization time, and can produce cross-language semantic differences.
  1. Expose raw short in generated code and APIs, and only provide helper functions on bits.
  • Rejected: loses type safety and makes user code error-prone; we want Float16 everywhere.
  1. Use third-party float16 libraries.
  • Possible, but we prefer a first-party minimal implementation to guarantee exact IEEE behavior, rounding mode, and avoid extra dependencies.

Additional context

#3099

Metadata

Metadata

Assignees

Labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions