From 902cd30289c35d5a731a0e2711d663a97456b635 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Mon, 13 Jul 2020 09:56:47 -0700 Subject: [PATCH 1/4] Implement ulps for path ops --- lib/web_ui/lib/src/engine.dart | 1 + lib/web_ui/lib/src/engine/ulps.dart | 194 ++++++++++++++++++++++++++ lib/web_ui/test/engine/ulps_test.dart | 88 ++++++++++++ 3 files changed, 283 insertions(+) create mode 100644 lib/web_ui/lib/src/engine/ulps.dart create mode 100644 lib/web_ui/test/engine/ulps_test.dart diff --git a/lib/web_ui/lib/src/engine.dart b/lib/web_ui/lib/src/engine.dart index f4ca2d230c2a4..edc390ee3915f 100644 --- a/lib/web_ui/lib/src/engine.dart +++ b/lib/web_ui/lib/src/engine.dart @@ -131,6 +131,7 @@ part 'engine/text_editing/autofill_hint.dart'; part 'engine/text_editing/input_type.dart'; part 'engine/text_editing/text_capitalization.dart'; part 'engine/text_editing/text_editing.dart'; +part 'engine/ulps.dart'; part 'engine/util.dart'; part 'engine/validators.dart'; part 'engine/vector_math.dart'; diff --git a/lib/web_ui/lib/src/engine/ulps.dart b/lib/web_ui/lib/src/engine/ulps.dart new file mode 100644 index 0000000000000..4bac674fa30e8 --- /dev/null +++ b/lib/web_ui/lib/src/engine/ulps.dart @@ -0,0 +1,194 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +part of engine; + +// This is a small library to handle stability for floating point operations. +// +// Since we are representing an infinite number of real numbers in finite +// number of bits, when we perform comparisons of coordinates for paths for +// example, we want to make sure that line and curve sections that are too +// close to each other (number of floating point numbers +// representable in bits between two numbers) are handled correctly and +// don't cause algorithms to fail when we perform operations such as +// subtraction or between checks. +// +// Small introduction into floating point comparison: +// +// For some good articles on the topic, see +// https://randomascii.wordpress.com/category/floating-point/page/2/ +// +// Here is the 32 bit IEEE representation: +// uint32_t mantissa : 23; +// uint32_t exponent : 8; +// uint32_t sign : 1; +// As you can see it was carefully designed to be reinterpreted as an integer. +// +// Ulps stands for unit in the last place. ulp(x) is the gap between two +// floating point numbers nearest x. + +/// Converts a sign-bit int (float interpreted as int) into a 2s complement +/// int. Also converts 0x80000000 to 0. Allows result to be compared using +/// int comparison. +int signBitTo2sCompliment(int x) => (x & 0x80000000) != 0 ? (-(x & 0x7fffffff)) : x; + +/// Convert a 2s compliment int to a sign-bit (i.e. int interpreted as float). +int twosComplimentToSignBit(int x) { + if ((x & 0x80000000) == 0) { + return x; + } + x = ~x + 1; + x |= 0x80000000; + return x; +} + +class _FloatBitConverter { + final Float32List float32List = Float32List(1); + late Int32List int32List; + _FloatBitConverter() { + int32List = float32List.buffer.asInt32List(0, 1); + } + int toInt(Float32List source, int index) { + float32List[0] = source[index]; + return int32List[0]; + } + int toBits(double x) { + float32List[0] = x; + return int32List[0]; + } + double toFloat(int bits) { + int32List[0] = bits; + return float32List[0]; + } +} + +// Singleton bit converter to prevent typed array allocations. +_FloatBitConverter _floatBitConverter = _FloatBitConverter(); + +// Converts float to bits. +int float2Bits(Float32List source, int index) { + return _floatBitConverter.toInt(source, index); +} + +// Converts bits to float. +double bitsToFloat(int bits) { + return _floatBitConverter.toFloat(bits); +} + +const int floatBitsExponentMask = 0x7F800000; +const int floatBitsMatissaMask = 0x007FFFFF; + +/// Returns a float as 2s compliment int to be able to compare floats to each +/// other. +int floatFromListAs2sCompliment(Float32List source, int index) => + signBitTo2sCompliment(float2Bits(source, index)); + +int floatAs2sCompliment(double x) => + signBitTo2sCompliment(_floatBitConverter.toBits(x)); + +double twosComplimentAsFloat(int x) => + bitsToFloat(twosComplimentToSignBit(x)); + +bool _argumentsDenormalized(double a, double b, int epsilon) { + double denormalizedCheck = kFltEpsilon * epsilon / 2; + return a.abs() <= denormalizedCheck && b.abs() <= denormalizedCheck; +} + +bool equalUlps(double a, double b, int epsilon, int depsilon) { + if (_argumentsDenormalized(a, b, depsilon)) { + return true; + } + int aBits = floatAs2sCompliment(a); + int bBits = floatAs2sCompliment(b); + // Find the difference in ULPs. + return aBits < bBits + epsilon && bBits < aBits + epsilon; +} + +bool almostEqualUlps(double a, double b) { + const int kUlpsEpsilon = 16; + return equalUlps(a, b, kUlpsEpsilon, kUlpsEpsilon); +} + +// Equality using the same error term as between +bool almostBequalUlps(double a, double b) { + const int kUlpsEpsilon = 2; + return equalUlps(a, b, kUlpsEpsilon, kUlpsEpsilon); +} + +bool almostPequalUlps(double a, double b) { + const int kUlpsEpsilon = 8; + return equalUlps(a, b, kUlpsEpsilon, kUlpsEpsilon); +} + +bool almostDequalUlps(double a, double b) { + const int kUlpsEpsilon = 16; + return equalUlps(a, b, kUlpsEpsilon, kUlpsEpsilon); +} + +bool approximatelyEqual(double ax, double ay, double bx, double by) { + if (approximatelyEqualT(ax, bx) && approximatelyEqualT(ay, by)) { + return true; + } + if (!roughlyEqualUlps(ax, bx) || !roughlyEqualUlps(ay, by)) { + return false; + } + final double dx = (ax - bx); + final double dy = (ay - by); + double dist = math.sqrt(dx * dx + dy * dy); + double tiniest = math.min(math.min(math.min(ax, bx), ay), by); + double largest = math.max(math.max(math.max(ax, bx), ay), by); + largest = math.max(largest, -tiniest); + return almostDequalUlps(largest, largest + dist); +} + +// Use this for comparing Ts in the range of 0 to 1. For general numbers (larger and smaller) use +// AlmostEqualUlps instead. +bool approximatelyEqualT(double t1, double t2) { + return approximatelyZero(t1 - t2); +} + +bool approximatelyZero(double value) => value.abs() < kFltEpsilon; + +bool roughlyEqualUlps(double a, double b) { + const int UlpsEpsilon = 256; + const int DUlpsEpsilon = 1024; + return equalUlps(a, b, UlpsEpsilon, DUlpsEpsilon); +} + +bool dEqualUlpsEpsilon(double a, double b, int epsilon) { + int aBits = floatAs2sCompliment(a); + int bBits = floatAs2sCompliment(b); + // Find the difference in ULPs. + return aBits < bBits + epsilon && bBits < aBits + epsilon; +} + +bool almostDequalUlpsDouble(double a, double b) { + if (a.abs() < kScalarMax && b.abs() < kScalarMax) { + return almostDequalUlps(a, b); + } + return (a - b).abs() / math.max(a.abs(), b.abs()) < kFltEpsilon * 16; +} + +const double kFltEpsilon = 1.19209290E-07; // == 1 / (2 ^ 23) +const double kDblEpsilon = 2.22045e-16; +const double kFltEpsilonCubed = kFltEpsilon * kFltEpsilon * kFltEpsilon; +const double kFltEpsilonHalf = kFltEpsilon / 2; +const double kFltEpsilonDouble = kFltEpsilon * 2; +const double kFltEpsilonOrderableErr = kFltEpsilon * 16; +const double kFltEpsilonSquared = kFltEpsilon * kFltEpsilon; +// Use a compile-time constant for FLT_EPSILON_SQRT to avoid initializers. +// A 17 digit constant guarantees exact results. +const double kFltEpsilonSqrt = 0.00034526697709225118; // sqrt(kFltEpsilon); +const double kFltEpsilonInverse = 1 / kFltEpsilon; +const double kDblEpsilonErr = kDblEpsilon * 4; +const double kDblEpsilonSubdivideErr = kDblEpsilon * 16; +const double kRoughEpsilon = kFltEpsilon * 64; +const double kMoreRoughEpsilon = kFltEpsilon * 256; +const double kWayRoughEpsilon = kFltEpsilon * 2048; +const double kBumpEpsilon = kFltEpsilon * 4096; + +// Scalar max is based on 32 bit float since [PathRef] stores values in +// Float32List. +const double kScalarMax = 3.402823466e+38; +const double kScalarMin = -kScalarMax; diff --git a/lib/web_ui/test/engine/ulps_test.dart b/lib/web_ui/test/engine/ulps_test.dart new file mode 100644 index 0000000000000..9d993e92eb262 --- /dev/null +++ b/lib/web_ui/test/engine/ulps_test.dart @@ -0,0 +1,88 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:typed_data'; +import 'package:test/test.dart'; +import 'package:ui/src/engine.dart'; + +void main() { + group('Float Int conversions', (){ + test('Should convert signbit to 2\'s compliment', () { + expect(signBitTo2sCompliment(0), 0); + expect(signBitTo2sCompliment(0x7fffffff).toUnsigned(32), 0x7fffffff); + expect(signBitTo2sCompliment(0x80000000), 0); + expect(signBitTo2sCompliment(0x8f000000).toUnsigned(32), 0xf1000000); + expect(signBitTo2sCompliment(0x8fffffff).toUnsigned(32), 0xf0000001); + expect(signBitTo2sCompliment(0xffffffff).toUnsigned(32), 0x80000001); + expect(signBitTo2sCompliment(0x8f000000), -251658240); + expect(signBitTo2sCompliment(0x8fffffff), -268435455); + expect(signBitTo2sCompliment(0xffffffff), -2147483647); + }); + + test('Should convert 2s compliment to signbit', () { + expect(twosComplimentToSignBit(0), 0); + expect(twosComplimentToSignBit(0x7fffffff), 0x7fffffff); + expect(twosComplimentToSignBit(0), 0); + expect(twosComplimentToSignBit(0xf1000000).toRadixString(16), 0x8f000000.toRadixString(16)); + expect(twosComplimentToSignBit(0xf0000001), 0x8fffffff); + expect(twosComplimentToSignBit(0x80000001), 0xffffffff); + expect(twosComplimentToSignBit(0x81234561), 0xfedcba9f); + expect(twosComplimentToSignBit(-5), 0x80000005); + }); + + test('Should convert float to bits', () { + Float32List floatList = Float32List(1); + floatList[0] = 0; + expect(float2Bits(floatList, 0), 0); + floatList[0] = 0.1; + expect(float2Bits(floatList, 0).toUnsigned(32).toRadixString(16), 0x3dcccccd.toRadixString(16)); + floatList[0] = 123456.0; + expect(float2Bits(floatList, 0).toUnsigned(32).toRadixString(16), 0x47f12000.toRadixString(16)); + floatList[0] = -0.1; + expect(float2Bits(floatList, 0).toUnsigned(32).toRadixString(16), 0xbdcccccd.toRadixString(16)); + floatList[0] = -123456.0; + expect(float2Bits(floatList, 0).toUnsigned(32).toRadixString(16), 0xc7f12000.toRadixString(16)); + }); + }); + group('Comparison', () { + test('Should compare equality based on ulps', () { + // If number of floats between a=1.1 and b are below 16, equals should + // return true. + final double a = 1.1; + int aBits = floatAs2sCompliment(a); + double b = twosComplimentAsFloat(aBits + 1); + expect(almostEqualUlps(a, b), true); + b = twosComplimentAsFloat(aBits + 15); + expect(almostEqualUlps(a, b), true); + b = twosComplimentAsFloat(aBits + 16); + expect(almostEqualUlps(a, b), false); + + // Test between variant of equalUlps. + b = twosComplimentAsFloat(aBits + 1); + expect(almostBequalUlps(a, b), true); + b = twosComplimentAsFloat(aBits + 1); + expect(almostBequalUlps(a, b), true); + b = twosComplimentAsFloat(aBits + 2); + expect(almostBequalUlps(a, b), false); + }); + + test('Should compare 2 coordinates based on ulps', () { + double a = 1.1; + int aBits = floatAs2sCompliment(a); + double b = twosComplimentAsFloat(aBits + 1); + expect(approximatelyEqual(5.0, a, 5.0, b), true); + b = twosComplimentAsFloat(aBits + 16); + expect(approximatelyEqual(5.0, a, 5.0, b), true); + + // Increase magnitude which should start checking with ulps rather than + // fltEpsilon. + a = 3000000.1; + aBits = floatAs2sCompliment(a); + b = twosComplimentAsFloat(aBits + 1); + expect(approximatelyEqual(5.0, a, 5.0, b), true); + b = twosComplimentAsFloat(aBits + 16); + expect(approximatelyEqual(5.0, a, 5.0, b), false); + }); + }); +} From 052c6311a89fc89eb00df124e2fa91944a88f90d Mon Sep 17 00:00:00 2001 From: ferhatb Date: Mon, 13 Jul 2020 11:37:41 -0700 Subject: [PATCH 2/4] Address review comments --- lib/web_ui/lib/src/engine/ulps.dart | 49 +++++++++++++++++++---------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/lib/web_ui/lib/src/engine/ulps.dart b/lib/web_ui/lib/src/engine/ulps.dart index 4bac674fa30e8..8f52961237b90 100644 --- a/lib/web_ui/lib/src/engine/ulps.dart +++ b/lib/web_ui/lib/src/engine/ulps.dart @@ -18,6 +18,8 @@ part of engine; // // For some good articles on the topic, see // https://randomascii.wordpress.com/category/floating-point/page/2/ +// Port based on: +// https://github.com/google/skia/blob/master/include/private/SkFloatBits.h // // Here is the 32 bit IEEE representation: // uint32_t mantissa : 23; @@ -33,7 +35,7 @@ part of engine; /// int comparison. int signBitTo2sCompliment(int x) => (x & 0x80000000) != 0 ? (-(x & 0x7fffffff)) : x; -/// Convert a 2s compliment int to a sign-bit (i.e. int interpreted as float). +/// Convert a 2s complement int to a sign-bit (i.e. int interpreted as float). int twosComplimentToSignBit(int x) { if ((x & 0x80000000) == 0) { return x; @@ -44,11 +46,16 @@ int twosComplimentToSignBit(int x) { } class _FloatBitConverter { - final Float32List float32List = Float32List(1); - late Int32List int32List; - _FloatBitConverter() { - int32List = float32List.buffer.asInt32List(0, 1); + final Float32List float32List; + final Int32List int32List; + _FloatBitConverter._(this.float32List, this.int32List); + + factory _FloatBitConverter() { + Float32List float32List = Float32List(1); + return _FloatBitConverter._(float32List, + float32List.buffer.asInt32List(0, 1)); } + int toInt(Float32List source, int index) { float32List[0] = source[index]; return int32List[0]; @@ -57,14 +64,15 @@ class _FloatBitConverter { float32List[0] = x; return int32List[0]; } - double toFloat(int bits) { + + double toDouble(int bits) { int32List[0] = bits; return float32List[0]; } } // Singleton bit converter to prevent typed array allocations. -_FloatBitConverter _floatBitConverter = _FloatBitConverter(); +final _FloatBitConverter _floatBitConverter = _FloatBitConverter(); // Converts float to bits. int float2Bits(Float32List source, int index) { @@ -73,13 +81,13 @@ int float2Bits(Float32List source, int index) { // Converts bits to float. double bitsToFloat(int bits) { - return _floatBitConverter.toFloat(bits); + return _floatBitConverter.toDouble(bits); } const int floatBitsExponentMask = 0x7F800000; const int floatBitsMatissaMask = 0x007FFFFF; -/// Returns a float as 2s compliment int to be able to compare floats to each +/// Returns a float as 2s complement int to be able to compare floats to each /// other. int floatFromListAs2sCompliment(Float32List source, int index) => signBitTo2sCompliment(float2Bits(source, index)); @@ -105,27 +113,32 @@ bool equalUlps(double a, double b, int epsilon, int depsilon) { return aBits < bBits + epsilon && bBits < aBits + epsilon; } +/// General equality check that covers between, product and division by using +/// ulps epsilon 16. bool almostEqualUlps(double a, double b) { const int kUlpsEpsilon = 16; return equalUlps(a, b, kUlpsEpsilon, kUlpsEpsilon); } -// Equality using the same error term as between +/// Equality using the same error term for between comparison. bool almostBequalUlps(double a, double b) { const int kUlpsEpsilon = 2; return equalUlps(a, b, kUlpsEpsilon, kUlpsEpsilon); } +/// Equality check for product. bool almostPequalUlps(double a, double b) { const int kUlpsEpsilon = 8; return equalUlps(a, b, kUlpsEpsilon, kUlpsEpsilon); } +/// Equality check for division. bool almostDequalUlps(double a, double b) { const int kUlpsEpsilon = 16; return equalUlps(a, b, kUlpsEpsilon, kUlpsEpsilon); } +/// Checks if 2 points are roughly equal (ulp 256) to each other. bool approximatelyEqual(double ax, double ay, double bx, double by) { if (approximatelyEqualT(ax, bx) && approximatelyEqualT(ay, by)) { return true; @@ -142,8 +155,10 @@ bool approximatelyEqual(double ax, double ay, double bx, double by) { return almostDequalUlps(largest, largest + dist); } -// Use this for comparing Ts in the range of 0 to 1. For general numbers (larger and smaller) use -// AlmostEqualUlps instead. +/// Equality check for comparing curve T values in the range of 0 to 1. +/// +/// For general numbers (larger and smaller) use +/// AlmostEqualUlps instead. bool approximatelyEqualT(double t1, double t2) { return approximatelyZero(t1 - t2); } @@ -151,9 +166,9 @@ bool approximatelyEqualT(double t1, double t2) { bool approximatelyZero(double value) => value.abs() < kFltEpsilon; bool roughlyEqualUlps(double a, double b) { - const int UlpsEpsilon = 256; - const int DUlpsEpsilon = 1024; - return equalUlps(a, b, UlpsEpsilon, DUlpsEpsilon); + const int kUlpsEpsilon = 256; + const int kDUlpsEpsilon = 1024; + return equalUlps(a, b, kUlpsEpsilon, kDUlpsEpsilon); } bool dEqualUlpsEpsilon(double a, double b, int epsilon) { @@ -163,11 +178,12 @@ bool dEqualUlpsEpsilon(double a, double b, int epsilon) { return aBits < bBits + epsilon && bBits < aBits + epsilon; } +// Checks equality for division. bool almostDequalUlpsDouble(double a, double b) { if (a.abs() < kScalarMax && b.abs() < kScalarMax) { return almostDequalUlps(a, b); } - return (a - b).abs() / math.max(a.abs(), b.abs()) < kFltEpsilon * 16; + return (a - b).abs() / math.max(a.abs(), b.abs()) < kDblEpsilonSubdivideErr; } const double kFltEpsilon = 1.19209290E-07; // == 1 / (2 ^ 23) @@ -175,6 +191,7 @@ const double kDblEpsilon = 2.22045e-16; const double kFltEpsilonCubed = kFltEpsilon * kFltEpsilon * kFltEpsilon; const double kFltEpsilonHalf = kFltEpsilon / 2; const double kFltEpsilonDouble = kFltEpsilon * 2; +// Epsilon to use when ordering vectors. const double kFltEpsilonOrderableErr = kFltEpsilon * 16; const double kFltEpsilonSquared = kFltEpsilon * kFltEpsilon; // Use a compile-time constant for FLT_EPSILON_SQRT to avoid initializers. From c067c45d536631b5e0973392b35776020ac8830f Mon Sep 17 00:00:00 2001 From: ferhatb Date: Mon, 13 Jul 2020 11:39:48 -0700 Subject: [PATCH 3/4] cache abs() --- lib/web_ui/lib/src/engine/ulps.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/web_ui/lib/src/engine/ulps.dart b/lib/web_ui/lib/src/engine/ulps.dart index 8f52961237b90..ee53329a59975 100644 --- a/lib/web_ui/lib/src/engine/ulps.dart +++ b/lib/web_ui/lib/src/engine/ulps.dart @@ -180,10 +180,12 @@ bool dEqualUlpsEpsilon(double a, double b, int epsilon) { // Checks equality for division. bool almostDequalUlpsDouble(double a, double b) { - if (a.abs() < kScalarMax && b.abs() < kScalarMax) { + final double absA = a.abs(); + final double absB = b.abs(); + if (absA < kScalarMax && absB < kScalarMax) { return almostDequalUlps(a, b); } - return (a - b).abs() / math.max(a.abs(), b.abs()) < kDblEpsilonSubdivideErr; + return (a - b).abs() / math.max(absA, absB) < kDblEpsilonSubdivideErr; } const double kFltEpsilon = 1.19209290E-07; // == 1 / (2 ^ 23) From 062960a14466ae8a7819db7a8bfe4be8a3b36bac Mon Sep 17 00:00:00 2001 From: ferhatb Date: Mon, 13 Jul 2020 11:53:51 -0700 Subject: [PATCH 4/4] dartfmt and update licenses --- ci/licenses_golden/licenses_flutter | 1 + lib/web_ui/lib/src/engine/ulps.dart | 13 +++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 5559d74271419..0134ce99514dd 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -524,6 +524,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/text_editing/autofill_hint.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/text_editing/input_type.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/text_editing/text_capitalization.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/text_editing/text_editing.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/ulps.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/util.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/validators.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/vector_math.dart diff --git a/lib/web_ui/lib/src/engine/ulps.dart b/lib/web_ui/lib/src/engine/ulps.dart index ee53329a59975..678537ef16408 100644 --- a/lib/web_ui/lib/src/engine/ulps.dart +++ b/lib/web_ui/lib/src/engine/ulps.dart @@ -33,7 +33,8 @@ part of engine; /// Converts a sign-bit int (float interpreted as int) into a 2s complement /// int. Also converts 0x80000000 to 0. Allows result to be compared using /// int comparison. -int signBitTo2sCompliment(int x) => (x & 0x80000000) != 0 ? (-(x & 0x7fffffff)) : x; +int signBitTo2sCompliment(int x) => + (x & 0x80000000) != 0 ? (-(x & 0x7fffffff)) : x; /// Convert a 2s complement int to a sign-bit (i.e. int interpreted as float). int twosComplimentToSignBit(int x) { @@ -52,14 +53,15 @@ class _FloatBitConverter { factory _FloatBitConverter() { Float32List float32List = Float32List(1); - return _FloatBitConverter._(float32List, - float32List.buffer.asInt32List(0, 1)); + return _FloatBitConverter._( + float32List, float32List.buffer.asInt32List(0, 1)); } int toInt(Float32List source, int index) { float32List[0] = source[index]; return int32List[0]; } + int toBits(double x) { float32List[0] = x; return int32List[0]; @@ -85,7 +87,7 @@ double bitsToFloat(int bits) { } const int floatBitsExponentMask = 0x7F800000; -const int floatBitsMatissaMask = 0x007FFFFF; +const int floatBitsMatissaMask = 0x007FFFFF; /// Returns a float as 2s complement int to be able to compare floats to each /// other. @@ -95,8 +97,7 @@ int floatFromListAs2sCompliment(Float32List source, int index) => int floatAs2sCompliment(double x) => signBitTo2sCompliment(_floatBitConverter.toBits(x)); -double twosComplimentAsFloat(int x) => - bitsToFloat(twosComplimentToSignBit(x)); +double twosComplimentAsFloat(int x) => bitsToFloat(twosComplimentToSignBit(x)); bool _argumentsDenormalized(double a, double b, int epsilon) { double denormalizedCheck = kFltEpsilon * epsilon / 2;