Browse Source

Merge branch 'master' into refactor/remove_duplicate_math_utils

pull/4104/head
Dariusz Komosiński 6 years ago
committed by GitHub
parent
commit
fcfd8a9293
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 85
      src/Avalonia.Base/Utilities/MathUtilities.cs
  2. 21
      src/Avalonia.Controls/AutoCompleteBox.cs
  3. 85
      tests/Avalonia.Base.UnitTests/Utilities/MathUtilitiesTests.cs

85
src/Avalonia.Base/Utilities/MathUtilities.cs

@ -11,6 +11,8 @@ namespace Avalonia.Utilities
// smallest such that 1.0+DoubleEpsilon != 1.0 // smallest such that 1.0+DoubleEpsilon != 1.0
internal static readonly double DoubleEpsilon = 2.2204460492503131e-016; internal static readonly double DoubleEpsilon = 2.2204460492503131e-016;
private const float FloatEpsilon = 1.192092896e-07F;
/// <summary> /// <summary>
/// AreClose - Returns whether or not two doubles are "close". That is, whether or /// AreClose - Returns whether or not two doubles are "close". That is, whether or
/// not they are within epsilon of each other. /// not they are within epsilon of each other.
@ -26,6 +28,21 @@ namespace Avalonia.Utilities
return (-eps < delta) && (eps > delta); return (-eps < delta) && (eps > delta);
} }
/// <summary>
/// AreClose - Returns whether or not two floats are "close". That is, whether or
/// not they are within epsilon of each other.
/// </summary>
/// <param name="value1"> The first float to compare. </param>
/// <param name="value2"> The second float to compare. </param>
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);
}
/// <summary> /// <summary>
/// LessThan - Returns whether or not the first double is less than the second double. /// 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 /// 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); return (value1 < value2) && !AreClose(value1, value2);
} }
/// <summary>
/// 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.
/// </summary>
/// <param name="value1"> The first single float to compare. </param>
/// <param name="value2"> The second single float to compare. </param>
public static bool LessThan(float value1, float value2)
{
return (value1 < value2) && !AreClose(value1, value2);
}
/// <summary> /// <summary>
/// GreaterThan - Returns whether or not the first double is greater than the second double. /// 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 /// 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); return (value1 > value2) && !AreClose(value1, value2);
} }
/// <summary>
/// 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.
/// </summary>
/// <param name="value1"> The first float to compare. </param>
/// <param name="value2"> The second float to compare. </param>
public static bool GreaterThan(float value1, float value2)
{
return (value1 > value2) && !AreClose(value1, value2);
}
/// <summary> /// <summary>
/// LessThanOrClose - Returns whether or not the first double is less than or close to /// 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 /// 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); return (value1 < value2) || AreClose(value1, value2);
} }
/// <summary>
/// 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.
/// </summary>
/// <param name="value1"> The first float to compare. </param>
/// <param name="value2"> The second float to compare. </param>
public static bool LessThanOrClose(float value1, float value2)
{
return (value1 < value2) || AreClose(value1, value2);
}
/// <summary> /// <summary>
/// GreaterThanOrClose - Returns whether or not the first double is greater than or close to /// 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 /// 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); return (value1 > value2) || AreClose(value1, value2);
} }
/// <summary>
/// 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.
/// </summary>
/// <param name="value1"> The first float to compare. </param>
/// <param name="value2"> The second float to compare. </param>
public static bool GreaterThanOrClose(float value1, float value2)
{
return (value1 > value2) || AreClose(value1, value2);
}
/// <summary> /// <summary>
/// IsOne - Returns whether or not the double is "close" to 1. Same as AreClose(double, 1), /// IsOne - Returns whether or not the double is "close" to 1. Same as AreClose(double, 1),
/// but this is faster. /// but this is faster.
@ -84,6 +149,16 @@ namespace Avalonia.Utilities
return Math.Abs(value - 1.0) < 10.0 * DoubleEpsilon; return Math.Abs(value - 1.0) < 10.0 * DoubleEpsilon;
} }
/// <summary>
/// IsOne - Returns whether or not the float is "close" to 1. Same as AreClose(float, 1),
/// but this is faster.
/// </summary>
/// <param name="value"> The float to compare to 1. </param>
public static bool IsOne(float value)
{
return Math.Abs(value - 1.0f) < 10.0f * FloatEpsilon;
}
/// <summary> /// <summary>
/// IsZero - Returns whether or not the double is "close" to 0. Same as AreClose(double, 0), /// IsZero - Returns whether or not the double is "close" to 0. Same as AreClose(double, 0),
/// but this is faster. /// but this is faster.
@ -94,6 +169,16 @@ namespace Avalonia.Utilities
return Math.Abs(value) < 10.0 * DoubleEpsilon; return Math.Abs(value) < 10.0 * DoubleEpsilon;
} }
/// <summary>
/// IsZero - Returns whether or not the float is "close" to 0. Same as AreClose(float, 0),
/// but this is faster.
/// </summary>
/// <param name="value"> The float to compare to 0. </param>
public static bool IsZero(float value)
{
return Math.Abs(value) < 10.0f * FloatEpsilon;
}
/// <summary> /// <summary>
/// Clamps a value between a minimum and maximum value. /// Clamps a value between a minimum and maximum value.
/// </summary> /// </summary>

