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 db529f19b feat(dart): add float16 to dart (#3336)
db529f19b is described below

commit db529f19b5212befad23f45643448f79c308bb9a
Author: Ayush Kumar <[email protected]>
AuthorDate: Wed Feb 18 21:01:25 2026 +0530

    feat(dart): add float16 to dart (#3336)
    
    ## Why?
    
    As requested in Issue #3209, Dart implementation was missing support for
    IEEE 754 half-precision floating-point (Float16) types that are already
    available in other Fory language implementations (Rust, Go, C++,
    Python). This PR adds complete Float16 support for Dart.
    
    ## What does this PR do?
    
    This PR implements schema-based support for IEEE 754 Float16 types in
    the Fory Dart library:
    
    ### Added Types
    
    - `float16`
    
    ### Changes Made
    
    #### 1. lib/src/datatype/float16.dart
    Implemented the `Float16` class which wraps a 16-bit integer and handles
    IEEE 754 conversions (handling rounding, subnormals, signed zeros,
    NaN/Infinity):
    
    ```dart
    final class Float16 extends FixedNum {
      final int _bits;
      // ...
      factory Float16(num value) => Float16.fromDouble(value.toDouble());
      double toDouble() => _bitsToDouble(_bits);
    }
    ```
    
    #### 2. lib/src/memory/
    Added read/write support for 16-bit floats in `ByteReader` and
    `ByteWriter`:
    
    ```dart
    void writeFloat16(Float16 value);
    Float16 readFloat16();
    ```
    
    #### 3. lib/src/serializer/
    Registered `Float16Serializer` to handle serialization of `Float16`
    types in `PrimitiveTypeSerializer`:
    
    ```dart
    typeToTypeInfo[Float16]!.serializer = 
Float16Serializer.cache.getSerializer(conf);
    ```
    
    #### 4. lib/src/const/dart_type.dart
    Added `FLOAT16` to `DartTypeEnum` for type inference and registry.
    
    #### 5. test/cross_lang_test/
    Updated cross-language tests to fix lint issues and build errors:
    - Renamed enum constants in `xlang_test_defs.dart` and
    `xlang_test_main.dart` to follow Dart's `lowerCamelCase` convention
    (e.g., `VALUE_A` -> `valueA`).
    
    ### Usage Example
    
    ```dart
    import 'package:fory/src/datatype/float16.dart';
    import 'package:fory/src/memory/byte_writer.dart';
    
    var val = Float16(1.5);
    print(val.toDouble())
    
    // Handling limits and edge cases
    var max = Float16(65504.0);
    var inf = Float16(65505.0);
    var subnormal = Float16(0.0000000596046);
    
    // Serialization
    var bw = ByteWriter();
    bw.writeFloat16(val);
    ```
    
    ## Related issues
    
    - Closes #3209
    
    ## Does this PR introduce any user-facing change?
    
    - [x] **Does this PR introduce any public API change?**
      - **Yes**: Adds `Float16` class and corresponding `NumType.float16`.
      - **Yes**: Adds `readFloat16` / `writeFloat16` to memory interfaces.
      - **Yes**: Exports `Float16` for user consumption.
    
    - [x] **Does this PR introduce any binary protocol compatibility
    change?**
      - **Yes**: Adds support for TypeId 17 (Float16).
    - **Cross-language compatible**: Uses the same TypeId values as other
    Fory implementations.
    
    ## Benchmark
    
    N/A
---
 .../fory-test/lib/entity/xlang_test_models.dart    |  27 +-
 .../test/cross_lang_test/xlang_test_main.dart      |   4 +-
 .../fory-test/test/datatype_test/float16_test.dart | 176 +++++++++
 dart/packages/fory/lib/fory.dart                   |   1 +
 dart/packages/fory/lib/src/const/dart_type.dart    |   3 +
 dart/packages/fory/lib/src/datatype/float16.dart   | 394 ++++++++++++++-------
 .../fory/lib/src/datatype/fory_fixed_num.dart      |   2 +
 dart/packages/fory/lib/src/memory/byte_reader.dart |   4 +
 .../fory/lib/src/memory/byte_reader_impl.dart      |   8 +
 dart/packages/fory/lib/src/memory/byte_writer.dart |   2 +
 .../fory/lib/src/memory/byte_writer_impl.dart      |   8 +
 .../src/serializer/primitive_type_serializer.dart  |  37 ++
 .../fory/lib/src/serializer/serializer_pool.dart   |   3 +
 13 files changed, 533 insertions(+), 136 deletions(-)

diff --git a/dart/packages/fory-test/lib/entity/xlang_test_models.dart 
b/dart/packages/fory-test/lib/entity/xlang_test_models.dart
index 6f9aef9b9..41f67225d 100644
--- a/dart/packages/fory-test/lib/entity/xlang_test_models.dart
+++ b/dart/packages/fory-test/lib/entity/xlang_test_models.dart
@@ -18,7 +18,6 @@
  */
 
 import 'package:fory/fory.dart';
-import 'package:fory/src/resolver/spec_lookup.dart';
 
 part '../generated/xlang_test_models.g.dart';
 
@@ -87,17 +86,17 @@ void registerXlangEnum(
 
 @foryEnum
 enum TestEnum {
-  VALUE_A,
-  VALUE_B,
-  VALUE_C,
+  valueA,
+  valueB,
+  valueC,
 }
 
 @foryClass
 class TwoEnumFieldStructEvolution {
-  TestEnum f1 = TestEnum.VALUE_A;
+  TestEnum f1 = TestEnum.valueA;
 
   @ForyKey(includeFromFory: false)
-  TestEnum f2 = TestEnum.VALUE_A;
+  TestEnum f2 = TestEnum.valueA;
 }
 
 @foryClass
@@ -157,10 +156,10 @@ class NullableComprehensiveCompatible {
 
 @foryEnum
 enum Color {
-  Green,
-  Red,
-  Blue,
-  White,
+  green,
+  red,
+  blue,
+  white,
 }
 
 @foryClass
@@ -174,7 +173,7 @@ class SimpleStruct {
   Int32 f2 = Int32(0);
   Item f3 = Item();
   String f4 = '';
-  Color f5 = Color.Green;
+  Color f5 = Color.green;
   List<String> f6 = <String>[];
   Int32 f7 = Int32(0);
   Int32 f8 = Int32(0);
@@ -221,13 +220,13 @@ class TwoStringFieldStruct {
 
 @foryClass
 class OneEnumFieldStruct {
-  TestEnum f1 = TestEnum.VALUE_A;
+  TestEnum f1 = TestEnum.valueA;
 }
 
 @foryClass
 class TwoEnumFieldStruct {
-  TestEnum f1 = TestEnum.VALUE_A;
-  TestEnum f2 = TestEnum.VALUE_A;
+  TestEnum f1 = TestEnum.valueA;
+  TestEnum f2 = TestEnum.valueA;
 }
 
 @foryClass
diff --git a/dart/packages/fory-test/test/cross_lang_test/xlang_test_main.dart 
b/dart/packages/fory-test/test/cross_lang_test/xlang_test_main.dart
index 67733af6a..26c07a2aa 100644
--- a/dart/packages/fory-test/test/cross_lang_test/xlang_test_main.dart
+++ b/dart/packages/fory-test/test/cross_lang_test/xlang_test_main.dart
@@ -64,8 +64,8 @@ void _runEnumSchemaEvolutionCompatibleReverse() {
   registerXlangStruct(fory, TwoEnumFieldStructEvolution, typeId: 211);
   final TwoEnumFieldStructEvolution obj =
       fory.deserialize(data) as TwoEnumFieldStructEvolution;
-  if (obj.f1 != TestEnum.VALUE_C) {
-    throw StateError('Expected f1=VALUE_C, got ${obj.f1}');
+  if (obj.f1 != TestEnum.valueC) {
+    throw StateError('Expected f1=valueC, got ${obj.f1}');
   }
   _writeFile(dataFile, fory.serialize(obj));
 }
diff --git a/dart/packages/fory-test/test/datatype_test/float16_test.dart 
b/dart/packages/fory-test/test/datatype_test/float16_test.dart
new file mode 100644
index 000000000..b96cffffe
--- /dev/null
+++ b/dart/packages/fory-test/test/datatype_test/float16_test.dart
@@ -0,0 +1,176 @@
+/*
+ * 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.
+ */
+
+// @Skip()
+import 'dart:math' as math;
+import 'package:test/test.dart';
+import 'package:fory/src/datatype/float16.dart';
+import 'package:fory/src/memory/byte_writer.dart';
+import 'package:fory/src/memory/byte_reader.dart';
+
+void main() {
+  group('Float16 Conversion', () {
+    test('Zero', () {
+      expect(Float16.positiveZero.toDouble(), 0.0);
+      expect(Float16.negativeZero.toDouble(), -0.0);
+      expect(Float16.fromDouble(0.0).toBits(), 0x0000);
+      expect(Float16.fromDouble(-0.0).toBits(), 0x8000);
+      
+      // Verify -0.0 vs 0.0 distinction
+      expect(Float16.fromDouble(-0.0).isNegative, true);
+      expect(Float16.fromDouble(0.0).isNegative, false);
+    });
+
+    test('Infinity', () {
+      expect(Float16.positiveInfinity.toDouble(), double.infinity);
+      expect(Float16.negativeInfinity.toDouble(), double.negativeInfinity);
+      expect(Float16.fromDouble(double.infinity).toBits(), 0x7C00);
+      expect(Float16.fromDouble(double.negativeInfinity).toBits(), 0xFC00);
+    });
+
+    test('NaN', () {
+      expect(Float16.nan.toDouble().isNaN, true);
+      expect(Float16.fromDouble(double.nan).isNaN, true);
+      // Canonical NaN
+      expect(Float16.fromDouble(double.nan).toBits(), 0x7E00);
+    });
+
+    test('Exact values', () {
+      expect(Float16(1.0).toDouble(), 1.0);
+      expect(Float16(-1.0).toDouble(), -1.0);
+      expect(Float16(1.5).toDouble(), 1.5);
+      expect(Float16(0.5).toDouble(), 0.5);
+      expect(Float16(65504.0).toDouble(), 65504.0); // Max Value
+    });
+
+    test('Rounding', () {
+      // 1.0 is 0x3C00. Next is 1 + 2^-10 = 1.0009765625 (0x3C01)
+      // Halfway is 1.00048828125
+      
+      // Round down
+      expect(Float16.fromDouble(1.0004).toDouble(), 1.0);
+      
+      // Round up
+      expect(Float16.fromDouble(1.0006).toDouble(), 1.0009765625);
+      
+      // Tie to even (1.0 is even (last bit 0))
+      // 1.0 + half_epsilon = 1.00048828125
+      // 3C00 is even. 3C01 is odd. 
+      // If result is exactly halfway, pick even.
+      // 1.0 corresponds to stored bits ...00
+      
+      double one = 1.0;
+      double next = 1.0009765625;
+      double mid = (one + next) / 2;
+      
+      // 1.0 (0x3C00) is even. 
+      expect(Float16.fromDouble(mid).toBits(), 0x3C00); // 1.0
+      
+      // 1.0009765625 (0x3C01) is odd.
+      // Next is 1.001953125 (0x3C02) - even.
+      // Mid between 3C01 and 3C02
+      double val1 = 1.0009765625;
+      double val2 = 1.001953125;
+      double mid2 = (val1 + val2) / 2;
+      expect(Float16.fromDouble(mid2).toBits(), 0x3C02); // Round to even (up)
+    });
+
+    test('Subnormal', () {
+      // Min subnormal: 2^-24 approx 5.96e-8
+      double minSub = math.pow(2, -24).toDouble();
+      expect(Float16.fromDouble(minSub).toDouble(), minSub);
+      expect(Float16.fromDouble(minSub).toBits(), 0x0001);
+      
+      // Below min subnormal -> 0
+      expect(Float16.fromDouble(minSub / 2.1).toBits(), 0x0000);
+      // Round up to min subnormal
+      expect(Float16.fromDouble(minSub * 0.6).toBits(), 0x0001);
+      
+      // Max subnormal: 0x03FF = (1 - 2^-10) * 2^-14
+      int maxSubBits = 0x03FF;
+      Float16 maxSub = Float16.fromBits(maxSubBits);
+      expect(maxSub.isSubnormal, true);
+      expect(Float16.fromDouble(maxSub.toDouble()).toBits(), maxSubBits);
+    });
+  });
+
+  group('Float16 Arithmetic', () {
+    test('Add', () {
+      expect((Float16(1.0) + Float16(2.0)).toDouble(), 3.0);
+      expect((Float16(1.0) + 2.0).toDouble(), 3.0);
+    });
+    test('Sub', () {
+      expect((Float16(3.0) - Float16(1.0)).toDouble(), 2.0);
+    });
+    test('Mul', () {
+      expect((Float16(2.0) * Float16(3.0)).toDouble(), 6.0);
+    });
+    test('Div', () {
+      expect((Float16(1.0) / Float16(2.0)), 0.5);
+    });
+    test('Neg', () {
+      expect((-Float16(1.0)).toDouble(), -1.0);
+    });
+    test('Abs', () {
+      expect(Float16(-1.0).abs().toDouble(), 1.0);
+    });
+  });
+  
+  group('Float16 Comparision', () {
+    test('Equality', () {
+       expect(Float16(1.0) == Float16(1.0), true);
+       expect(Float16(1.0) == Float16(2.0), false);
+       expect(Float16.nan == Float16.nan, true); // Bitwise equality
+    });
+    
+    test('Less/Greater', () {
+       expect(Float16(1.0) < Float16(2.0), true);
+       expect(Float16(2.0) > Float16(1.0), true);
+    });
+  });
+
+  group('Serialization', () {
+    test('RoundTrip', () {
+      var bw = ByteWriter();
+      var f1 = Float16(1.234375); // 0x3D3C approx
+      var f2 = Float16(-100.0);
+      var f3 = Float16.nan;
+      var f4 = Float16.positiveInfinity;
+      
+      bw.writeFloat16(f1);
+      bw.writeFloat16(f2);
+      bw.writeFloat16(f3);
+      bw.writeFloat16(f4);
+      
+      var bytes = bw.toBytes();
+      var br = ByteReader.forBytes(bytes);
+      
+      var r1 = br.readFloat16();
+      var r2 = br.readFloat16();
+      var r3 = br.readFloat16();
+      var r4 = br.readFloat16();
+      
+      expect(r1.toDouble(), f1.toDouble());
+      expect(r2.toDouble(), f2.toDouble());
+      expect(r3.isNaN, true);
+      expect(r4.isInfinite, true);
+      expect(r4.sign, 1);
+    });
+  });
+}
diff --git a/dart/packages/fory/lib/fory.dart b/dart/packages/fory/lib/fory.dart
index 9f795ad15..ba5526ca2 100644
--- a/dart/packages/fory/lib/fory.dart
+++ b/dart/packages/fory/lib/fory.dart
@@ -40,6 +40,7 @@ export 'src/config/fory_config.dart';
 
 // Constants
 export 'src/const/types.dart';
+export 'src/resolver/spec_lookup.dart';
 
 // User-related
 export 'src/fory_type_provider.dart';
diff --git a/dart/packages/fory/lib/src/const/dart_type.dart 
b/dart/packages/fory/lib/src/const/dart_type.dart
index c7fafd36b..85c37fcf1 100644
--- a/dart/packages/fory/lib/src/const/dart_type.dart
+++ b/dart/packages/fory/lib/src/const/dart_type.dart
@@ -21,6 +21,7 @@ import 'dart:collection';
 import 'dart:typed_data';
 import 'package:collection/collection.dart' show BoolList;
 import 'package:decimal/decimal.dart';
+import 'package:fory/src/datatype/float16.dart';
 import 'package:fory/src/datatype/float32.dart';
 import 'package:fory/src/datatype/int16.dart';
 import 'package:fory/src/datatype/int32.dart';
@@ -57,6 +58,8 @@ enum DartTypeEnum {
   INT(int, true, 'int', 'dart', 'core', ObjType.INT64, true, 'dart:core@int'),
   FLOAT32(Float32, true, 'Float32', 'package', 
'fory/src/datatype/float32.dart',
       ObjType.FLOAT32, true, 'dart:core@Float32'),
+  FLOAT16(Float16, true, 'Float16', 'package', 
'fory/src/datatype/float16.dart',
+        ObjType.FLOAT16, true, 'dart:core@Float16'),
   DOUBLE(double, true, 'double', 'dart', 'core', ObjType.FLOAT64, true,
       'dart:core@double'),
   STRING(String, true, 'String', 'dart', 'core', ObjType.STRING, true,
diff --git a/dart/packages/fory/lib/src/datatype/float16.dart 
b/dart/packages/fory/lib/src/datatype/float16.dart
index 501e04aea..7fbf4145e 100644
--- a/dart/packages/fory/lib/src/datatype/float16.dart
+++ b/dart/packages/fory/lib/src/datatype/float16.dart
@@ -17,7 +17,8 @@
  * under the License.
  */
 
-import 'dart:math' as math show pow, log, ln2;
+import 'dart:math' as math;
+import 'dart:typed_data';
 
 import 'float32.dart' show Float32;
 import 'fory_fixed_num.dart';
@@ -26,150 +27,303 @@ import 'int32.dart' show Int32;
 import 'int8.dart' show Int8;
 
 /// Float16: 16-bit floating point (IEEE 754 half precision)
+/// Wraps a 16-bit integer representing the IEEE 754 binary16 format.
 final class Float16 extends FixedNum {
-  static const double MIN_VALUE = -65504;
-  static const double MAX_VALUE = 65504;
-  static const double EPSILON = 0.0009765625; // 2^-10
-
-  static Float16 get maxValue => Float16(MAX_VALUE);
-
-  static Float16 get minValue => Float16(MIN_VALUE);
-
-  final double _value;
-
-  Float16(num input) : _value = _convert(input);
-
-  static double _convert(num value) {
-    // This is a proper IEEE 754 half-precision implementation
-    double val = value.toDouble();
-    if (val.isNaN) return double.nan;
-    if (val.isInfinite) return val;
-
-    // Handle zeros
-    if (val == 0.0) return val.sign < 0 ? -0.0 : 0.0;
-
-    // Clamp to float16 range
-    val = val.clamp(-MAX_VALUE, MAX_VALUE).toDouble();
-
-    // Implementing IEEE 754 half-precision conversion
-    int bits;
-    if (val.abs() < EPSILON) {
-      // Handle subnormal numbers
-      bits =
-          ((val < 0 ? 1 : 0) << 15) | ((val.abs() / EPSILON).round() & 0x3FF);
-    } else {
-      // Extract components from double
-      int sign = val < 0 ? 1 : 0;
-      double absVal = val.abs();
-      int exp = (math.log(absVal) / math.ln2).floor();
-      double frac = absVal / math.pow(2, exp) - 1.0;
-
-      // Adjust for 5-bit exponent
-      exp += 15; // Bias
-      exp = exp.clamp(0, 31);
-
-      // Convert to 10-bit fraction
-      int fracBits = (frac * 1024).round() & 0x3FF;
-
-      // Combine into 16 bits
-      bits = (sign << 15) | (exp << 10) | fracBits;
-    }
-
-    // Convert back to double (simulates float16 storage and retrieval)
-    // In a real-world implementation, you would use binary data directly
-    int sign = (bits >> 15) & 0x1;
-    int exp = (bits >> 10) & 0x1F;
-    int frac = bits & 0x3FF;
-
-    if (exp == 0) {
-      // Subnormal numbers
-      return (sign == 0 ? 1.0 : -1.0) * frac * EPSILON;
-    } else if (exp == 31) {
-      // Infinity or NaN
-      return frac == 0
-          ? (sign == 0 ? double.infinity : double.negativeInfinity)
-          : double.nan;
-    }
-
-    // Normal numbers
-    double result = (sign == 0 ? 1.0 : -1.0) *
-        math.pow(2, exp - 15) *
-        (1.0 + frac / 1024.0);
-    return result;
+  /// The raw 16-bit integer storage.
+  final int _bits;
+
+  /// Masks the bits to ensure 16-bit range.
+  static const int _mask = 0xFFFF;
+
+  // --- Constants ---
+  static const int _exponentBias = 15;
+  static const int _maxExponent = 31; // 2^5 - 1
+  
+  // Bit placeholders
+  static const int _signMask = 0x8000;
+  static const int _exponentMask = 0x7C00;
+  static const int _mantissaMask = 0x03FF;
+
+  // --- Public Constants ---
+  static const Float16 positiveZero = Float16.fromBits(0x0000);
+  static const Float16 negativeZero = Float16.fromBits(0x8000);
+  static const Float16 positiveInfinity = Float16.fromBits(0x7C00);
+  static const Float16 negativeInfinity = Float16.fromBits(0xFC00);
+  static const Float16 nan = Float16.fromBits(0x7E00);
+
+  static const double minValue = 6.103515625e-05;
+  static const double minSubnormal = 5.960464477539063e-08;
+  static const double maxValue = 65504.0;
+  static const double epsilon = 0.0009765625;
+
+  /// Constructs a [Float16] from a number. 
+  /// Delegates to [Float16.fromDouble].
+  factory Float16(num value) => Float16.fromDouble(value.toDouble());
+
+  /// Constructs a [Float16] directly from raw bits.
+  const Float16.fromBits(int bits) : _bits = bits & _mask, super();
+
+  /// Converts a [double] to [Float16] using IEEE 754 half-precision rules 
(round-to-nearest-even).
+  factory Float16.fromDouble(double value) {
+     if (value.isNaN) {
+       return Float16.nan;
+     }
+
+     final int doubleBits = _doubleToBits(value);
+     final int sign = (doubleBits >> 63) & 0x1;
+     final int rawExp = (doubleBits >> 52) & 0x7FF;
+     final int rawMantissa = doubleBits & 0xFFFFFFFFFFFFF;
+
+     // 1. Convert double exp to float16 exp
+     // Double bias: 1023, Float16 bias: 15.
+     // Shift: 1023 - 15 = 1008.
+     int exp = rawExp - 1023 + _exponentBias;
+     
+     if (exp >= _maxExponent) {
+       // Overflow or Infinity
+        return rawExp == 2047 && rawMantissa != 0
+            ? Float16.nan // Should have been caught by value.isNaN check 
above usually
+            : (sign == 0 ? Float16.positiveInfinity : 
Float16.negativeInfinity);
+     } else if (exp <= 0) {
+       // Subnormal or Zero
+       if (exp < -10) {
+         // Too small for subnormal -> signed zero
+         return sign == 0 ? Float16.positiveZero : Float16.negativeZero;
+       }
+       
+       // Convert to subnormal
+       return Float16.fromBits(_doubleToFloat16Bits(value));
+     } else {
+       // Normalized
+       return Float16.fromBits(_doubleToFloat16Bits(value));
+     }
   }
 
-  @override
-  double get value => _value;
+  static int _doubleToBits(double value) {
+    var bdata = ByteData(8);
+    bdata.setFloat64(0, value, Endian.little);
+    return bdata.getUint64(0, Endian.little);
+  }
+  
+  static double _bitsToDouble(int bits) {
+      // Logic for converting float16 bits to double
+      int s = (bits >> 15) & 0x0001;
+      int e = (bits >> 10) & 0x001f;
+      int m = bits & 0x03ff;
+      
+      if (e == 0) {
+          if (m == 0) {
+              // signed zero
+              return s == 1 ? -0.0 : 0.0;
+          } else {
+              // subnormal
+              return (s == 1 ? -1 : 1) * math.pow(2, -14) * (m / 1024.0);
+          }
+      } else if (e == 31) {
+          if (m == 0) {
+              return s == 1 ? double.negativeInfinity : double.infinity;
+          } else {
+              return double.nan;
+          }
+      } else {
+          // normalized
+          return (s == 1 ? -1 : 1) * math.pow(2, e - 15) * (1 + m / 1024.0);
+      }
+  }
+  
+  /// Helper to convert double to float16 bits with proper rounding
+  static int _doubleToFloat16Bits(double val) {
+      if (val.isNaN) return 0x7E00; // Canonical NaN
+      
+      // Check for zero
+      if (val == 0.0) {
+          return (1 / val) == double.negativeInfinity ? 0x8000 : 0x0000; 
+      }
+      
+      int fbits = _doubleToBits(val);
+      int sign = (fbits >> 63) & 1;
+      int exp = (fbits >> 52) & 0x7FF; // 11 bits
+      
+      // Bias adjustment
+      int newExp = exp - 1023 + 15;
+      
+      // Inf / NaN handled?
+      if (exp == 2047) {
+          // Infinity only (NaN handled at start)
+          return (sign << 15) | 0x7C00;
+      }
+      
+      if (newExp >= 31) {
+           // Overflow to Infinity
+           return (sign << 15) | 0x7C00;
+      }
+      
+      if (newExp <= 0) {
+          // Possible subnormal or zero
+          if (newExp < -10) {
+             // Underflow to zero
+             return sign << 15;
+          }
+          
+          double absVal = val.abs();
+          if (absVal < minValue) {
+              // It is subnormal
+              // val = 0.m * 2^-14
+              // m = val / 2^-14
+              double m = absVal / math.pow(2, -14);
+              // m is now in [0, 1). 
+              // We want 10 bits of it.
+              // bits = round(m * 1024)
+              int mBits = (m * 1024).round();
+              return (sign << 15) | mBits;
+          }
+      }
+      
+      // Normalized
+      // We need to round the mantissa.
+      
+      int fullMantissa = (fbits & 0xFFFFFFFFFFFFF);
+      // We want to reduce 52 bits to 10 bits.
+      // So we shift right by 42.
+      // The bits we shift out are the last 42 bits.
+      // The 42nd bit (from right, 0-indexed) is the round bit.
+      // The bits 0-41 are safety/sticky bits.
+      
+      int m10 = fullMantissa >> 42;
+      int guard = (fullMantissa >> 41) & 1;
+      int sticky = (fullMantissa & 0x1FFFFFFFFFF) != 0 ? 1 : 0;
+      
+      if (guard == 1) {
+          if (sticky == 1 || (m10 & 1) == 1) {
+              m10++;
+          }
+      }
+      
+      if (m10 >= 1024) {
+          // Mantissa overflowed, increment exponent
+          m10 = 0;
+          newExp++;
+          if (newExp >= 31) {
+              return (sign << 15) | 0x7C00; // Inf
+          }
+      }
+      
+      return (sign << 15) | (newExp << 10) | m10;
+  }
+
+  /// Returns the raw 16-bit integer.
+  int toBits() => _bits;
 
-  // Operators
+  /// Returns the value as a [double].
+  double toDouble() => _bitsToDouble(_bits);
+
+  /// Returns the underlying values as a [double].
+  @override
+  double get value => toDouble();
+
+  // --- Classification ---
+  bool get isNaN => (_bits & _exponentMask) == _exponentMask && (_bits & 
_mantissaMask) != 0;
+  
+  bool get isInfinite => (_bits & _exponentMask) == _exponentMask && (_bits & 
_mantissaMask) == 0;
+  
+  bool get isFinite => (_bits & _exponentMask) != _exponentMask;
+  
+  bool get isNormal => 
+      (_bits & _exponentMask) != 0 && (_bits & _exponentMask) != _exponentMask;
+      
+  bool get isSubnormal => 
+      (_bits & _exponentMask) == 0 && (_bits & _mantissaMask) != 0;
+      
+  bool get isZero => (_bits & 0x7FFF) == 0;
+  
+  bool get isNegative => (_bits & _signMask) != 0;
+  
+  int get sign => isNaN ? 0 : (isNegative ? -1 : 1); 
+
+  // --- Arithmetic (Explicit) ---
+
+  Float16 add(Float16 other) => Float16.fromDouble(toDouble() + 
other.toDouble());
+  Float16 sub(Float16 other) => Float16.fromDouble(toDouble() - 
other.toDouble());
+  Float16 mul(Float16 other) => Float16.fromDouble(toDouble() * 
other.toDouble());
+  Float16 div(Float16 other) => Float16.fromDouble(toDouble() / 
other.toDouble());
+  
+  Float16 neg() => Float16.fromBits(_bits ^ _signMask);
+  
+  Float16 abs() => Float16.fromBits(_bits & 0x7FFF);
+
+  // --- Comparisons ---
+  
+  /// Bitwise equality. +0 != -0, NaN == NaN (if payload matches).
+  /// This is effectively `_bits == other._bits`.
+  bool equalsValue(Float16 other) => _bits == other._bits;
+  
+  /// IEEE comparison.
+  /// NaN != NaN. +0 == -0.
+  bool ieeeEquals(Float16 other) => toDouble() == other.toDouble();
+  
+  bool lessThan(Float16 other) => toDouble() < other.toDouble();
+  bool lessThanOrEqual(Float16 other) => toDouble() <= other.toDouble();
+  bool greaterThan(Float16 other) => toDouble() > other.toDouble();
+  bool greaterThanOrEqual(Float16 other) => toDouble() >= other.toDouble();
+  
+  static int compare(Float16 a, Float16 b) => 
a.toDouble().compareTo(b.toDouble());
+
+  // --- Operators (Delegation) ---
+  
   Float16 operator +(dynamic other) =>
-      Float16(_value + (other is FixedNum ? other.value : other));
+      add(other is Float16 ? other : Float16(other));
 
   Float16 operator -(dynamic other) =>
-      Float16(_value - (other is FixedNum ? other.value : other));
+      sub(other is Float16 ? other : Float16(other));
 
   Float16 operator *(dynamic other) =>
-      Float16(_value * (other is FixedNum ? other.value : other));
+      mul(other is Float16 ? other : Float16(other));
 
   double operator /(dynamic other) =>
-      _value / (other is FixedNum ? other.value : other);
-
+      toDouble() / (other is Float16 ? other.toDouble() : other);
+      
   Float16 operator ~/(dynamic other) =>
-      Float16(_value ~/ (other is FixedNum ? other.value : other));
+      Float16((toDouble() ~/ (other is Float16 ? other.toDouble() : other)));
 
   Float16 operator %(dynamic other) =>
-      Float16(_value % (other is FixedNum ? other.value : other));
+      Float16(toDouble() % (other is Float16 ? other.toDouble() : other));
 
-  Float16 operator -() => Float16(-_value);
+  Float16 operator -() => neg();
 
-  // Comparison
-  bool operator <(dynamic other) =>
-      _value < (other is FixedNum ? other.value : other);
+  // Comparison Operators
+  bool operator <(dynamic other) => toDouble() < (other is Float16 ? 
other.toDouble() : other);
 
-  bool operator <=(dynamic other) =>
-      _value <= (other is FixedNum ? other.value : other);
+  bool operator <=(dynamic other) => toDouble() <= (other is Float16 ? 
other.toDouble() : other);
 
-  bool operator >(dynamic other) =>
-      _value > (other is FixedNum ? other.value : other);
+  bool operator >(dynamic other) => toDouble() > (other is Float16 ? 
other.toDouble() : other);
 
-  bool operator >=(dynamic other) =>
-      _value >= (other is FixedNum ? other.value : other);
+  bool operator >=(dynamic other) => toDouble() >= (other is Float16 ? 
other.toDouble() : other);
 
-  // Equality
   @override
   bool operator ==(Object other) {
-    if (other is FixedNum) return _value == other.value;
-    if (other is num) return _value == other;
+    if (other is Float16) return _bits == other._bits; // Policy A: Bitwise 
equality
     return false;
   }
 
   @override
-  int get hashCode => _value.hashCode;
-
-  // Common num methods
-  double abs() => _value.abs();
-  double get sign => _value.sign;
-  bool get isNegative => _value < 0;
-  bool get isNaN => _value.isNaN;
-  bool get isInfinite => _value.isInfinite;
-  bool get isFinite => _value.isFinite;
-
-  // Type conversions
-  int toInt() => _value.toInt();
-  double toDouble() => _value;
-  Int8 toInt8() => Int8(_value);
-  Int16 toInt16() => Int16(_value);
-  Int32 toInt32() => Int32(_value);
-  Float32 toFloat32() => Float32(_value);
-
-  // String formatting
-  String toStringAsFixed(int fractionDigits) =>
-      _value.toStringAsFixed(fractionDigits);
-  String toStringAsExponential([int? fractionDigits]) =>
-      _value.toStringAsExponential(fractionDigits);
-  String toStringAsPrecision(int precision) =>
-      _value.toStringAsPrecision(precision);
-
+  int get hashCode => _bits.hashCode;
+
+  // --- Type Conversions ---
+  
+  int toInt() => toDouble().toInt();
+  
+  Int8 toInt8() => Int8(toInt());
+  Int16 toInt16() => Int16(toInt());
+  Int32 toInt32() => Int32(toInt());
+  
+  Float32 toFloat32() => Float32(toDouble());
+  
+  // --- String Formatting ---
+  
+  String toStringAsFixed(int fractionDigits) => 
toDouble().toStringAsFixed(fractionDigits);
+  String toStringAsExponential([int? fractionDigits]) => 
toDouble().toStringAsExponential(fractionDigits);
+  String toStringAsPrecision(int precision) => 
toDouble().toStringAsPrecision(precision);
+  
   @override
-  String toString() => _value.toString();
+  String toString() => toDouble().toString();
 }
diff --git a/dart/packages/fory/lib/src/datatype/fory_fixed_num.dart 
b/dart/packages/fory/lib/src/datatype/fory_fixed_num.dart
index d3f2ece2b..2e1fead36 100644
--- a/dart/packages/fory/lib/src/datatype/fory_fixed_num.dart
+++ b/dart/packages/fory/lib/src/datatype/fory_fixed_num.dart
@@ -27,6 +27,8 @@ enum NumType { int8, int16, int32, float16, float32 }
 
 /// Base abstract class for fixed-size numeric types
 abstract base class FixedNum implements Comparable<FixedNum> {
+  const FixedNum();
+
   num get value;
 
   // Factory constructor to create the appropriate type
diff --git a/dart/packages/fory/lib/src/memory/byte_reader.dart 
b/dart/packages/fory/lib/src/memory/byte_reader.dart
index e10f7214e..b8531d782 100644
--- a/dart/packages/fory/lib/src/memory/byte_reader.dart
+++ b/dart/packages/fory/lib/src/memory/byte_reader.dart
@@ -19,6 +19,7 @@
 
 import 'dart:typed_data';
 import 'package:meta/meta.dart';
+import 'package:fory/src/datatype/float16.dart';
 import 'package:fory/src/memory/byte_reader_impl.dart';
 
 abstract base class ByteReader {
@@ -66,6 +67,9 @@ abstract base class ByteReader {
   /// Reads a 64-bit floating point number from the stream.
   double readFloat64();
 
+  /// Reads a 16-bit floating point number from the stream.
+  Float16 readFloat16();
+
   int readVarUint36Small();
 
   int readVarInt32();
diff --git a/dart/packages/fory/lib/src/memory/byte_reader_impl.dart 
b/dart/packages/fory/lib/src/memory/byte_reader_impl.dart
index 7a8f34160..571a9d2e3 100644
--- a/dart/packages/fory/lib/src/memory/byte_reader_impl.dart
+++ b/dart/packages/fory/lib/src/memory/byte_reader_impl.dart
@@ -19,6 +19,7 @@
 
 import 'dart:typed_data';
 import 'package:fory/src/dev_annotation/optimize.dart';
+import 'package:fory/src/datatype/float16.dart';
 import 'package:fory/src/memory/byte_reader.dart';
 
 final class ByteReaderImpl extends ByteReader {
@@ -118,6 +119,13 @@ final class ByteReaderImpl extends ByteReader {
     return value;
   }
 
+  @override
+  Float16 readFloat16() {
+    int value = _bd.getUint16(_offset, endian);
+    _offset += 2;
+    return Float16.fromBits(value);
+  }
+
   @override
   Uint8List readBytesView(int length) {
     // create a view of the original list
diff --git a/dart/packages/fory/lib/src/memory/byte_writer.dart 
b/dart/packages/fory/lib/src/memory/byte_writer.dart
index 78f76bfa3..fb556afd0 100644
--- a/dart/packages/fory/lib/src/memory/byte_writer.dart
+++ b/dart/packages/fory/lib/src/memory/byte_writer.dart
@@ -20,6 +20,7 @@
 import 'dart:typed_data';
 import 'package:meta/meta.dart';
 import 'package:fory/src/memory/byte_writer_impl.dart';
+import 'package:fory/src/datatype/float16.dart';
 
 abstract base class ByteWriter {
   @protected
@@ -45,6 +46,7 @@ abstract base class ByteWriter {
 
   void writeFloat32(double value);
   void writeFloat64(double value);
+  void writeFloat16(Float16 value);
 
   void writeBytes(List<int> bytes);
 
diff --git a/dart/packages/fory/lib/src/memory/byte_writer_impl.dart 
b/dart/packages/fory/lib/src/memory/byte_writer_impl.dart
index 110ae286c..56d7ecbfd 100644
--- a/dart/packages/fory/lib/src/memory/byte_writer_impl.dart
+++ b/dart/packages/fory/lib/src/memory/byte_writer_impl.dart
@@ -19,6 +19,7 @@
 
 import 'dart:typed_data';
 import 'package:fory/src/dev_annotation/optimize.dart';
+import 'package:fory/src/datatype/float16.dart';
 import 'package:fory/src/memory/byte_writer.dart';
 
 final class ByteWriterImpl extends ByteWriter {
@@ -134,6 +135,13 @@ final class ByteWriterImpl extends ByteWriter {
     _buffer.add(_tempByteData.buffer.asUint8List(0, 8));
   }
 
+  /// Append a `Float16` (2 bytes, Little Endian) to the buffer
+  @inline
+  @override
+  void writeFloat16(Float16 value) {
+    writeUint16(value.toBits());
+  }
+
   /// Append a list of bytes to the buffer
   @override
   @inline
diff --git 
a/dart/packages/fory/lib/src/serializer/primitive_type_serializer.dart 
b/dart/packages/fory/lib/src/serializer/primitive_type_serializer.dart
index dd145df22..fdbc5ea94 100644
--- a/dart/packages/fory/lib/src/serializer/primitive_type_serializer.dart
+++ b/dart/packages/fory/lib/src/serializer/primitive_type_serializer.dart
@@ -19,6 +19,7 @@
 
 import 'package:fory/src/config/fory_config.dart';
 import 'package:fory/src/const/types.dart';
+import 'package:fory/src/datatype/float16.dart';
 import 'package:fory/src/datatype/float32.dart';
 import 'package:fory/src/datatype/fory_fixed_num.dart';
 import 'package:fory/src/datatype/int16.dart';
@@ -271,6 +272,42 @@ final class Float32Serializer extends Serializer<FixedNum> 
{
   }
 }
 
+final class _Float16SerializerCache extends PrimitiveSerializerCache {
+  static Float16Serializer? serializerWithRef;
+  static Float16Serializer? serializerWithoutRef;
+
+  const _Float16SerializerCache();
+
+  @override
+  Serializer getSerializerWithRef(bool writeRef) {
+    if (writeRef) {
+      serializerWithRef ??= Float16Serializer._(true);
+      return serializerWithRef!;
+    } else {
+      serializerWithoutRef ??= Float16Serializer._(false);
+      return serializerWithoutRef!;
+    }
+  }
+}
+
+// Dart does not have float16; the user can specify converting Dart double to 
float16 through annotation, so precision errors may occur
+final class Float16Serializer extends Serializer<FixedNum> {
+  static const SerializerCache cache = _Float16SerializerCache();
+
+  Float16Serializer._(bool writeRef) : super(ObjType.FLOAT16, writeRef);
+
+  @override
+  Float16 read(ByteReader br, int refId, DeserializationContext pack) {
+    return br.readFloat16();
+  }
+
+  @override
+  void write(ByteWriter bw, covariant Float16 v, SerializationContext pack) {
+    // No checks are performed here
+    bw.writeFloat16(v);
+  }
+}
+
 final class _Float64SerializerCache extends PrimitiveSerializerCache {
   static Float64Serializer? serializerWithRef;
   static Float64Serializer? serializerWithoutRef;
diff --git a/dart/packages/fory/lib/src/serializer/serializer_pool.dart 
b/dart/packages/fory/lib/src/serializer/serializer_pool.dart
index ad1a84a6a..6bf2cee00 100644
--- a/dart/packages/fory/lib/src/serializer/serializer_pool.dart
+++ b/dart/packages/fory/lib/src/serializer/serializer_pool.dart
@@ -23,6 +23,7 @@ import 'package:collection/collection.dart';
 import 'package:fory/src/config/fory_config.dart';
 import 'package:fory/src/const/dart_type.dart';
 import 'package:fory/src/const/types.dart';
+import 'package:fory/src/datatype/float16.dart';
 import 'package:fory/src/datatype/float32.dart';
 import 'package:fory/src/datatype/int16.dart';
 import 'package:fory/src/datatype/int32.dart';
@@ -79,6 +80,8 @@ class SerializerPool {
         UInt32Serializer.cache.getSerializer(conf);
     typeToTypeInfo[Float32]!.serializer =
         Float32Serializer.cache.getSerializer(conf);
+    typeToTypeInfo[Float16]!.serializer =
+        Float16Serializer.cache.getSerializer(conf);
     typeToTypeInfo[String]!.serializer =
         StringSerializer.cache.getSerializer(conf);
 


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to