Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 25 additions & 11 deletions UnitsNet.Tests/UnitMathTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,26 +156,40 @@ public void SumOfLengthsWithSelectorCalculatesCorrectly()
}

[Fact]
public void ClampCalculatesCorrectly()
public void Clamp_ReturnsValue_WhenWithinBounds()
{
var min = Length.FromMeters(-1);
var max = Length.FromCentimeters(150);

var value1 = Length.FromMillimeters(33);

Length clampedValue = UnitMath.Clamp(value1, min, max);
var value = Length.FromMillimeters(33);

Length clampedValue = UnitMath.Clamp(value, min, max);

Assert.Equal(33, clampedValue.Value);
Assert.Equal(LengthUnit.Millimeter, clampedValue.Unit);
}

var value2 = Length.FromMillimeters(-1500);

Length clampedMin = UnitMath.Clamp(value2, min, max);
[Fact]
public void Clamp_ReturnsMinAsValueUnit_WhenValueIsBelowMin()
{
var min = Length.FromMeters(-1);
var max = Length.FromCentimeters(150);
var value = Length.FromMillimeters(-1500);

Length clampedMin = UnitMath.Clamp(value, min, max);

Assert.Equal(-1000, clampedMin.Value);
Assert.Equal(LengthUnit.Millimeter, clampedMin.Unit);
}

var value3 = Length.FromMillimeters(2000);

Length clampedMax = UnitMath.Clamp(value3, min, max);
[Fact]
public void Clamp_ReturnsMaxAsValueUnit_WhenValueIsAboveMax()
{
var min = Length.FromMeters(-1);
var max = Length.FromCentimeters(150);
var value = Length.FromMillimeters(2000);

Length clampedMax = UnitMath.Clamp(value, min, max);

Assert.Equal(1500, clampedMax.Value);
Assert.Equal(LengthUnit.Millimeter, clampedMax.Unit);
}
Expand Down
144 changes: 82 additions & 62 deletions UnitsNet/UnitMath.cs
Original file line number Diff line number Diff line change
@@ -1,76 +1,96 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
namespace UnitsNet;

