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]