// Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; using System.Runtime.InteropServices; namespace Avalonia.Utilities { /// /// Provides math utilities not provided in System.Math. /// public static class MathUtilities { /// /// AreClose - Returns whether or not two doubles are "close". That is, whether or /// not they are within epsilon of each other. /// /// The first double to compare. /// The second double to compare. public static bool AreClose(double value1, double value2) { //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 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 /// the other number. /// /// The first double to compare. /// The second double to compare. public static bool LessThan(double value1, double 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 /// the other number. /// /// The first double to compare. /// The second double to compare. public static bool GreaterThan(double value1, double 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 /// epsilon of the other number. /// /// The first double to compare. /// The second double to compare. public static bool LessThanOrClose(double value1, double 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 /// epsilon of the other number. /// /// The first double to compare. /// The second double to compare. public static bool GreaterThanOrClose(double value1, double 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. /// /// The double to compare to 1. public static bool IsOne(double value) { return Math.Abs(value - 1.0) < 10.0 * double.Epsilon; } /// /// IsZero - Returns whether or not the double is "close" to 0. Same as AreClose(double, 0), /// but this is faster. /// /// The double to compare to 0. public static bool IsZero(double value) { return Math.Abs(value) < 10.0 * double.Epsilon; } /// /// Clamps a value between a minimum and maximum value. /// /// The value. /// The minimum value. /// The maximum value. /// The clamped value. public static double Clamp(double val, double min, double max) { if (val < min) { return min; } else if (val > max) { return max; } else { return val; } } /// /// Calculates the value to be used for layout rounding at high DPI. /// /// Input value to be rounded. /// Ratio of screen's DPI to layout DPI /// Adjusted value that will produce layout rounding on screen at high dpi. /// This is a layout helper method. It takes DPI into account and also does not return /// the rounded value if it is unacceptable for layout, e.g. Infinity or NaN. It's a helper associated with /// UseLayoutRounding property and should not be used as a general rounding utility. public static double RoundLayoutValue(double value, double dpiScale) { double newValue; // If DPI == 1, don't use DPI-aware rounding. if (!MathUtilities.AreClose(dpiScale, 1.0)) { newValue = Math.Round(value * dpiScale) / dpiScale; // If rounding produces a value unacceptable to layout (NaN, Infinity or MaxValue), use the original value. if (double.IsNaN(newValue) || double.IsInfinity(newValue) || MathUtilities.AreClose(newValue, double.MaxValue)) { newValue = value; } } else { newValue = Math.Round(value); } return newValue; } /// /// Clamps a value between a minimum and maximum value. /// /// The value. /// The minimum value. /// The maximum value. /// The clamped value. public static int Clamp(int val, int min, int max) { if (min > max) { throw new ArgumentException($"{min} cannot be greater than {max}."); } if (val < min) { return min; } else if (val > max) { return max; } else { return val; } } } }