From 006186b981d0b4a2f92c270b7d9d4eab03dbc6bc Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Tue, 9 Jun 2020 23:59:29 +0300 Subject: [PATCH 1/5] Fix machine epsilon for double --- src/Avalonia.Base/Utilities/MathUtilities.cs | 9 ++-- .../Utilities/MathUtilitiesTests.cs | 51 +++++++++++++++++++ 2 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 tests/Avalonia.Base.UnitTests/Utilities/MathUtilitiesTests.cs diff --git a/src/Avalonia.Base/Utilities/MathUtilities.cs b/src/Avalonia.Base/Utilities/MathUtilities.cs index a1a2d6c3d0..3fb5f7a162 100644 --- a/src/Avalonia.Base/Utilities/MathUtilities.cs +++ b/src/Avalonia.Base/Utilities/MathUtilities.cs @@ -8,6 +8,9 @@ namespace Avalonia.Utilities /// public static class MathUtilities { + // smallest such that 1.0+DoubleEpsilon != 1.0 + private const double DoubleEpsilon = 2.2204460492503131e-016; + /// /// AreClose - Returns whether or not two doubles are "close". That is, whether or /// not they are within epsilon of each other. @@ -18,7 +21,7 @@ namespace Avalonia.Utilities { //in case they are Infinities (then epsilon check does not work) if (value1 == value2) return true; - double eps = (Math.Abs(value1) + Math.Abs(value2) + 10.0) * double.Epsilon; + double eps = (Math.Abs(value1) + Math.Abs(value2) + 10.0) * DoubleEpsilon; double delta = value1 - value2; return (-eps < delta) && (eps > delta); } @@ -78,7 +81,7 @@ namespace Avalonia.Utilities /// The double to compare to 1. public static bool IsOne(double value) { - return Math.Abs(value - 1.0) < 10.0 * double.Epsilon; + return Math.Abs(value - 1.0) < 10.0 * DoubleEpsilon; } /// @@ -88,7 +91,7 @@ namespace Avalonia.Utilities /// The double to compare to 0. public static bool IsZero(double value) { - return Math.Abs(value) < 10.0 * double.Epsilon; + return Math.Abs(value) < 10.0 * DoubleEpsilon; } /// diff --git a/tests/Avalonia.Base.UnitTests/Utilities/MathUtilitiesTests.cs b/tests/Avalonia.Base.UnitTests/Utilities/MathUtilitiesTests.cs new file mode 100644 index 0000000000..708e703a1d --- /dev/null +++ b/tests/Avalonia.Base.UnitTests/Utilities/MathUtilitiesTests.cs @@ -0,0 +1,51 @@ +using Avalonia.Utilities; +using Xunit; + +namespace Avalonia.Base.UnitTests.Utilities +{ + public class MathUtilitiesTests + { + [Fact] + public void Two_Equivalent_Double_Values_Are_Close() + { + const int N = 10; + var x = 42.42; + var y = 0.0; + var dx = x / N; + + for (var i = 0; i < N; ++i) + y += dx; + var actual = MathUtilities.AreClose(x, y); + + Assert.True(actual); + } + + [Fact] + public void Calculated_Double_One_Is_One() + { + const int N = 10; + var dx = 1.0 / N; + var x = 0.0; + + for (var i = 0; i < N; ++i) + x += dx; + var actual = MathUtilities.IsOne(x); + + Assert.True(actual); + } + + [Fact] + public void Calculated_Double_Zero_Is_Zero() + { + const int N = 10; + var x = 1.0; + var dx = x / N; + + for (var i = 0; i < N; ++i) + x -= dx; + var actual = MathUtilities.IsZero(x); + + Assert.True(actual); + } + } +} From a2b5b3ad3b035692d81ec1645372cefbc2ea1426 Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Wed, 10 Jun 2020 10:19:47 +0300 Subject: [PATCH 2/5] Fix machine epsilon for double elsewhere --- src/Avalonia.Base/Properties/AssemblyInfo.cs | 3 +- src/Avalonia.Base/Utilities/MathUtilities.cs | 2 +- src/Avalonia.Controls/Grid.cs | 35 ++++--------------- src/Avalonia.Controls/Slider.cs | 2 +- .../Utils/BorderRenderHelper.cs | 3 +- src/Avalonia.Visuals/Media/DrawingContext.cs | 5 +-- .../Media/TextFormatting/TextLayout.cs | 3 +- .../Media/DrawingContextImpl.cs | 2 +- 8 files changed, 19 insertions(+), 36 deletions(-) diff --git a/src/Avalonia.Base/Properties/AssemblyInfo.cs b/src/Avalonia.Base/Properties/AssemblyInfo.cs index 75d58f45d5..fd5e411850 100644 --- a/src/Avalonia.Base/Properties/AssemblyInfo.cs +++ b/src/Avalonia.Base/Properties/AssemblyInfo.cs @@ -7,4 +7,5 @@ using Avalonia.Metadata; [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Data.Converters")] [assembly: InternalsVisibleTo("Avalonia.Base.UnitTests")] [assembly: InternalsVisibleTo("Avalonia.UnitTests")] -[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] \ No newline at end of file +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] +[assembly: InternalsVisibleTo("Avalonia.Controls")] diff --git a/src/Avalonia.Base/Utilities/MathUtilities.cs b/src/Avalonia.Base/Utilities/MathUtilities.cs index 3fb5f7a162..6da695407b 100644 --- a/src/Avalonia.Base/Utilities/MathUtilities.cs +++ b/src/Avalonia.Base/Utilities/MathUtilities.cs @@ -9,7 +9,7 @@ namespace Avalonia.Utilities public static class MathUtilities { // smallest such that 1.0+DoubleEpsilon != 1.0 - private const double DoubleEpsilon = 2.2204460492503131e-016; + internal static readonly double DoubleEpsilon = 2.2204460492503131e-016; /// /// AreClose - Returns whether or not two doubles are "close". That is, whether or diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index 1781067abb..e10d78917e 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -1228,7 +1228,7 @@ namespace Avalonia.Controls Debug.Assert(1 < count && 0 <= start && (start + count) <= definitions.Count); // avoid processing when asked to distribute "0" - if (!_IsZero(requestedSize)) + if (!MathUtilities.IsZero(requestedSize)) { DefinitionBase[] tempDefinitions = TempDefinitions; // temp array used to remember definitions for sorting int end = start + count; @@ -1306,7 +1306,7 @@ namespace Avalonia.Controls } // sanity check: requested size must all be distributed - Debug.Assert(_IsZero(sizeToDistribute)); + Debug.Assert(MathUtilities.IsZero(sizeToDistribute)); } else if (requestedSize <= rangeMaxSize) { @@ -1346,7 +1346,7 @@ namespace Avalonia.Controls } // sanity check: requested size must all be distributed - Debug.Assert(_IsZero(sizeToDistribute)); + Debug.Assert(MathUtilities.IsZero(sizeToDistribute)); } else { @@ -1358,7 +1358,7 @@ namespace Avalonia.Controls double equalSize = requestedSize / count; if (equalSize < maxMaxSize - && !_AreClose(equalSize, maxMaxSize)) + && !MathUtilities.AreClose(equalSize, maxMaxSize)) { // equi-size is less than maximum of maxSizes. // in this case distribute so that smaller definitions grow faster than @@ -2151,7 +2151,7 @@ namespace Avalonia.Controls // and precision of floating-point computation. (However, the resulting // display is subject to anti-aliasing problems. TANSTAAFL.) - if (!_AreClose(roundedTakenSize, finalSize)) + if (!MathUtilities.AreClose(roundedTakenSize, finalSize)) { // Compute deltas for (int i = 0; i < definitions.Count; ++i) @@ -2168,7 +2168,7 @@ namespace Avalonia.Controls if (roundedTakenSize > finalSize) { int i = definitions.Count - 1; - while ((adjustedSize > finalSize && !_AreClose(adjustedSize, finalSize)) && i >= 0) + while ((adjustedSize > finalSize && !MathUtilities.AreClose(adjustedSize, finalSize)) && i >= 0) { DefinitionBase definition = definitions[definitionIndices[i]]; double final = definition.SizeCache - dpiIncrement; @@ -2184,7 +2184,7 @@ namespace Avalonia.Controls else if (roundedTakenSize < finalSize) { int i = 0; - while ((adjustedSize < finalSize && !_AreClose(adjustedSize, finalSize)) && i < definitions.Count) + while ((adjustedSize < finalSize && !MathUtilities.AreClose(adjustedSize, finalSize)) && i < definitions.Count) { DefinitionBase definition = definitions[definitionIndices[i]]; double final = definition.SizeCache + dpiIncrement; @@ -2595,27 +2595,6 @@ namespace Avalonia.Controls set { SetFlags(value, Flags.HasGroup3CellsInAutoRows); } } - /// - /// fp version of d == 0. - /// - /// Value to check. - /// true if d == 0. - private static bool _IsZero(double d) - { - return (Math.Abs(d) < double.Epsilon); - } - - /// - /// fp version of d1 == d2 - /// - /// First value to compare - /// Second value to compare - /// true if d1 == d2 - private static bool _AreClose(double d1, double d2) - { - return (Math.Abs(d1 - d2) < double.Epsilon); - } - /// /// Returns reference to extended data bag. /// diff --git a/src/Avalonia.Controls/Slider.cs b/src/Avalonia.Controls/Slider.cs index ec23bfa396..6cebe32322 100644 --- a/src/Avalonia.Controls/Slider.cs +++ b/src/Avalonia.Controls/Slider.cs @@ -193,7 +193,7 @@ namespace Avalonia.Controls var orient = Orientation == Orientation.Horizontal; var pointDen = orient ? _track.Bounds.Width : _track.Bounds.Height; - pointDen += double.Epsilon; // Just add epsilon to avoid divide by zero exceptions. + pointDen += MathUtilities.DoubleEpsilon; // Just add epsilon to avoid divide by zero exceptions. var pointNum = orient ? x.Position.X : x.Position.Y; var logicalPos = MathUtilities.Clamp(pointNum / pointDen, 0.0d, 1.0d); diff --git a/src/Avalonia.Controls/Utils/BorderRenderHelper.cs b/src/Avalonia.Controls/Utils/BorderRenderHelper.cs index 51eb6edbea..438cbc8b27 100644 --- a/src/Avalonia.Controls/Utils/BorderRenderHelper.cs +++ b/src/Avalonia.Controls/Utils/BorderRenderHelper.cs @@ -1,6 +1,7 @@ using System; using Avalonia.Media; using Avalonia.Platform; +using Avalonia.Utilities; namespace Avalonia.Controls.Utils { @@ -119,7 +120,7 @@ namespace Avalonia.Controls.Utils } var rect = new Rect(_size); - if (Math.Abs(borderThickness) > double.Epsilon) + if (!MathUtilities.IsZero(borderThickness)) rect = rect.Deflate(borderThickness * 0.5); var rrect = new RoundedRect(rect, _cornerRadius.TopLeft, _cornerRadius.TopRight, _cornerRadius.BottomRight, _cornerRadius.BottomLeft); diff --git a/src/Avalonia.Visuals/Media/DrawingContext.cs b/src/Avalonia.Visuals/Media/DrawingContext.cs index 6fdcd9631b..b1cf1aecc9 100644 --- a/src/Avalonia.Visuals/Media/DrawingContext.cs +++ b/src/Avalonia.Visuals/Media/DrawingContext.cs @@ -4,6 +4,7 @@ using Avalonia.Media.Imaging; using Avalonia.Platform; using Avalonia.Rendering.SceneGraph; using Avalonia.Threading; +using Avalonia.Utilities; using Avalonia.Visuals.Media.Imaging; namespace Avalonia.Media @@ -154,12 +155,12 @@ namespace Avalonia.Media return; } - if (Math.Abs(radiusX) > double.Epsilon) + if (!MathUtilities.IsZero(radiusX)) { radiusX = Math.Min(radiusX, rect.Width / 2); } - if (Math.Abs(radiusY) > double.Epsilon) + if (!MathUtilities.IsZero(radiusY)) { radiusY = Math.Min(radiusY, rect.Height / 2); } diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs index 720185a3ad..0292398782 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs @@ -4,6 +4,7 @@ using System.Linq; using Avalonia.Media.Immutable; using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Platform; +using Avalonia.Utilities; using Avalonia.Utility; namespace Avalonia.Media.TextFormatting @@ -184,7 +185,7 @@ namespace Avalonia.Media.TextFormatting /// private void UpdateLayout() { - if (_text.IsEmpty || Math.Abs(MaxWidth) < double.Epsilon || Math.Abs(MaxHeight) < double.Epsilon) + if (_text.IsEmpty || MathUtilities.IsZero(MaxWidth) || MathUtilities.IsZero(MaxHeight)) { var textLine = CreateEmptyTextLine(0); diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index bbb45cf64c..9b7ba4844a 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -236,7 +236,7 @@ namespace Avalonia.Direct2D1.Media Math.Max(rrect.RadiiTopRight.X, Math.Max(rrect.RadiiBottomRight.X, rrect.RadiiBottomLeft.X))); var radiusY = Math.Max(rrect.RadiiTopLeft.Y, Math.Max(rrect.RadiiTopRight.Y, Math.Max(rrect.RadiiBottomRight.Y, rrect.RadiiBottomLeft.Y))); - var isRounded = Math.Abs(radiusX) > double.Epsilon || Math.Abs(radiusY) > double.Epsilon; + var isRounded = !MathUtilities.IsZero(radiusX) || !MathUtilities.IsZero(radiusY); if (brush != null) { From c73e777ed2755e226a4b16b71ff25e27fbb26c81 Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Wed, 10 Jun 2020 18:53:27 +0300 Subject: [PATCH 3/5] Return hack with double.Epsilon --- src/Avalonia.Base/Properties/AssemblyInfo.cs | 1 - src/Avalonia.Base/Utilities/MathUtilities.cs | 2 +- src/Avalonia.Controls/Slider.cs | 3 +- .../Utilities/MathUtilitiesTests.cs | 29 +++++++++++++++++++ 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Base/Properties/AssemblyInfo.cs b/src/Avalonia.Base/Properties/AssemblyInfo.cs index fd5e411850..0664f22dcb 100644 --- a/src/Avalonia.Base/Properties/AssemblyInfo.cs +++ b/src/Avalonia.Base/Properties/AssemblyInfo.cs @@ -8,4 +8,3 @@ using Avalonia.Metadata; [assembly: InternalsVisibleTo("Avalonia.Base.UnitTests")] [assembly: InternalsVisibleTo("Avalonia.UnitTests")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] -[assembly: InternalsVisibleTo("Avalonia.Controls")] diff --git a/src/Avalonia.Base/Utilities/MathUtilities.cs b/src/Avalonia.Base/Utilities/MathUtilities.cs index 6da695407b..3fb5f7a162 100644 --- a/src/Avalonia.Base/Utilities/MathUtilities.cs +++ b/src/Avalonia.Base/Utilities/MathUtilities.cs @@ -9,7 +9,7 @@ namespace Avalonia.Utilities public static class MathUtilities { // smallest such that 1.0+DoubleEpsilon != 1.0 - internal static readonly double DoubleEpsilon = 2.2204460492503131e-016; + private const double DoubleEpsilon = 2.2204460492503131e-016; /// /// AreClose - Returns whether or not two doubles are "close". That is, whether or diff --git a/src/Avalonia.Controls/Slider.cs b/src/Avalonia.Controls/Slider.cs index 6cebe32322..64378a4eb2 100644 --- a/src/Avalonia.Controls/Slider.cs +++ b/src/Avalonia.Controls/Slider.cs @@ -193,7 +193,8 @@ namespace Avalonia.Controls var orient = Orientation == Orientation.Horizontal; var pointDen = orient ? _track.Bounds.Width : _track.Bounds.Height; - pointDen += MathUtilities.DoubleEpsilon; // Just add epsilon to avoid divide by zero exceptions. + // Just add epsilon to avoid NaN in case 0/0 + pointDen += double.Epsilon; var pointNum = orient ? x.Position.X : x.Position.Y; var logicalPos = MathUtilities.Clamp(pointNum / pointDen, 0.0d, 1.0d); diff --git a/tests/Avalonia.Base.UnitTests/Utilities/MathUtilitiesTests.cs b/tests/Avalonia.Base.UnitTests/Utilities/MathUtilitiesTests.cs index 708e703a1d..a36b22fee2 100644 --- a/tests/Avalonia.Base.UnitTests/Utilities/MathUtilitiesTests.cs +++ b/tests/Avalonia.Base.UnitTests/Utilities/MathUtilitiesTests.cs @@ -1,3 +1,4 @@ +using System; using Avalonia.Utilities; using Xunit; @@ -18,6 +19,7 @@ namespace Avalonia.Base.UnitTests.Utilities var actual = MathUtilities.AreClose(x, y); Assert.True(actual); + Assert.Equal(x, Math.Round(y, 14)); } [Fact] @@ -32,6 +34,7 @@ namespace Avalonia.Base.UnitTests.Utilities var actual = MathUtilities.IsOne(x); Assert.True(actual); + Assert.Equal(1.0, Math.Round(x, 15)); } [Fact] @@ -46,6 +49,32 @@ namespace Avalonia.Base.UnitTests.Utilities var actual = MathUtilities.IsZero(x); Assert.True(actual); + Assert.Equal(0.0, Math.Round(x, 15)); + } + + [Fact] + public void Clamp_Input_NaN_Return_NaN() + { + var clamp = MathUtilities.Clamp(double.NaN, 0.0, 1.0); + Assert.True(double.IsNaN(clamp)); + } + + [Fact] + public void Clamp_Input_NegativeInfinity_Return_Min() + { + const double min = 0.0; + const double max = 1.0; + var actual = MathUtilities.Clamp(double.NegativeInfinity, min, max); + Assert.Equal(min, actual); + } + + [Fact] + public void Clamp_Input_PositiveInfinity_Return_Max() + { + const double min = 0.0; + const double max = 1.0; + var actual = MathUtilities.Clamp(double.PositiveInfinity, min, max); + Assert.Equal(max, actual); } } } From c3fca8325f34ed240069db256ce9081e13c0b20a Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Thu, 11 Jun 2020 20:21:07 +0300 Subject: [PATCH 4/5] Add math utilities to compare single precision --- src/Avalonia.Base/Utilities/MathUtilities.cs | 85 ++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/src/Avalonia.Base/Utilities/MathUtilities.cs b/src/Avalonia.Base/Utilities/MathUtilities.cs index 3fb5f7a162..7949a62949 100644 --- a/src/Avalonia.Base/Utilities/MathUtilities.cs +++ b/src/Avalonia.Base/Utilities/MathUtilities.cs @@ -11,6 +11,8 @@ namespace Avalonia.Utilities // smallest such that 1.0+DoubleEpsilon != 1.0 private const double DoubleEpsilon = 2.2204460492503131e-016; + private const float FloatEpsilon = 1.192092896e-07F; + /// /// AreClose - Returns whether or not two doubles are "close". That is, whether or /// not they are within epsilon of each other. @@ -26,6 +28,21 @@ namespace Avalonia.Utilities return (-eps < delta) && (eps > delta); } + /// + /// AreClose - Returns whether or not two floats are "close". That is, whether or + /// not they are within epsilon of each other. + /// + /// The first float to compare. + /// The second float to compare. + public static bool AreClose(float value1, float value2) + { + //in case they are Infinities (then epsilon check does not work) + if (value1 == value2) return true; + float eps = (Math.Abs(value1) + Math.Abs(value2) + 10.0f) * FloatEpsilon; + float delta = value1 - value2; + return (-eps < delta) && (eps > delta); + } + /// /// LessThan - Returns whether or not the first double is less than the second double. /// That is, whether or not the first is strictly less than *and* not within epsilon of @@ -38,6 +55,18 @@ namespace Avalonia.Utilities return (value1 < value2) && !AreClose(value1, value2); } + /// + /// LessThan - Returns whether or not the first float is less than the second float. + /// That is, whether or not the first is strictly less than *and* not within epsilon of + /// the other number. + /// + /// The first single float to compare. + /// The second single float to compare. + public static bool LessThan(float value1, float value2) + { + return (value1 < value2) && !AreClose(value1, value2); + } + /// /// GreaterThan - Returns whether or not the first double is greater than the second double. /// That is, whether or not the first is strictly greater than *and* not within epsilon of @@ -50,6 +79,18 @@ namespace Avalonia.Utilities return (value1 > value2) && !AreClose(value1, value2); } + /// + /// GreaterThan - Returns whether or not the first float is greater than the second float. + /// That is, whether or not the first is strictly greater than *and* not within epsilon of + /// the other number. + /// + /// The first float to compare. + /// The second float to compare. + public static bool GreaterThan(float value1, float value2) + { + return (value1 > value2) && !AreClose(value1, value2); + } + /// /// LessThanOrClose - Returns whether or not the first double is less than or close to /// the second double. That is, whether or not the first is strictly less than or within @@ -62,6 +103,18 @@ namespace Avalonia.Utilities return (value1 < value2) || AreClose(value1, value2); } + /// + /// LessThanOrClose - Returns whether or not the first float is less than or close to + /// the second float. That is, whether or not the first is strictly less than or within + /// epsilon of the other number. + /// + /// The first float to compare. + /// The second float to compare. + public static bool LessThanOrClose(float value1, float value2) + { + return (value1 < value2) || AreClose(value1, value2); + } + /// /// GreaterThanOrClose - Returns whether or not the first double is greater than or close to /// the second double. That is, whether or not the first is strictly greater than or within @@ -74,6 +127,18 @@ namespace Avalonia.Utilities return (value1 > value2) || AreClose(value1, value2); } + /// + /// GreaterThanOrClose - Returns whether or not the first float is greater than or close to + /// the second float. That is, whether or not the first is strictly greater than or within + /// epsilon of the other number. + /// + /// The first float to compare. + /// The second float to compare. + public static bool GreaterThanOrClose(float value1, float value2) + { + return (value1 > value2) || AreClose(value1, value2); + } + /// /// IsOne - Returns whether or not the double is "close" to 1. Same as AreClose(double, 1), /// but this is faster. @@ -84,6 +149,16 @@ namespace Avalonia.Utilities return Math.Abs(value - 1.0) < 10.0 * DoubleEpsilon; } + /// + /// IsOne - Returns whether or not the float is "close" to 1. Same as AreClose(float, 1), + /// but this is faster. + /// + /// The float to compare to 1. + public static bool IsOne(float value) + { + return Math.Abs(value - 1.0f) < 10.0f * FloatEpsilon; + } + /// /// IsZero - Returns whether or not the double is "close" to 0. Same as AreClose(double, 0), /// but this is faster. @@ -94,6 +169,16 @@ namespace Avalonia.Utilities return Math.Abs(value) < 10.0 * DoubleEpsilon; } + /// + /// IsZero - Returns whether or not the float is "close" to 0. Same as AreClose(float, 0), + /// but this is faster. + /// + /// The float to compare to 0. + public static bool IsZero(float value) + { + return Math.Abs(value) < 10.0f * FloatEpsilon; + } + /// /// Clamps a value between a minimum and maximum value. /// From c4668f5ef3f57c639085a35b32e663ba78f5a440 Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Thu, 11 Jun 2020 23:43:52 +0300 Subject: [PATCH 5/5] Add tests for float math utils --- .../Utilities/MathUtilitiesTests.cs | 85 ++++++++++++++----- 1 file changed, 62 insertions(+), 23 deletions(-) diff --git a/tests/Avalonia.Base.UnitTests/Utilities/MathUtilitiesTests.cs b/tests/Avalonia.Base.UnitTests/Utilities/MathUtilitiesTests.cs index a36b22fee2..0378a5b017 100644 --- a/tests/Avalonia.Base.UnitTests/Utilities/MathUtilitiesTests.cs +++ b/tests/Avalonia.Base.UnitTests/Utilities/MathUtilitiesTests.cs @@ -6,50 +6,89 @@ namespace Avalonia.Base.UnitTests.Utilities { public class MathUtilitiesTests { - [Fact] - public void Two_Equivalent_Double_Values_Are_Close() + private const double AnyValue = 42.42; + private readonly double _calculatedAnyValue; + private readonly double _one; + private readonly double _zero; + + public MathUtilitiesTests() { + _calculatedAnyValue = 0.0; + _one = 0.0; + _zero = 1.0; + const int N = 10; - var x = 42.42; - var y = 0.0; - var dx = x / N; + var dxAny = AnyValue / N; + var dxOne = 1.0 / N; + var dxZero = _zero / N; for (var i = 0; i < N; ++i) - y += dx; - var actual = MathUtilities.AreClose(x, y); + { + _calculatedAnyValue += dxAny; + _one += dxOne; + _zero -= dxZero; + } + } + + [Fact] + public void Two_Equivalent_Double_Values_Are_Close() + { + var actual = MathUtilities.AreClose(AnyValue, _calculatedAnyValue); Assert.True(actual); - Assert.Equal(x, Math.Round(y, 14)); + Assert.Equal(AnyValue, Math.Round(_calculatedAnyValue, 14)); + } + + [Fact] + public void Two_Equivalent_Single_Values_Are_Close() + { + var expectedValue = (float)AnyValue; + var actualValue = (float)_calculatedAnyValue; + + var actual = MathUtilities.AreClose(expectedValue, actualValue); + + Assert.True(actual); + Assert.Equal((float) Math.Round(expectedValue, 5), (float) Math.Round(actualValue, 4)); } [Fact] public void Calculated_Double_One_Is_One() { - const int N = 10; - var dx = 1.0 / N; - var x = 0.0; + var actual = MathUtilities.IsOne(_one); + + Assert.True(actual); + Assert.Equal(1.0, Math.Round(_one, 15)); + } + + [Fact] + public void Calculated_Single_One_Is_One() + { + var actualValue = (float)_one; - for (var i = 0; i < N; ++i) - x += dx; - var actual = MathUtilities.IsOne(x); + var actual = MathUtilities.IsOne(actualValue); Assert.True(actual); - Assert.Equal(1.0, Math.Round(x, 15)); + Assert.Equal(1.0f, (float) Math.Round(actualValue, 7)); } [Fact] public void Calculated_Double_Zero_Is_Zero() { - const int N = 10; - var x = 1.0; - var dx = x / N; - - for (var i = 0; i < N; ++i) - x -= dx; - var actual = MathUtilities.IsZero(x); + var actual = MathUtilities.IsZero(_zero); + + Assert.True(actual); + Assert.Equal(0.0, Math.Round(_zero, 15)); + } + + [Fact] + public void Calculated_Single_Zero_Is_Zero() + { + var actualValue = (float)_zero; + + var actual = MathUtilities.IsZero(actualValue); Assert.True(actual); - Assert.Equal(0.0, Math.Round(x, 15)); + Assert.Equal(0.0f, (float) Math.Round(actualValue, 7)); } [Fact]