Browse Source

Merge pull request #4104 from rstm-sf/refactor/remove_duplicate_math_utils

Remove duplicate code for MathUtilities
Dariusz Komosiński 5 years ago
parent
commit
c8c4d1bf0a

+ 1 - 0
src/Avalonia.Base/Properties/AssemblyInfo.cs

@@ -8,3 +8,4 @@ using Avalonia.Metadata;
 [assembly: InternalsVisibleTo("Avalonia.Base.UnitTests")]
 [assembly: InternalsVisibleTo("Avalonia.UnitTests")]
 [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
+[assembly: InternalsVisibleTo("Avalonia.Controls.DataGrid")]

+ 13 - 4
src/Avalonia.Base/Utilities/MathUtilities.cs

@@ -1,5 +1,4 @@
 using System;
-using System.Runtime.InteropServices;
 
 namespace Avalonia.Utilities
 {
@@ -9,7 +8,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;
 
         private const float FloatEpsilon = 1.192092896e-07F;
 
@@ -188,6 +187,11 @@ namespace Avalonia.Utilities
         /// <returns>The clamped value.</returns>
         public static double Clamp(double val, double min, double max)
         {
+            if (min > max)
+            {
+                ThrowCannotBeGreaterThanException(min, max);
+            }
+
             if (val < min)
             {
                 return min;
@@ -216,7 +220,7 @@ namespace Avalonia.Utilities
             double newValue;
 
             // If DPI == 1, don't use DPI-aware rounding.
-            if (!MathUtilities.AreClose(dpiScale, 1.0))
+            if (!MathUtilities.IsOne(dpiScale))
             {
                 newValue = Math.Round(value * dpiScale) / dpiScale;
                 // If rounding produces a value unacceptable to layout (NaN, Infinity or MaxValue), use the original value.
@@ -246,7 +250,7 @@ namespace Avalonia.Utilities
         {
             if (min > max)
             {
-                throw new ArgumentException($"{min} cannot be greater than {max}.");
+                ThrowCannotBeGreaterThanException(min, max);
             }
 
             if (val < min)
@@ -262,5 +266,10 @@ namespace Avalonia.Utilities
                 return val;
             }
         }
+
+        private static void ThrowCannotBeGreaterThanException(double min, double max)
+        {
+            throw new ArgumentException($"{min} cannot be greater than {max}.");
+        }
     }
 }

+ 19 - 19
src/Avalonia.Controls.DataGrid/DataGrid.cs

@@ -2681,7 +2681,7 @@ namespace Avalonia.Controls
             {
                 return;
             }
-            Debug.Assert(DoubleUtil.LessThanOrClose(_vScrollBar.Value, _vScrollBar.Maximum));
+            Debug.Assert(MathUtilities.LessThanOrClose(_vScrollBar.Value, _vScrollBar.Maximum));
 
             _verticalScrollChangesIgnored++;
             try
@@ -2698,7 +2698,7 @@ namespace Avalonia.Controls
                 }
                 else if (scrollEventType == ScrollEventType.SmallDecrement)
                 {
-                    if (DoubleUtil.GreaterThan(NegVerticalOffset, 0))
+                    if (MathUtilities.GreaterThan(NegVerticalOffset, 0))
                     {
                         DisplayData.PendingVerticalScrollHeight -= NegVerticalOffset;
                     }
@@ -2717,7 +2717,7 @@ namespace Avalonia.Controls
                     DisplayData.PendingVerticalScrollHeight = _vScrollBar.Value - _verticalOffset;
                 }
 
-                if (!DoubleUtil.IsZero(DisplayData.PendingVerticalScrollHeight))
+                if (!MathUtilities.IsZero(DisplayData.PendingVerticalScrollHeight))
                 {
                     // Invalidate so the scroll happens on idle
                     InvalidateRowsMeasure(invalidateIndividualElements: false);
@@ -3346,22 +3346,22 @@ namespace Avalonia.Controls
                 bool needHorizScrollbarWithoutVertScrollbar = false;
 
                 if (allowHorizScrollbar &&
-                    DoubleUtil.GreaterThan(totalVisibleWidth, cellsWidth) &&
-                    DoubleUtil.LessThan(totalVisibleFrozenWidth, cellsWidth) &&
-                    DoubleUtil.LessThanOrClose(horizScrollBarHeight, cellsHeight))
+                    MathUtilities.GreaterThan(totalVisibleWidth, cellsWidth) &&
+                    MathUtilities.LessThan(totalVisibleFrozenWidth, cellsWidth) &&
+                    MathUtilities.LessThanOrClose(horizScrollBarHeight, cellsHeight))
                 {
                     double oldDataHeight = cellsHeight;
                     cellsHeight -= horizScrollBarHeight;
                     Debug.Assert(cellsHeight >= 0);
                     needHorizScrollbarWithoutVertScrollbar = needHorizScrollbar = true;
-                    if (allowVertScrollbar && (DoubleUtil.LessThanOrClose(totalVisibleWidth - cellsWidth, vertScrollBarWidth) ||
-                        DoubleUtil.LessThanOrClose(cellsWidth - totalVisibleFrozenWidth, vertScrollBarWidth)))
+                    if (allowVertScrollbar && (MathUtilities.LessThanOrClose(totalVisibleWidth - cellsWidth, vertScrollBarWidth) ||
+                        MathUtilities.LessThanOrClose(cellsWidth - totalVisibleFrozenWidth, vertScrollBarWidth)))
                     {
                         // Would we still need a horizontal scrollbar without the vertical one?
                         UpdateDisplayedRows(DisplayData.FirstScrollingSlot, cellsHeight);
                         if (DisplayData.NumTotallyDisplayedScrollingElements != VisibleSlotCount)
                         {
-                            needHorizScrollbar = DoubleUtil.LessThan(totalVisibleFrozenWidth, cellsWidth - vertScrollBarWidth);
+                            needHorizScrollbar = MathUtilities.LessThan(totalVisibleFrozenWidth, cellsWidth - vertScrollBarWidth);
                         }
                     }
 
@@ -3374,8 +3374,8 @@ namespace Avalonia.Controls
 
                 UpdateDisplayedRows(DisplayData.FirstScrollingSlot, cellsHeight);
                 if (allowVertScrollbar &&
-                    DoubleUtil.GreaterThan(cellsHeight, 0) &&
-                    DoubleUtil.LessThanOrClose(vertScrollBarWidth, cellsWidth) &&
+                    MathUtilities.GreaterThan(cellsHeight, 0) &&
+                    MathUtilities.LessThanOrClose(vertScrollBarWidth, cellsWidth) &&
                     DisplayData.NumTotallyDisplayedScrollingElements != VisibleSlotCount)
                 {
                     cellsWidth -= vertScrollBarWidth;
@@ -3389,9 +3389,9 @@ namespace Avalonia.Controls
 
                 if (allowHorizScrollbar &&
                     needVertScrollbar && !needHorizScrollbar &&
-                    DoubleUtil.GreaterThan(totalVisibleWidth, cellsWidth) &&
-                    DoubleUtil.LessThan(totalVisibleFrozenWidth, cellsWidth) &&
-                    DoubleUtil.LessThanOrClose(horizScrollBarHeight, cellsHeight))
+                    MathUtilities.GreaterThan(totalVisibleWidth, cellsWidth) &&
+                    MathUtilities.LessThan(totalVisibleFrozenWidth, cellsWidth) &&
+                    MathUtilities.LessThanOrClose(horizScrollBarHeight, cellsHeight))
                 {
                     cellsWidth += vertScrollBarWidth;
                     cellsHeight -= horizScrollBarHeight;
@@ -3422,7 +3422,7 @@ namespace Avalonia.Controls
                 if (allowVertScrollbar)
                 {
                     if (cellsHeight > 0 &&
-                        DoubleUtil.LessThanOrClose(vertScrollBarWidth, cellsWidth) &&
+                        MathUtilities.LessThanOrClose(vertScrollBarWidth, cellsWidth) &&
                         DisplayData.NumTotallyDisplayedScrollingElements != VisibleSlotCount)
                     {
                         cellsWidth -= vertScrollBarWidth;
@@ -3439,9 +3439,9 @@ namespace Avalonia.Controls
                 if (allowHorizScrollbar)
                 {
                     if (cellsWidth > 0 &&
-                        DoubleUtil.LessThanOrClose(horizScrollBarHeight, cellsHeight) &&
-                        DoubleUtil.GreaterThan(totalVisibleWidth, cellsWidth) &&
-                        DoubleUtil.LessThan(totalVisibleFrozenWidth, cellsWidth))
+                        MathUtilities.LessThanOrClose(horizScrollBarHeight, cellsHeight) &&
+                        MathUtilities.GreaterThan(totalVisibleWidth, cellsWidth) &&
+                        MathUtilities.LessThan(totalVisibleFrozenWidth, cellsWidth))
                     {
                         cellsHeight -= horizScrollBarHeight;
                         Debug.Assert(cellsHeight >= 0);
@@ -5387,7 +5387,7 @@ namespace Avalonia.Controls
         private void SetVerticalOffset(double newVerticalOffset)
         {
             _verticalOffset = newVerticalOffset;
-            if (_vScrollBar != null && !DoubleUtil.AreClose(newVerticalOffset, _vScrollBar.Value))
+            if (_vScrollBar != null && !MathUtilities.AreClose(newVerticalOffset, _vScrollBar.Value))
             {
                 _vScrollBar.Value = _verticalOffset;
             }

+ 1 - 1
src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs

@@ -301,7 +301,7 @@ namespace Avalonia.Controls
         private static bool CanResizeColumn(DataGridColumn column)
         {
             if (column.OwningGrid != null && column.OwningGrid.ColumnsInternal != null && column.OwningGrid.UsesStarSizing &&
-                (column.OwningGrid.ColumnsInternal.LastVisibleColumn == column || !DoubleUtil.AreClose(column.OwningGrid.ColumnsInternal.VisibleEdgedColumnsWidth, column.OwningGrid.CellsWidth)))
+                (column.OwningGrid.ColumnsInternal.LastVisibleColumn == column || !MathUtilities.AreClose(column.OwningGrid.ColumnsInternal.VisibleEdgedColumnsWidth, column.OwningGrid.CellsWidth)))
             {
                 return false;
             }

+ 8 - 8
src/Avalonia.Controls.DataGrid/DataGridColumns.cs

@@ -44,7 +44,7 @@ namespace Avalonia.Controls
         /// <returns>The remaining amount of adjustment.</returns>
         internal double AdjustColumnWidths(int displayIndex, double amount, bool userInitiated)
         {
-            if (!DoubleUtil.IsZero(amount))
+            if (!MathUtilities.IsZero(amount))
             {
                 if (amount < 0)
                 {
@@ -777,7 +777,7 @@ namespace Avalonia.Controls
         private double AdjustStarColumnWidths(int displayIndex, double adjustment, bool userInitiated)
         {
             double remainingAdjustment = adjustment;
-            if (DoubleUtil.IsZero(remainingAdjustment))
+            if (MathUtilities.IsZero(remainingAdjustment))
             {
                 return remainingAdjustment;
             }
@@ -843,7 +843,7 @@ namespace Avalonia.Controls
         /// <returns>The remaining amount of adjustment.</returns>
         private double AdjustStarColumnWidths(int displayIndex, double remainingAdjustment, bool userInitiated, Func<DataGridColumn, double> targetWidth)
         {
-            if (DoubleUtil.IsZero(remainingAdjustment))
+            if (MathUtilities.IsZero(remainingAdjustment))
             {
                 return remainingAdjustment;
             }
@@ -1244,7 +1244,7 @@ namespace Avalonia.Controls
             Debug.Assert(amount < 0);
             Debug.Assert(column.Width.UnitType != DataGridLengthUnitType.Star);
 
-            if (DoubleUtil.GreaterThanOrClose(targetWidth, column.Width.DisplayValue))
+            if (MathUtilities.GreaterThanOrClose(targetWidth, column.Width.DisplayValue))
             {
                 return amount;
             }
@@ -1271,7 +1271,7 @@ namespace Avalonia.Controls
         /// <returns>The remaining amount of adjustment.</returns>
         private double DecreaseNonStarColumnWidths(int displayIndex, Func<DataGridColumn, double> targetWidth, double amount, bool reverse, bool affectNewColumns)
         {
-            if (DoubleUtil.GreaterThanOrClose(amount, 0))
+            if (MathUtilities.GreaterThanOrClose(amount, 0))
             {
                 return amount;
             }
@@ -1285,7 +1285,7 @@ namespace Avalonia.Controls
                     (affectNewColumns || column.IsInitialDesiredWidthDetermined)))
             {
                 amount = DecreaseNonStarColumnWidth(column, Math.Max(column.ActualMinWidth, targetWidth(column)), amount);
-                if (DoubleUtil.IsZero(amount))
+                if (MathUtilities.IsZero(amount))
                 {
                     break;
                 }
@@ -1392,7 +1392,7 @@ namespace Avalonia.Controls
         /// <returns>The remaining amount of adjustment.</returns>
         private double IncreaseNonStarColumnWidths(int displayIndex, Func<DataGridColumn, double> targetWidth, double amount, bool reverse, bool affectNewColumns)
         {
-            if (DoubleUtil.LessThanOrClose(amount, 0))
+            if (MathUtilities.LessThanOrClose(amount, 0))
             {
                 return amount;
             }
@@ -1406,7 +1406,7 @@ namespace Avalonia.Controls
                     (affectNewColumns || column.IsInitialDesiredWidthDetermined)))
             {
                 amount = IncreaseNonStarColumnWidth(column, Math.Min(column.ActualMaxWidth, targetWidth(column)), amount);
-                if (DoubleUtil.IsZero(amount))
+                if (MathUtilities.IsZero(amount))
                 {
                     break;
                 }

+ 1 - 1
src/Avalonia.Controls.DataGrid/DataGridLength.cs

@@ -529,7 +529,7 @@ namespace Avalonia.Controls
                     //  in this case drop value part and print only "Star"
                     case DataGridLengthUnitType.Star:
                         return (
-                            DoubleUtil.AreClose(1.0, dataGridLength.Value.Value)
+                            MathUtilities.AreClose(1.0, dataGridLength.Value.Value)
                             ? _starSuffix
                             : Convert.ToString(dataGridLength.Value.Value, culture ?? CultureInfo.CurrentCulture) + DataGridLengthConverter._starSuffix);
 

+ 1 - 1
src/Avalonia.Controls.DataGrid/DataGridRow.cs

@@ -879,7 +879,7 @@ namespace Avalonia.Controls
                 && (double.IsNaN(_detailsContent.Height))
                 && (AreDetailsVisible)
                 && (!double.IsNaN(_detailsDesiredHeight))
-                && !DoubleUtil.AreClose(_detailsContent.Bounds.Inflate(_detailsContent.Margin).Height, _detailsDesiredHeight)
+                && !MathUtilities.AreClose(_detailsContent.Bounds.Inflate(_detailsContent.Margin).Height, _detailsDesiredHeight)
                 && Slot != -1)
             {
                 _detailsDesiredHeight = _detailsContent.Bounds.Inflate(_detailsContent.Margin).Height;

+ 30 - 30
src/Avalonia.Controls.DataGrid/DataGridRows.cs

@@ -329,7 +329,7 @@ namespace Avalonia.Controls
 
         internal void OnRowsMeasure()
         {
-            if (!DoubleUtil.IsZero(DisplayData.PendingVerticalScrollHeight))
+            if (!MathUtilities.IsZero(DisplayData.PendingVerticalScrollHeight))
             {
                 ScrollSlotsByHeight(DisplayData.PendingVerticalScrollHeight);
                 DisplayData.PendingVerticalScrollHeight = 0;
@@ -432,7 +432,7 @@ namespace Avalonia.Controls
             }
             else if (DisplayData.FirstScrollingSlot == slot && slot != -1)
             {
-                if (!DoubleUtil.IsZero(NegVerticalOffset))
+                if (!MathUtilities.IsZero(NegVerticalOffset))
                 {
                     // First displayed row is partially scrolled of. Let's scroll it so that NegVerticalOffset becomes 0.
                     DisplayData.PendingVerticalScrollHeight = -NegVerticalOffset;
@@ -447,7 +447,7 @@ namespace Avalonia.Controls
             {
                 // Scroll up to the new row so it becomes the first displayed row
                 firstFullSlot = DisplayData.FirstScrollingSlot - 1;
-                if (DoubleUtil.GreaterThan(NegVerticalOffset, 0))
+                if (MathUtilities.GreaterThan(NegVerticalOffset, 0))
                 {
                     deltaY = -NegVerticalOffset;
                 }
@@ -470,7 +470,7 @@ namespace Avalonia.Controls
                 // Figure out how much of the last row is cut off
                 double rowHeight = GetExactSlotElementHeight(DisplayData.LastScrollingSlot);
                 double availableHeight = AvailableSlotElementRoom + rowHeight;
-                if (DoubleUtil.AreClose(rowHeight, availableHeight))
+                if (MathUtilities.AreClose(rowHeight, availableHeight))
                 {
                     if (DisplayData.LastScrollingSlot == slot)
                     {
@@ -499,7 +499,7 @@ namespace Avalonia.Controls
                 {
                     ResetDisplayedRows();
                 }
-                if (DoubleUtil.GreaterThanOrClose(GetExactSlotElementHeight(slot), CellsHeight))
+                if (MathUtilities.GreaterThanOrClose(GetExactSlotElementHeight(slot), CellsHeight))
                 {
                     // The entire row won't fit in the DataGrid so we start showing it from the top
                     NegVerticalOffset = 0;
@@ -519,7 +519,7 @@ namespace Avalonia.Controls
             }
 
             // 
-            Debug.Assert(DoubleUtil.LessThanOrClose(NegVerticalOffset, _verticalOffset));
+            Debug.Assert(MathUtilities.LessThanOrClose(NegVerticalOffset, _verticalOffset));
 
             SetVerticalOffset(_verticalOffset);
 
@@ -1660,7 +1660,7 @@ namespace Avalonia.Controls
         private void ScrollSlotsByHeight(double height)
         {
             Debug.Assert(DisplayData.FirstScrollingSlot >= 0);
-            Debug.Assert(!DoubleUtil.IsZero(height));
+            Debug.Assert(!MathUtilities.IsZero(height));
 
             _scrollingByHeight = true;
             try
@@ -1672,7 +1672,7 @@ namespace Avalonia.Controls
                 {
                     // Scrolling Down
                     int lastVisibleSlot = GetPreviousVisibleSlot(SlotCount);
-                    if (_vScrollBar != null && DoubleUtil.AreClose(_vScrollBar.Maximum, newVerticalOffset))
+                    if (_vScrollBar != null && MathUtilities.AreClose(_vScrollBar.Maximum, newVerticalOffset))
                     {
                         // We've scrolled to the bottom of the ScrollBar, automatically place the user at the very bottom
                         // of the DataGrid.  If this produces very odd behavior, evaluate the coping strategy used by
@@ -1684,7 +1684,7 @@ namespace Avalonia.Controls
                     else
                     {
                         deltaY = GetSlotElementHeight(newFirstScrollingSlot) - NegVerticalOffset;
-                        if (DoubleUtil.LessThan(height, deltaY))
+                        if (MathUtilities.LessThan(height, deltaY))
                         {
                             // We've merely covered up more of the same row we're on
                             NegVerticalOffset += height;
@@ -1707,7 +1707,7 @@ namespace Avalonia.Controls
                             }
                             else
                             {
-                                while (DoubleUtil.LessThanOrClose(deltaY, height))
+                                while (MathUtilities.LessThanOrClose(deltaY, height))
                                 {
                                     if (newFirstScrollingSlot < lastVisibleSlot)
                                     {
@@ -1727,7 +1727,7 @@ namespace Avalonia.Controls
 
                                     double rowHeight = GetExactSlotElementHeight(newFirstScrollingSlot);
                                     double remainingHeight = height - deltaY;
-                                    if (DoubleUtil.LessThanOrClose(rowHeight, remainingHeight))
+                                    if (MathUtilities.LessThanOrClose(rowHeight, remainingHeight))
                                     {
                                         deltaY += rowHeight;
                                     }
@@ -1744,7 +1744,7 @@ namespace Avalonia.Controls
                 else
                 {
                     // Scrolling Up
-                    if (DoubleUtil.GreaterThanOrClose(height + NegVerticalOffset, 0))
+                    if (MathUtilities.GreaterThanOrClose(height + NegVerticalOffset, 0))
                     {
                         // We've merely exposing more of the row we're on
                         NegVerticalOffset += height;
@@ -1778,7 +1778,7 @@ namespace Avalonia.Controls
                         else
                         {
                             int lastScrollingSlot = DisplayData.LastScrollingSlot;
-                            while (DoubleUtil.GreaterThan(deltaY, height))
+                            while (MathUtilities.GreaterThan(deltaY, height))
                             {
                                 if (newFirstScrollingSlot > 0)
                                 {
@@ -1797,7 +1797,7 @@ namespace Avalonia.Controls
                                 }
                                 double rowHeight = GetExactSlotElementHeight(newFirstScrollingSlot);
                                 double remainingHeight = height - deltaY;
-                                if (DoubleUtil.LessThanOrClose(rowHeight + remainingHeight, 0))
+                                if (MathUtilities.LessThanOrClose(rowHeight + remainingHeight, 0))
                                 {
                                     deltaY -= rowHeight;
                                 }
@@ -1809,7 +1809,7 @@ namespace Avalonia.Controls
                             }
                         }
                     }
-                    if (DoubleUtil.GreaterThanOrClose(0, newVerticalOffset) && newFirstScrollingSlot != 0)
+                    if (MathUtilities.GreaterThanOrClose(0, newVerticalOffset) && newFirstScrollingSlot != 0)
                     {
                         // We've scrolled to the top of the ScrollBar, automatically place the user at the very top
                         // of the DataGrid.  If this produces very odd behavior, evaluate the RowHeight estimate.  
@@ -1822,7 +1822,7 @@ namespace Avalonia.Controls
                 }
 
                 double firstRowHeight = GetExactSlotElementHeight(newFirstScrollingSlot);
-                if (DoubleUtil.LessThan(firstRowHeight, NegVerticalOffset))
+                if (MathUtilities.LessThan(firstRowHeight, NegVerticalOffset))
                 {
                     // We've scrolled off more of the first row than what's possible.  This can happen
                     // if the first row got shorter (Ex: Collpasing RowDetails) or if the user has a recycling
@@ -1838,11 +1838,11 @@ namespace Avalonia.Controls
                 UpdateDisplayedRows(newFirstScrollingSlot, CellsHeight);
 
                 double firstElementHeight = GetExactSlotElementHeight(DisplayData.FirstScrollingSlot);
-                if (DoubleUtil.GreaterThan(NegVerticalOffset, firstElementHeight))
+                if (MathUtilities.GreaterThan(NegVerticalOffset, firstElementHeight))
                 {
                     int firstElementSlot = DisplayData.FirstScrollingSlot;
                     // We filled in some rows at the top and now we have a NegVerticalOffset that's greater than the first element
-                    while (newFirstScrollingSlot > 0 && DoubleUtil.GreaterThan(NegVerticalOffset, firstElementHeight))
+                    while (newFirstScrollingSlot > 0 && MathUtilities.GreaterThan(NegVerticalOffset, firstElementHeight))
                     {
                         int previousSlot = GetPreviousVisibleSlot(firstElementSlot);
                         if (previousSlot == -1)
@@ -1872,7 +1872,7 @@ namespace Avalonia.Controls
                 {
                     _verticalOffset = NegVerticalOffset;
                 }
-                else if (DoubleUtil.GreaterThan(NegVerticalOffset, newVerticalOffset))
+                else if (MathUtilities.GreaterThan(NegVerticalOffset, newVerticalOffset))
                 {
                     // The scrolled-in row was larger than anticipated. Adjust the DataGrid so the ScrollBar thumb
                     // can stay in the same place
@@ -1890,8 +1890,8 @@ namespace Avalonia.Controls
 
                 DisplayData.FullyRecycleElements();
 
-                Debug.Assert(DoubleUtil.GreaterThanOrClose(NegVerticalOffset, 0));
-                Debug.Assert(DoubleUtil.GreaterThanOrClose(_verticalOffset, NegVerticalOffset));
+                Debug.Assert(MathUtilities.GreaterThanOrClose(NegVerticalOffset, 0));
+                Debug.Assert(MathUtilities.GreaterThanOrClose(_verticalOffset, NegVerticalOffset));
             }
             finally
             {
@@ -2032,7 +2032,7 @@ namespace Avalonia.Controls
             double deltaY = -NegVerticalOffset;
             int visibleScrollingRows = 0;
 
-            if (DoubleUtil.LessThanOrClose(displayHeight, 0) || SlotCount == 0 || ColumnsItemsInternal.Count == 0)
+            if (MathUtilities.LessThanOrClose(displayHeight, 0) || SlotCount == 0 || ColumnsItemsInternal.Count == 0)
             {
                 return;
             }
@@ -2044,7 +2044,7 @@ namespace Avalonia.Controls
             }
 
             int slot = firstDisplayedScrollingSlot;
-            while (slot < SlotCount && !DoubleUtil.GreaterThanOrClose(deltaY, displayHeight))
+            while (slot < SlotCount && !MathUtilities.GreaterThanOrClose(deltaY, displayHeight))
             {
                 deltaY += GetExactSlotElementHeight(slot);
                 visibleScrollingRows++;
@@ -2052,7 +2052,7 @@ namespace Avalonia.Controls
                 slot = GetNextVisibleSlot(slot);
             }
 
-            while (DoubleUtil.LessThan(deltaY, displayHeight) && slot >= 0)
+            while (MathUtilities.LessThan(deltaY, displayHeight) && slot >= 0)
             {
                 slot = GetPreviousVisibleSlot(firstDisplayedScrollingSlot);
                 if (slot >= 0)
@@ -2063,14 +2063,14 @@ namespace Avalonia.Controls
                 }
             }
             // If we're up to the first row, and we still have room left, uncover as much of the first row as we can
-            if (firstDisplayedScrollingSlot == 0 && DoubleUtil.LessThan(deltaY, displayHeight))
+            if (firstDisplayedScrollingSlot == 0 && MathUtilities.LessThan(deltaY, displayHeight))
             {
                 double newNegVerticalOffset = Math.Max(0, NegVerticalOffset - displayHeight + deltaY);
                 deltaY += NegVerticalOffset - newNegVerticalOffset;
                 NegVerticalOffset = newNegVerticalOffset;
             }
 
-            if (DoubleUtil.GreaterThan(deltaY, displayHeight) || (DoubleUtil.AreClose(deltaY, displayHeight) && DoubleUtil.GreaterThan(NegVerticalOffset, 0)))
+            if (MathUtilities.GreaterThan(deltaY, displayHeight) || (MathUtilities.AreClose(deltaY, displayHeight) && MathUtilities.GreaterThan(NegVerticalOffset, 0)))
             {
                 DisplayData.NumTotallyDisplayedScrollingElements = visibleScrollingRows - 1;
             }
@@ -2108,7 +2108,7 @@ namespace Avalonia.Controls
             double deltaY = 0;
             int visibleScrollingRows = 0;
 
-            if (DoubleUtil.LessThanOrClose(displayHeight, 0) || SlotCount == 0 || ColumnsItemsInternal.Count == 0)
+            if (MathUtilities.LessThanOrClose(displayHeight, 0) || SlotCount == 0 || ColumnsItemsInternal.Count == 0)
             {
                 ResetDisplayedRows();
                 return;
@@ -2120,7 +2120,7 @@ namespace Avalonia.Controls
             }
 
             int slot = lastDisplayedScrollingRow;
-            while (DoubleUtil.LessThan(deltaY, displayHeight) && slot >= 0)
+            while (MathUtilities.LessThan(deltaY, displayHeight) && slot >= 0)
             {
                 deltaY += GetExactSlotElementHeight(slot);
                 visibleScrollingRows++;
@@ -2542,7 +2542,7 @@ namespace Avalonia.Controls
                             double heightChange = UpdateRowGroupVisibility(rowGroupInfo, isVisible, isDisplayed: false);
                             // Use epsilon instead of 0 here so that in the off chance that our estimates put the vertical offset negative
                             // the user can still scroll to the top since the offset is non-zero
-                            SetVerticalOffset(Math.Max(DoubleUtil.DBL_EPSILON, _verticalOffset + heightChange));
+                            SetVerticalOffset(Math.Max(MathUtilities.DoubleEpsilon, _verticalOffset + heightChange));
                         }
                         else
                         {
@@ -3024,4 +3024,4 @@ namespace Avalonia.Controls
         }
 #endif
     }
-}
+}

+ 3 - 3
src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs

@@ -310,9 +310,9 @@ namespace Avalonia.Controls.Primitives
             double leftEdge = column.IsFrozen ? frozenLeftEdge : scrollingLeftEdge;
             double rightEdge = leftEdge + column.ActualWidth;
             return 
-                DoubleUtil.GreaterThan(rightEdge, 0) &&
-                DoubleUtil.LessThanOrClose(leftEdge, OwningGrid.CellsWidth) &&
-                DoubleUtil.GreaterThan(rightEdge, frozenLeftEdge); // scrolling column covered up by frozen column(s)
+                MathUtilities.GreaterThan(rightEdge, 0) &&
+                MathUtilities.LessThanOrClose(leftEdge, OwningGrid.CellsWidth) &&
+                MathUtilities.GreaterThan(rightEdge, frozenLeftEdge); // scrolling column covered up by frozen column(s)
         }
     }
 }

+ 0 - 136
src/Avalonia.Controls.DataGrid/Utils/DoubleUtil.cs

@@ -1,136 +0,0 @@
-// (c) Copyright Microsoft Corporation.
-// This source is subject to the Microsoft Public License (Ms-PL).
-// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
-// All other rights reserved.
-
-using System;
-
-namespace Avalonia.Controls.Utils
-{
-    internal static class DoubleUtil
-    {
-        internal const double DBL_EPSILON = 1e-6;
-
-        /// <summary>
-        /// AreClose - Returns whether or not two doubles are "close".  That is, whether or 
-        /// not they are within epsilon of each other.  Note that this epsilon is proportional
-        /// to the numbers themselves to that AreClose survives scalar multiplication.
-        /// There are plenty of ways for this to return false even for numbers which
-        /// are theoretically identical, so no code calling this should fail to work if this 
-        /// returns false.  This is important enough to repeat:
-        /// NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be
-        /// used for optimizations *only*.
-        /// </summary>
-        /// <returns>
-        /// bool - the result of the AreClose comparison.
-        /// </returns>
-        /// <param name="value1"> The first double to compare. </param>
-        /// <param name="value2"> The second double to compare. </param>
-        public static bool AreClose(double value1, double value2)
-        {
-            //in case they are Infinities (then epsilon check does not work)
-            if (value1 == value2) return true;
-            // This computes (|value1-value2| / (|value1| + |value2| + 10.0)) < DBL_EPSILON
-            double eps = (Math.Abs(value1) + Math.Abs(value2) + 10.0) * DBL_EPSILON;
-            double delta = value1 - value2;
-            return (-eps < delta) && (eps > delta);
-        }
-
-        /// <summary>
-        /// 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
-        /// the other number.  Note that this epsilon is proportional to the numbers themselves
-        /// to that AreClose survives scalar multiplication.  Note,
-        /// There are plenty of ways for this to return false even for numbers which
-        /// are theoretically identical, so no code calling this should fail to work if this 
-        /// returns false.  This is important enough to repeat:
-        /// NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be
-        /// used for optimizations *only*.
-        /// </summary>
-        /// <returns>
-        /// bool - the result of the GreaterThan comparison.
-        /// </returns>
-        /// <param name="value1"> The first double to compare. </param>
-        /// <param name="value2"> The second double to compare. </param>
-        public static bool GreaterThan(double value1, double value2)
-        {
-            return (value1 > value2) && !AreClose(value1, value2);
-        }
-
-        /// <summary>
-        /// 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
-        /// epsilon of the other number.  Note that this epsilon is proportional to the numbers 
-        /// themselves to that AreClose survives scalar multiplication.  Note,
-        /// There are plenty of ways for this to return false even for numbers which
-        /// are theoretically identical, so no code calling this should fail to work if this 
-        /// returns false.  This is important enough to repeat:
-        /// NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be
-        /// used for optimizations *only*.
-        /// </summary>
-        /// <returns>
-        /// bool - the result of the GreaterThanOrClose comparison.
-        /// </returns>
-        /// <param name="value1"> The first double to compare. </param>
-        /// <param name="value2"> The second double to compare. </param>
-        public static bool GreaterThanOrClose(double value1, double value2)
-        {
-            return (value1 > value2) || AreClose(value1, value2);
-        }
-
-        /// <summary>
-        /// IsZero - Returns whether or not the double is "close" to 0.  Same as AreClose(double, 0),
-        /// but this is faster.
-        /// </summary>
-        /// <returns>
-        /// bool - the result of the IsZero comparison.
-        /// </returns>
-        /// <param name="value"> The double to compare to 0. </param>
-        public static bool IsZero(double value)
-        {
-            return Math.Abs(value) < 10.0 * DBL_EPSILON;
-        }
-
-        /// <summary>
-        /// 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
-        /// the other number.  Note that this epsilon is proportional to the numbers themselves
-        /// to that AreClose survives scalar multiplication.  Note,
-        /// There are plenty of ways for this to return false even for numbers which
-        /// are theoretically identical, so no code calling this should fail to work if this 
-        /// returns false.  This is important enough to repeat:
-        /// NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be
-        /// used for optimizations *only*.
-        /// </summary>
-        /// <returns>
-        /// bool - the result of the LessThan comparison.
-        /// </returns>
-        /// <param name="value1"> The first double to compare. </param>
-        /// <param name="value2"> The second double to compare. </param>
-        public static bool LessThan(double value1, double value2)
-        {
-            return (value1 < value2) && !AreClose(value1, value2);
-        }
-
-        /// <summary>
-        /// 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
-        /// epsilon of the other number.  Note that this epsilon is proportional to the numbers 
-        /// themselves to that AreClose survives scalar multiplication.  Note,
-        /// There are plenty of ways for this to return false even for numbers which
-        /// are theoretically identical, so no code calling this should fail to work if this 
-        /// returns false.  This is important enough to repeat:
-        /// NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be
-        /// used for optimizations *only*.
-        /// </summary>
-        /// <returns>
-        /// bool - the result of the LessThanOrClose comparison.
-        /// </returns>
-        /// <param name="value1"> The first double to compare. </param>
-        /// <param name="value2"> The second double to compare. </param>
-        public static bool LessThanOrClose(double value1, double value2)
-        {
-            return (value1 < value2) || AreClose(value1, value2);
-        }
-    }
-}

+ 106 - 3
tests/Avalonia.Base.UnitTests/Utilities/MathUtilitiesTests.cs

@@ -92,14 +92,14 @@ namespace Avalonia.Base.UnitTests.Utilities
         }
 
         [Fact]
-        public void Clamp_Input_NaN_Return_NaN()
+        public void Float_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()
+        public void Float_Clamp_Input_NegativeInfinity_Return_Min()
         {
             const double min = 0.0;
             const double max = 1.0;
@@ -108,12 +108,115 @@ namespace Avalonia.Base.UnitTests.Utilities
         }
 
         [Fact]
-        public void Clamp_Input_PositiveInfinity_Return_Max()
+        public void Float_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);
         }
+
+        [Fact]
+        public void Double_Float_Zero_Less_Than_One()
+        {
+            var actual = MathUtilities.LessThan(0d, 1d);
+            Assert.True(actual);
+        }
+
+        [Fact]
+        public void Single_Float_Zero_Less_Than_One()
+        {
+            var actual = MathUtilities.LessThan(0f, 1f);
+            Assert.True(actual);
+        }
+
+        [Fact]
+        public void Double_Float_One_Not_Less_Than_Zero()
+        {
+            var actual = MathUtilities.LessThan(1d, 0d);
+            Assert.False(actual);
+        }
+
+        [Fact]
+        public void Single_Float_One_Not_Less_Than_Zero()
+        {
+            var actual = MathUtilities.LessThan(1f, 0f);
+            Assert.False(actual);
+        }
+
+        [Fact]
+        public void Double_Float_Zero_Not_Greater_Than_One()
+        {
+            var actual = MathUtilities.GreaterThan(0d, 1d);
+            Assert.False(actual);
+        }
+
+        [Fact]
+        public void Single_Float_Zero_Not_Greater_Than_One()
+        {
+            var actual = MathUtilities.GreaterThan(0f, 1f);
+            Assert.False(actual);
+        }
+
+        [Fact]
+        public void Double_Float_One_Greater_Than_Zero()
+        {
+            var actual = MathUtilities.GreaterThan(1d, 0d);
+            Assert.True(actual);
+        }
+
+        [Fact]
+        public void Single_Float_One_Greater_Than_Zero()
+        {
+            var actual = MathUtilities.GreaterThan(1f, 0f);
+            Assert.True(actual);
+        }
+
+        [Fact]
+        public void Double_Float_One_Less_Than_Or_Close_One()
+        {
+            var actual = MathUtilities.LessThanOrClose(1d, 1d);
+            Assert.True(actual);
+        }
+
+        [Fact]
+        public void Single_Float_One_Less_Than_Or_Close_One()
+        {
+            var actual = MathUtilities.LessThanOrClose(1f, 1f);
+            Assert.True(actual);
+        }
+
+        [Fact]
+        public void Double_Float_One_Greater_Than_Or_Close_One()
+        {
+            var actual = MathUtilities.GreaterThanOrClose(1d, 1d);
+            Assert.True(actual);
+        }
+
+        [Fact]
+        public void Single_Float_One_Greater_Than_Or_Close_One()
+        {
+            var actual = MathUtilities.GreaterThanOrClose(1f, 1f);
+            Assert.True(actual);
+        }
+
+        [Fact]
+        public void Round_Layout_Value_Without_DPI_Aware()
+        {
+            const double value = 42.5;
+            var expectedValue = Math.Round(value);
+            var actualValue = MathUtilities.RoundLayoutValue(value, 1.0);
+            Assert.Equal(expectedValue, actualValue);
+        }
+
+        [Fact]
+        public void Round_Layout_Value_With_DPI_Aware()
+        {
+            const double dpiScale = 1.25;
+            const double value = 42.5;
+            var expectedValue = Math.Round(value * dpiScale) / dpiScale;
+            var actualValue = MathUtilities.RoundLayoutValue(value, dpiScale);
+            Assert.Equal(expectedValue, actualValue);
+        }
     }
 }