21
src/Avalonia.Controls/AutoCompleteBox.cs

@ -468,10 +468,11 @@ namespace Avalonia.Controls
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.Text" /> /// <see cref="P:Avalonia.Controls.AutoCompleteBox.Text" />
/// dependency property.</value> /// dependency property.</value>
public static readonly DirectProperty<AutoCompleteBox, string> TextProperty = public static readonly DirectProperty<AutoCompleteBox, string> TextProperty =
AvaloniaProperty.RegisterDirect<AutoCompleteBox, string>( TextBlock.TextProperty.AddOwnerWithDataValidation<AutoCompleteBox>(
nameof(Text),
o => o.Text, o => o.Text,
(o, v) => o.Text = v); (o, v) => o.Text = v,
defaultBindingMode: BindingMode.TwoWay,
enableDataValidation: true);
/// <summary> /// <summary>
/// Identifies the /// Identifies the
@ -1244,6 +1245,20 @@ namespace Avalonia.Controls
base.OnApplyTemplate(e); base.OnApplyTemplate(e);
} }
/// <summary>
/// Called to update the validation state for properties for which data validation is
/// enabled.
/// </summary>
/// <param name="property">The property.</param>
/// <param name="value">The new binding value for the property.</param>
protected override void UpdateDataValidation<T>(AvaloniaProperty<T> property, BindingValue<T> value)
{
if (property == TextProperty)
{
DataValidationErrors.SetError(this, value.Error);
}
}
/// <summary> /// <summary>
/// Provides handling for the /// Provides handling for the

85
tests/Avalonia.Base.UnitTests/Utilities/MathUtilitiesTests.cs

@ -6,50 +6,89 @@ namespace Avalonia.Base.UnitTests.Utilities
{ {
public class MathUtilitiesTests public class MathUtilitiesTests
{ {
[Fact] private const double AnyValue = 42.42;
public void Two_Equivalent_Double_Values_Are_Close() 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; const int N = 10;
var x = 42.42; var dxAny = AnyValue / N;
var y = 0.0; var dxOne = 1.0 / N;
var dx = x / N; var dxZero = _zero / N;
for (var i = 0; i < N; ++i) 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.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] [Fact]
public void Calculated_Double_One_Is_One() public void Calculated_Double_One_Is_One()
{ {
const int N = 10; var actual = MathUtilities.IsOne(_one);
var dx = 1.0 / N;
var x = 0.0; 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) var actual = MathUtilities.IsOne(actualValue);
x += dx;
var actual = MathUtilities.IsOne(x);
Assert.True(actual); Assert.True(actual);
Assert.Equal(1.0, Math.Round(x, 15)); Assert.Equal(1.0f, (float) Math.Round(actualValue, 7));
} }
[Fact] [Fact]
public void Calculated_Double_Zero_Is_Zero() public void Calculated_Double_Zero_Is_Zero()
{ {
const int N = 10; var actual = MathUtilities.IsZero(_zero);
var x = 1.0;
var dx = x / N; Assert.True(actual);
Assert.Equal(0.0, Math.Round(_zero, 15));
for (var i = 0; i < N; ++i) }
x -= dx;
var actual = MathUtilities.IsZero(x); [Fact]
public void Calculated_Single_Zero_Is_Zero()
{
var actualValue = (float)_zero;
var actual = MathUtilities.IsZero(actualValue);
Assert.True(actual); Assert.True(actual);
Assert.Equal(0.0, Math.Round(x, 15)); Assert.Equal(0.0f, (float) Math.Round(actualValue, 7));
} }
[Fact] [Fact]

Loading…
Cancel
Save