// 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;
}
}
}
}