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)
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)
-
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).
-
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.
-
Cross-language golden tests
- Can be implemented in a future PR; must validate binary compatibility and NaN policy consistency.
Describe alternatives you've considered
- 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.
- 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.
- 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
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 typeFloat16(internally storingshortbits), plus exhaustive tests for conversion/rounding/NaN/subnormal semantics.Is your feature request related to a problem? Please describe
We want to use
float16in FDL to reduce payload size and memory footprint and to interoperate with other ecosystems (ML/graphics/etc.) where half precision is common. Currently Fory supportsfloat32/float64but notfloat16.The main blocker is Java:
float16) primitive.Float16) to appear in public APIs and generated code (no rawshortparameters).Describe the solution you'd like
1) FDL / Type System
float16.float16as a true primitive (likefloat32/float64), usable in:https://en.wikipedia.org/wiki/Half-precision_floating-point_format
2) Wire Format / Serialization Semantics
float16as 2 bytes representing the raw IEEE 754 binary16 bit pattern.3) Java Runtime (core requirement):
Float16strong type onlyProvide a public strong type
Float16. All runtime APIs must accept/returnFloat16only (noshortparameters in public APIs).3.1 Type definition
public final class Float16private final short bits;public static Float16 fromBits(short bits)public short toBits()(bit access is allowed, but callers still passFloat16to APIs)3.2 Conversions (IEEE 754 compliant)
public static Float16 fromFloat(float f)public float toFloat()3.3 Classification (IEEE-consistent)
All methods operate on
Float16:public boolean isNaN()public boolean isInfinite()and optionallypublic 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,roundToEvenImplementation rule for arithmetic (unless full half-FPU emulation is desired):
return Float16.fromFloat(this.toFloat() op other.toFloat());3.5 Comparisons
Provide IEEE-consistent comparisons as instance/static methods using
Float16only:public boolean equalsValue(Float16 other)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)public static int totalOrderCompare(Float16 a, Float16 b)(optional)Note:
Object.equals()/hashCode()should be defined carefully:equals()bitwise (strict bits equality), and provideequalsValue()for IEEE numeric equality.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 viaFloat.toString(toFloat()))public static Float16 parse(String s)(parse as float, then convert to half)Text/JSONmarshal/unmarshal helpers if Fory uses them4) Java Codegen requirement
float16must useFloat16(notshort).float16should useList<Float16>(or an existing generated container type), unless you have a specialized primitive container strategy for wrappers.float16should beMap<K, Float16>.5) Compiler / Reflection Integration
float16is treated as a primitive type.Float16as thefloat16primitive (distinct fromshort/int16).float16 <-> float32evolution is allowed, document conversion behavior/rounding; otherwise enforce strict matching.6) Tests (must be exhaustive)
Conversion tests (Java)
h = Float16.fromBits(bits);h2 = Float16.fromFloat(h.toFloat());Serializer/deserializer tests
Float16.toBits()).Cross-language golden tests
Describe alternatives you've considered
float16asfloatin Java and convert toFloat16only during serialization.shortin generated code and APIs, and only provide helper functions on bits.Float16everywhere.Additional context
#3099