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 6f9aef9b96..41f67225d5 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 f6 = []; 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 67733af6a1..26c07a2aa5 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 0000000000..b96cffffe2 --- /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 9f795ad15d..ba5526ca23 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 c7fafd36b8..85c37fcf11 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 501e04aea0..7fbf4145ef 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 d3f2ece2bc..2e1fead364 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 { + 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 e10f7214e9..b8531d7826 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 7a8f341609..571a9d2e3f 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 78f76bfa3b..fb556afd02 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 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 110ae286c7..56d7ecbfd0 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 dd145df227..fdbc5ea94f 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 { } } +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 { + 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 ad1a84a6a6..6bf2cee007 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);