namespace UnitsNet
/// <summary>
/// Extension methods for common math operations.
/// </summary>
public static class UnitMath
{
/// <summary>Returns the smaller of two <typeparamref name="TQuantity" /> values.</summary>
/// <typeparam name="TQuantity">The type of quantities to compare.</typeparam>
/// <param name="val1">The first of two <typeparamref name="TQuantity" /> values to compare.</param>
/// <param name="val2">The second of two <typeparamref name="TQuantity" /> values to compare.</param>
/// <returns>Parameter <paramref name="val1" /> or <paramref name="val2" />, whichever is smaller.</returns>
public static TQuantity Min<TQuantity>(TQuantity val1, TQuantity val2)
where TQuantity : IQuantity, IComparable<TQuantity>
{
return val1.CompareTo(val2) == 1 ? val2 : val1;
}

/// <summary>Returns the larger of two <typeparamref name="TQuantity" /> values.</summary>
/// <typeparam name="TQuantity">The type of quantities to compare.</typeparam>
/// <param name="val1">The first of two <typeparamref name="TQuantity" /> values to compare.</param>
/// <param name="val2">The second of two <typeparamref name="TQuantity" /> values to compare.</param>
/// <returns>Parameter <paramref name="val1" /> or <paramref name="val2" />, whichever is larger.</returns>
public static TQuantity Max<TQuantity>(TQuantity val1, TQuantity val2)
where TQuantity : IQuantity, IComparable<TQuantity>
{
return val1.CompareTo(val2) == -1 ? val2 : val1;
}

/// <summary>
/// A set of extension methods for some of the most common Math operations, such as Min, Max, Sum and Average
/// Clamps the specified <paramref name="value" /> to the inclusive range defined by <paramref name="min" /> and
/// <paramref name="max" />.
/// </summary>
public static class UnitMath
/// <typeparam name="TQuantity">
/// The type of the quantity, which must implement <see cref="IQuantityOfType{TQuantity}" /> and
/// <see cref="IComparable{TQuantity}" />.
/// </typeparam>
/// <param name="value">The value to clamp.</param>
/// <param name="min">The minimum allowable value.</param>
/// <param name="max">The maximum allowable value.</param>
/// <returns>
/// The clamped value:
/// <list type="bullet">
/// <item>
/// <description>
/// <paramref name="value" /> if it lies within the range [<paramref name="min" />,
/// <paramref name="max" />].
/// </description>
/// </item>
/// <item>
/// <description>
/// <paramref name="min" /> (converted to value.Unit) if <paramref name="value" /> is less than
/// <paramref name="min" />.
/// </description>
/// </item>
/// <item>
/// <description>
/// <paramref name="max" /> (converted to value.Unit) if <paramref name="value" /> is greater than
/// <paramref name="max" />.
/// </description>
/// </item>
/// </list>
/// </returns>
/// <exception cref="ArgumentException">
/// Thrown if <paramref name="min" /> is greater than <paramref name="max" />.
/// </exception>
public static TQuantity Clamp<TQuantity>(TQuantity value, TQuantity min, TQuantity max)
where TQuantity : IQuantityOfType<TQuantity>, IComparable<TQuantity>
{
/// <summary>Returns the smaller of two <typeparamref name="TQuantity" /> values.</summary>
/// <typeparam name="TQuantity">The type of quantities to compare.</typeparam>
/// <param name="val1">The first of two <typeparamref name="TQuantity" /> values to compare.</param>
/// <param name="val2">The second of two <typeparamref name="TQuantity" /> values to compare.</param>
/// <returns>Parameter <paramref name="val1" /> or <paramref name="val2" />, whichever is smaller.</returns>
public static TQuantity Min<TQuantity>(TQuantity val1, TQuantity val2)
where TQuantity : IQuantity, IComparable<TQuantity>
UnitKey unitKey = value.UnitKey;
#if NET
TQuantity minValue = TQuantity.Create(min.As(unitKey), unitKey);
TQuantity maxValue = TQuantity.Create(max.As(unitKey), unitKey);
#else
TQuantity minValue = value.QuantityInfo.Create(min.As(unitKey), unitKey);
TQuantity maxValue = value.QuantityInfo.Create(max.As(unitKey), unitKey);
#endif

if (minValue.CompareTo(maxValue) > 0)
{
return val1.CompareTo(val2) == 1 ? val2 : val1;
throw new ArgumentException($"min ({min}) cannot be greater than max ({max})", nameof(min));
}

/// <summary>Returns the larger of two <typeparamref name="TQuantity" /> values.</summary>
/// <typeparam name="TQuantity">The type of quantities to compare.</typeparam>
/// <param name="val1">The first of two <typeparamref name="TQuantity" /> values to compare.</param>
/// <param name="val2">The second of two <typeparamref name="TQuantity" /> values to compare.</param>
/// <returns>Parameter <paramref name="val1" /> or <paramref name="val2" />, whichever is larger.</returns>
public static TQuantity Max<TQuantity>(TQuantity val1, TQuantity val2)
where TQuantity : IQuantity, IComparable<TQuantity>
if (value.CompareTo(minValue) < 0)
{
return val1.CompareTo(val2) == -1 ? val2 : val1;
return minValue;
}

/// <summary>Returns <paramref name="value" /> clamped to the inclusive range of <paramref name="min" /> and <paramref name="max" />.</summary>
/// <param name="value">The value to be clamped.</param>
/// <param name="min">The lower bound of the result.</param>
/// <param name="max">The upper bound of the result.</param>
/// <returns>
/// <paramref name="value" /> if <paramref name="min" /> ≤ <paramref name="value" /> ≤ <paramref name="max" />.
///
/// -or-
///
/// <paramref name="min" /> (converted to value.Unit) if <paramref name="value" /> &lt; <paramref name="min" />.
///
/// -or-
///
/// <paramref name="max" /> (converted to value.Unit) if <paramref name="max" /> &lt; <paramref name="value" />.
/// </returns>
/// <exception cref="ArgumentException">
/// <paramref name="min" /> cannot be greater than <paramref name="max" />.
/// </exception>
public static TQuantity Clamp<TQuantity>(TQuantity value, TQuantity min, TQuantity max) where TQuantity : IComparable, IQuantity
{
var minValue = (TQuantity)min.ToUnit(value.Unit);
var maxValue = (TQuantity)max.ToUnit(value.Unit);

if (minValue.CompareTo(maxValue) > 0)
{
throw new ArgumentException($"min ({min}) cannot be greater than max ({max})", nameof(min));
}

if (value.CompareTo(minValue) < 0)
{
return minValue;
}

if (value.CompareTo(maxValue) > 0)
{
return maxValue;
}

return value;
if (value.CompareTo(maxValue) > 0)
{
return maxValue;
}

return value;
}
}