From 889e4340325c5acfac1baed46e1610ce81ccb51b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Fri, 7 Jun 2024 14:25:30 +0200 Subject: [PATCH] RDART-866: kn/decimal128 web support (#1713) * Implement Decimal128 for web * fromDouble * ==, hashCode * Add deps on decimal and rational * Match IEEE 754 Decimal128 precision (+/- 10^6144) * Comment on compliancy * Fix compile for web regression * Update CHANGELOG --- .vscode/settings.json | 1 + CHANGELOG.md | 3 +- .../lib/src/handles/web/decimal128.dart | 88 +++++++++++++++++-- packages/realm_dart/pubspec.yaml | 2 + 4 files changed, 87 insertions(+), 7 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 483d755b2..44b79f873 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -24,6 +24,7 @@ "fnum", "geospatial", "HRESULT", + "intf", "keepalive", "keypaths", "loggable", diff --git a/CHANGELOG.md b/CHANGELOG.md index 009d15b6c..40429f0dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,8 @@ ### Enhancements * Report the originating error that caused a client reset to occur. (Core 14.9.0) * Allow the realm package, and code generated by realm_generator to be included when building - for web without breaking compilation. (Issue [#1374](https://github.com/realm/realm-dart/issues/1374)) + for web without breaking compilation. (Issue [#1374](https://github.com/realm/realm-dart/issues/1374), + PR [#1713](https://github.com/realm/realm-dart/pull/1713)) ### Fixed * `Realm.writeAsync` did not handle async callbacks (`Future Function()`) correctly. (Issue [#1667](https://github.com/realm/realm-dart/issues/1667)) diff --git a/packages/realm_dart/lib/src/handles/web/decimal128.dart b/packages/realm_dart/lib/src/handles/web/decimal128.dart index 58240b22b..342433918 100644 --- a/packages/realm_dart/lib/src/handles/web/decimal128.dart +++ b/packages/realm_dart/lib/src/handles/web/decimal128.dart @@ -1,9 +1,27 @@ // Copyright 2024 MongoDB, Inc. // SPDX-License-Identifier: Apache-2.0 -import '../decimal128.dart' as intf; +import 'package:decimal/decimal.dart'; + +import 'package:realm_dart/src/convert.dart'; + import 'web_not_supported.dart'; +import '../decimal128.dart' as intf; + +/// This is not a compliant IEEE 754-2008 Decimal128 implementation, as it is +/// just based on the [decimal](https://pub.dev/packages/decimal) package. +/// Which is based on the [rational](https://pub.dev/packages/rational) package, +/// which is again based on the [BigInt] class. +/// +/// The issues are mostly in some of the odd corner cases of IEEE 754-2008 +/// Decimal128, such as: +/// * -0 < 0, +/// * NaN != NaN, +/// * 1 / 0 = Inf, etc. +/// +/// Also, be warned that this class is incredible slow compared to the native +/// implementation. class Decimal128 implements intf.Decimal128 { static final zero = Decimal128.fromInt(0); @@ -23,17 +41,75 @@ class Decimal128 implements intf.Decimal128 { static final negativeInfinity = -infinity; /// Parses a string into a [Decimal128]. Returns `null` if the string is not a valid [Decimal128]. - static Decimal128? tryParse(String source) => webNotSupported(); + static Decimal128? tryParse(String source) => Decimal.tryParse(source).convert(Decimal128._); /// Parses a string into a [Decimal128]. Throws a [FormatException] if the string is not a valid [Decimal128]. - factory Decimal128.parse(String source) => webNotSupported(); + factory Decimal128.parse(String source) => Decimal128._(Decimal.parse(source)); /// Converts a `int` into a [Decimal128]. - factory Decimal128.fromInt(int value) => webNotSupported(); + factory Decimal128.fromInt(int value) => Decimal128._(Decimal.fromInt(value)); /// Converts a `double` into a [Decimal128]. - factory Decimal128.fromDouble(double value) => webNotSupported(); + factory Decimal128.fromDouble(double value) { + if (value.isNaN) return nan; + if (value.isInfinite) return value.isNegative ? negativeInfinity : infinity; + return Decimal128._(Decimal.parse(value.toString())); + } + + final Decimal _value; + Decimal128._(Decimal value) : _value = value.truncate(scale: 6144); + + @override + Decimal128 operator *(covariant Decimal128 other) => Decimal128._(_value * other._value); + + @override + Decimal128 operator +(covariant Decimal128 other) => Decimal128._(_value + other._value); + + @override + Decimal128 operator -() => Decimal128._(-_value); + + @override + Decimal128 operator -(covariant Decimal128 other) => Decimal128._(_value - other._value); + + // Note IEEE 754 Decimal128 defines division with zero as infinity, similar to double + @override + Decimal128 operator /(covariant Decimal128 other) => Decimal128._((_value / other._value).toDecimal(scaleOnInfinitePrecision: 6144)); + + @override + bool operator <(covariant Decimal128 other) => _value < other._value; + + @override + bool operator <=(covariant Decimal128 other) => _value <= other._value; + + @override + bool operator >(covariant Decimal128 other) => _value > other._value; + + @override + bool operator >=(covariant Decimal128 other) => _value >= other._value; + + @override + Decimal128 abs() => Decimal128._(_value.abs()); + + @override + int compareTo(covariant Decimal128 other) { + final sign = _value.compareTo(other._value); + if (sign < 0) return -1; + if (sign > 0) return 1; + return 0; + } + + @override + bool get isNaN => this == nan; + + @override + int toInt() => _value.toBigInt().toInt(); + + @override + String toString() => _value.toStringAsExponential(27); + + @override + operator ==(Object other) => other is Decimal128 && _value == other._value; @override - noSuchMethod(Invocation invocation) => webNotSupported(); + int get hashCode => _value.hashCode; } diff --git a/packages/realm_dart/pubspec.yaml b/packages/realm_dart/pubspec.yaml index b4a63abd2..86e38dff5 100644 --- a/packages/realm_dart/pubspec.yaml +++ b/packages/realm_dart/pubspec.yaml @@ -29,6 +29,8 @@ dependencies: build_runner: ^2.1.0 http: ^1.0.0 cancellation_token: ^2.0.0 + decimal: ^3.0.1 + rational: ^2.2.3 dev_dependencies: build_cli: ^2.2.2