From e3273934a7f28157af73fd2b558fb532bb74b068 Mon Sep 17 00:00:00 2001 From: Chris Cameron Date: Wed, 12 Jun 2019 23:42:59 -0400 Subject: [PATCH 1/6] fix: Begin rewriting RangeAttribute --- .../Attributes/RangeAttributeTest.cs | 104 ++++ ICD.Common.Utils.Tests/MathUtilsTest.cs | 12 +- ICD.Common.Utils/Attributes/RangeAttribute.cs | 453 ++++++++++-------- ICD.Common.Utils/MathUtils.cs | 36 -- 4 files changed, 359 insertions(+), 246 deletions(-) create mode 100644 ICD.Common.Utils.Tests/Attributes/RangeAttributeTest.cs diff --git a/ICD.Common.Utils.Tests/Attributes/RangeAttributeTest.cs b/ICD.Common.Utils.Tests/Attributes/RangeAttributeTest.cs new file mode 100644 index 0000000..edc7a3c --- /dev/null +++ b/ICD.Common.Utils.Tests/Attributes/RangeAttributeTest.cs @@ -0,0 +1,104 @@ +using System; +using NUnit.Framework; +using RangeAttribute = ICD.Common.Utils.Attributes.RangeAttribute; + +namespace ICD.Common.Utils.Tests.Attributes +{ + [TestFixture] + public sealed class RangeAttributeTest : AbstractIcdAttributeTest + { + #region Properties + + [TestCase(1)] + [TestCase(1.0f)] + [TestCase(1.0)] + public void MinTest(object min) + { + Assert.AreEqual(min, new RangeAttribute(min, min).Min); + } + + [TestCase(1)] + [TestCase(1.0f)] + [TestCase(1.0)] + public void MaxTest(object max) + { + Assert.AreEqual(max, new RangeAttribute(max, max).Max); + } + + #endregion + + #region Methods + + #region Remap To + + [TestCase(0, 0)] + [TestCase(1.0, 1.0)] + [TestCase(ushort.MaxValue, double.MaxValue)] + [TestCase(short.MinValue, double.MinValue)] + public void RemapToDoubleTest(object value, double expected) + { + Assert.AreEqual(expected, RangeAttribute.RemapToDouble(value)); + } + + [TestCase(0, (ushort)0)] + [TestCase(1.0, (ushort)1)] + [TestCase(double.MaxValue, ushort.MaxValue)] + [TestCase(double.MinValue, ushort.MinValue)] + public void RemapToUShortTest(object value, ushort expected) + { + Assert.AreEqual(expected, RangeAttribute.RemapToUShort(value)); + } + + #endregion + + #region Remap From + + [TestCase(0, typeof(ushort), (ushort)0)] + [TestCase(double.MinValue, typeof(ushort), ushort.MinValue)] + [TestCase(double.MaxValue, typeof(ushort), ushort.MaxValue)] + public void RemapFromDoubleTest(double value, Type type, object expected) + { + Assert.AreEqual(expected, RangeAttribute.RemapFromDouble(value, type)); + } + + [TestCase((ushort)0, typeof(double), 0)] + [TestCase(ushort.MinValue, typeof(double), double.MinValue)] + [TestCase(ushort.MaxValue, typeof(double), double.MaxValue)] + public void RemapFromUShortTest(ushort value, Type type, object expected) + { + Assert.AreEqual(expected, RangeAttribute.RemapFromUShort(value, type)); + } + + #endregion + + #region Remap To Attribute And Clamp + + public void RemapAndClampToDoubleTest(object value, object min, object max, double expected) + { + Assert.AreEqual(expected, new RangeAttribute(min, max).RemapAndClampToDouble(value)); + } + + public void RemapAndClampToUShortTest(object value, object min, object max, ushort expected) + { + Assert.AreEqual(expected, new RangeAttribute(min, max).RemapAndClampToDouble(value)); + } + + #endregion + + #region Remap And Clamp + + public void RemapAndClampFromDoubleTest(double value, object min, object max, object expected) + { + Assert.AreEqual(expected, new RangeAttribute(min, max).RemapAndClamp(value)); + } + + public void RemapAndClampFromUShortTest(ushort value, object min, object max, object expected) + { + Assert.AreEqual(expected, new RangeAttribute(min, max).RemapAndClamp(value)); + } + + #endregion + + #endregion + } +} diff --git a/ICD.Common.Utils.Tests/MathUtilsTest.cs b/ICD.Common.Utils.Tests/MathUtilsTest.cs index 52c1edd..7591083 100644 --- a/ICD.Common.Utils.Tests/MathUtilsTest.cs +++ b/ICD.Common.Utils.Tests/MathUtilsTest.cs @@ -27,6 +27,10 @@ namespace ICD.Common.Utils.Tests [Test, UsedImplicitly] public void MapRangeTest() { + Assert.AreEqual(5, MathUtils.MapRange(-100, 100, 0, 10, 0)); + Assert.AreEqual(7, MathUtils.MapRange(-100, 100, 0, 10, 50)); + Assert.AreEqual(10, MathUtils.MapRange(-100, 100, 0, 10, 100)); + Assert.AreEqual(0, MathUtils.MapRange(0, 100, 0, 10, 0)); Assert.AreEqual(5, MathUtils.MapRange(0, 100, 0, 10, 50)); Assert.AreEqual(10, MathUtils.MapRange(0, 100, 0, 10, 100)); @@ -36,7 +40,13 @@ namespace ICD.Common.Utils.Tests Assert.AreEqual(100, MathUtils.MapRange(0, 10, 0, 100, 10)); } - [Test, UsedImplicitly] + [TestCase(double.MinValue, double.MaxValue, 0.0, 1.0, double.MaxValue, 1.0)] + public void MapRangeTest(double inputStart, double inputEnd, double outputStart, double outputEnd, double value, double expected) + { + Assert.AreEqual(expected, MathUtils.MapRange(inputStart, inputEnd, outputStart, outputEnd, value)); + } + + [Test, UsedImplicitly] public void GetRangesTest() { IEnumerable values = new [] { 1, 3, 5, 6, 7, 8, 9, 10, 12 }; diff --git a/ICD.Common.Utils/Attributes/RangeAttribute.cs b/ICD.Common.Utils/Attributes/RangeAttribute.cs index 1574f9e..8cef7fa 100644 --- a/ICD.Common.Utils/Attributes/RangeAttribute.cs +++ b/ICD.Common.Utils/Attributes/RangeAttribute.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using ICD.Common.Utils.Extensions; namespace ICD.Common.Utils.Attributes { @@ -8,292 +10,325 @@ namespace ICD.Common.Utils.Attributes [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter | - AttributeTargets.ReturnValue, - AllowMultiple = false, - Inherited = true)] + AttributeTargets.ReturnValue)] public sealed class RangeAttribute : AbstractIcdAttribute { + /// + /// Remaps from the source numeric min/max to double min/max. + /// + private static readonly Dictionary> s_RemapToDouble = + new Dictionary> + { + { typeof(double), o => (double)o}, + { typeof(ushort), o => MathUtils.MapRange(ushort.MinValue, ushort.MaxValue, double.MinValue, double.MaxValue, (double)o)}, + { typeof(short), o => MathUtils.MapRange(short.MinValue, short.MaxValue, double.MinValue, double.MaxValue, (double)o)}, + { typeof(uint), o => MathUtils.MapRange(uint.MinValue, uint.MaxValue, double.MinValue, double.MaxValue, (double)o)}, + { typeof(int), o => MathUtils.MapRange(int.MinValue, int.MaxValue, double.MinValue, double.MaxValue, (double)o)}, + { typeof(ulong), o => MathUtils.MapRange(ulong.MinValue, ulong.MaxValue, double.MinValue, double.MaxValue, (double)o)}, + { typeof(long), o => MathUtils.MapRange(long.MinValue, long.MaxValue, double.MinValue, double.MaxValue, (double)o)}, + { typeof(float), o => MathUtils.MapRange(float.MinValue, float.MaxValue, double.MinValue, double.MaxValue, (double)o)}, + { typeof(decimal), o => MathUtils.MapRange((double)decimal.MinValue, (double)decimal.MaxValue, double.MinValue, double.MaxValue, (double)o)}, + { typeof(byte), o => MathUtils.MapRange(byte.MinValue, byte.MaxValue, double.MinValue, double.MaxValue, (double)o)}, + }; + + /// + /// Remaps from the double min/max to target numeric min/max. + /// + private static readonly Dictionary> s_RemapFromDouble = + new Dictionary> + { + { typeof(double), v => v}, + { typeof(ushort), v => (ushort)(MathUtils.MapRange(double.MinValue, double.MaxValue, 0, 1, v) * ushort.MaxValue)}, + { typeof(short), v => (short)(MathUtils.MapRange(double.MinValue, double.MaxValue, -1, 1, v) * short.MaxValue)}, + { typeof(uint), v => (uint)(MathUtils.MapRange(double.MinValue, double.MaxValue, 0, 1, v) * uint.MaxValue)}, + { typeof(int), v =>(int)(MathUtils.MapRange(double.MinValue, double.MaxValue, -1, 1, v) * int.MaxValue)}, + { typeof(ulong), v => (ulong)(MathUtils.MapRange(double.MinValue, double.MaxValue, 0, 1, v) * ulong.MaxValue)}, + { typeof(long), v => (long)(MathUtils.MapRange(double.MinValue, double.MaxValue, -1, 1, v) * long.MaxValue)}, + { typeof(float), v => (float)(MathUtils.MapRange(double.MinValue, double.MaxValue, -1, 1, v) * float.MaxValue)}, + { typeof(decimal), v => (decimal)MathUtils.MapRange(double.MinValue, double.MaxValue, -1, 1, v) * decimal.MaxValue}, + { typeof(byte), v => (byte)(MathUtils.MapRange(double.MinValue, double.MaxValue, 0, 1, v) * byte.MaxValue)}, + }; + + private readonly object m_Min; + private readonly object m_Max; + #region Properties - public object Min { get; private set; } - public object Max { get; private set; } + /// + /// Gets the min value for this range. + /// + public object Min { get { return m_Min; } } + + /// + /// Gets the max value for this range. + /// + public object Max { get { return m_Max; } } #endregion #region Constructors + /// + /// Constructor. + /// + /// + /// public RangeAttribute(ushort min, ushort max) + : this((object)min, (object)max) { - Min = min; - Max = max; } + /// + /// Constructor. + /// + /// + /// public RangeAttribute(short min, short max) + : this((object)min, (object)max) { - Min = min; - Max = max; } + /// + /// Constructor. + /// + /// + /// public RangeAttribute(uint min, uint max) + : this((object)min, (object)max) { - Min = min; - Max = max; } + /// + /// Constructor. + /// + /// + /// public RangeAttribute(int min, int max) + : this((object)min, (object)max) { - Min = min; - Max = max; } + /// + /// Constructor. + /// + /// + /// public RangeAttribute(ulong min, ulong max) + : this((object)min, (object)max) { - Min = min; - Max = max; } + /// + /// Constructor. + /// + /// + /// public RangeAttribute(long min, long max) + : this((object)min, (object)max) { - Min = min; - Max = max; } + /// + /// Constructor. + /// + /// + /// public RangeAttribute(float min, float max) + : this((object)min, (object)max) { - Min = min; - Max = max; } + /// + /// Constructor. + /// + /// + /// public RangeAttribute(double min, double max) + : this((object)min, (object)max) { - Min = min; - Max = max; } + /// + /// Constructor. + /// + /// + /// public RangeAttribute(byte min, byte max) + : this((object)min, (object)max) { - Min = min; - Max = max; } + /// + /// Constructor. + /// + /// + /// public RangeAttribute(sbyte min, sbyte max) + : this((object)min, (object)max) { - Min = min; - Max = max; } + /// + /// Constructor. + /// + /// + /// public RangeAttribute(decimal min, decimal max) + : this((object)min, (object)max) { - Min = min; - Max = max; + } + + /// + /// Constructor. + /// + /// + /// + public RangeAttribute(object min, object max) + { + if (min == null) + throw new ArgumentNullException("min"); + + if (max == null) + throw new ArgumentNullException("max"); + + if (min.GetType() != max.GetType()) + throw new ArgumentException("Min and Max types do not match"); + + if (!min.GetType().IsNumeric()) + throw new ArgumentException("Given types are not numeric"); + + m_Min = min; + m_Max = max; } #endregion #region Methods - public T GetMin() + #region Remap To + + /// + /// Remaps the given numeric value from its min/max range into double min/max range. + /// + /// + /// + public static double RemapToDouble(object value) { - return (T)Convert.ChangeType(Min, typeof(T), null); + if (value == null) + throw new ArgumentNullException("value"); + + Func remap; + if (!s_RemapToDouble.TryGetValue(value.GetType(), out remap)) + throw new NotSupportedException("Value type is not supported."); + + return remap(value); } - public T GetMax() + /// + /// Remaps the given numeric value from its min/max range into ushort min/max range. + /// + /// + /// + public static ushort RemapToUShort(object value) { - return (T)Convert.ChangeType(Max, typeof(T), null); - } - - public bool IsInRange(object value) - { - if (value is ushort) - { - if (!(Min is ushort)) - throw new ArgumentException("the type of value does not match the type of min / max"); - - var castVal = (ushort)value; - return (castVal >= GetMin() && castVal <= GetMax()); - } - - if (value is short) - { - if (!(Min is short)) - throw new ArgumentException("the type of value does not match the type of min / max"); - - var castVal = (short)value; - return (castVal >= GetMin() && castVal <= GetMax()); - } - - if (value is uint) - { - if (!(Min is uint)) - throw new ArgumentException("the type of value does not match the type of min / max"); - - var castVal = (uint)value; - return (castVal >= GetMin() && castVal <= GetMax()); - } - - if (value is int) - { - if (!(Min is int)) - throw new ArgumentException("the type of value does not match the type of min / max"); - - var castVal = (int)value; - return (castVal >= GetMin() && castVal <= GetMax()); - } - - if (value is ulong) - { - if (!(Min is ulong)) - throw new ArgumentException("the type of value does not match the type of min / max"); - - var castVal = (ulong)value; - return (castVal >= GetMin() && castVal <= GetMax()); - } - - if (value is long) - { - if (!(Min is long)) - throw new ArgumentException("the type of value does not match the type of min / max"); - - var castVal = (long)value; - return (castVal >= GetMin() && castVal <= GetMax()); - } - - if (value is float) - { - if (!(Min is float)) - throw new ArgumentException("the type of value does not match the type of min / max"); - - var castVal = (float)value; - return (castVal >= GetMin() && castVal <= GetMax()); - } - - if (value is double) - { - if (!(Min is double)) - throw new ArgumentException("the type of value does not match the type of min / max"); - - var castVal = (double)value; - return (castVal >= GetMin() && castVal <= GetMax()); - } - - if (value is decimal) - { - if (!(Min is decimal)) - throw new ArgumentException("the type of value does not match the type of min / max"); - - var castVal = (decimal)value; - return (castVal >= GetMin() && castVal <= GetMax()); - } - - if (value is byte) - { - if (!(Min is byte)) - throw new ArgumentException("the type of value does not match the type of min / max"); - - var castVal = (byte)value; - return (castVal >= GetMin() && castVal <= GetMax()); - } - - if (value is sbyte) - { - if (!(Min is sbyte)) - throw new ArgumentException("the type of value does not match the type of min / max"); - - var castVal = (sbyte)value; - return (castVal >= GetMin() && castVal <= GetMax()); - } - - throw new ArgumentException("the type of value is not a numeric type."); - } - - #region Range -> UShort - - public ushort RemapRangeToUshort(double value) - { - return (ushort)MathUtils.MapRange(GetMin(), GetMax(), ushort.MinValue, ushort.MaxValue, value); - } - - public ushort RemapRangeToUshort(float value) - { - return (ushort)MathUtils.MapRange(GetMin(), GetMax(), ushort.MinValue, ushort.MaxValue, value); - } - - public ushort RemapRangeToUshort(int value) - { - return (ushort)MathUtils.MapRange(GetMin(), GetMax(), ushort.MinValue, ushort.MaxValue, value); - } - - public ushort RemapRangeToUshort(ushort value) - { - return MathUtils.MapRange(GetMin(), GetMax(), ushort.MinValue, ushort.MaxValue, value); + return (ushort)(ushort.MaxValue * RemapToDouble(value)); } #endregion - #region UShort -> Range + #region Remap From - public object RemapUshortToRange(ushort value) + /// + /// Remaps the given double value from its min/max range into the target type min/max range. + /// + /// + /// + /// + public static object RemapFromDouble(double value, Type type) { - if (Min is ushort) - { - return MathUtils.MapRange(ushort.MinValue, ushort.MaxValue, GetMin(), GetMax(), value); - } + if (type == null) + throw new ArgumentNullException("type"); - if (Min is short) - { - var castVal = (short)value; - return (short)MathUtils.MapRange(ushort.MinValue, ushort.MaxValue, GetMin(), GetMax(), castVal); - } + Func remap; + if (!s_RemapFromDouble.TryGetValue(type, out remap)) + throw new NotSupportedException("Value type is not supported."); - if (Min is uint) - { - var castVal = (uint)value; - return (uint)MathUtils.MapRange(ushort.MinValue, ushort.MaxValue, GetMin(), GetMax(), castVal); - } + return remap(value); + } - if (Min is int) - { - var castVal = (int)value; - return MathUtils.MapRange(ushort.MinValue, ushort.MaxValue, GetMin(), GetMax(), castVal); - } + /// + /// Remaps the given ushort value from its min/max range into the target type min/max range. + /// + /// + /// + /// + public static object RemapFromUShort(ushort value, Type type) + { + double intermediate = RemapToDouble(value); + return RemapFromDouble(intermediate, type); + } - if (Min is ulong) - { - var castVal = (ulong)value; - return MathUtils.MapRange(ushort.MinValue, ushort.MaxValue, GetMin(), GetMax(), castVal); - } + #endregion - if (Min is long) - { - var castVal = (long)value; - return MathUtils.MapRange(ushort.MinValue, ushort.MaxValue, GetMin(), GetMax(), castVal); - } + #region Remap To Attribute And Clamp - if (Min is float) - { - var castVal = (float)value; - return MathUtils.MapRange(ushort.MinValue, ushort.MaxValue, GetMin(), GetMax(), castVal); - } + /// + /// Remaps the given numeric value from its min/max range into the min/max range of this attribute, + /// clamps, and then remaps the result to double. + /// + /// + /// + public double RemapAndClampToDouble(object value) + { + double intermediate = RemapToDouble(value); + double min = RemapToDouble(Min); + double max = RemapToDouble(Max); - if (Min is double) - { - var castVal = (double)value; - return MathUtils.MapRange(ushort.MinValue, ushort.MaxValue, GetMin(), GetMax(), castVal); - } + return MathUtils.Clamp(intermediate, min, max); + } - if (Min is decimal) - { - var castVal = (decimal)value; - return MathUtils.MapRange(ushort.MinValue, ushort.MaxValue, GetMin(), GetMax(), castVal); - } + /// + /// Remaps the given numeric value from its min/max range into the min/max range of this attribute, + /// clamps, and then remaps the result to ushort. + /// + /// + /// + public ushort RemapAndClampToUShort(object value) + { + double intermediate = RemapAndClampToDouble(value); + return RemapToUShort(intermediate); + } - if (Min is byte) - { - var castVal = (byte)value; - return (byte)MathUtils.MapRange(ushort.MinValue, ushort.MaxValue, GetMin(), GetMax(), castVal); - } + #endregion - throw new NotSupportedException("Value type of range attribute is not supported."); + #region Remap And Clamp + + /// + /// Remaps the ushort value from its min/max range into the min/max range of this attribute, + /// clamps and returns the result. + /// + /// + /// + public object RemapAndClamp(double value) + { + double min = RemapToDouble(Min); + double max = RemapToDouble(Max); + + double clamp = MathUtils.Clamp(value, min, max); + + return RemapFromDouble(clamp, m_Min.GetType()); + } + + /// + /// Remaps the ushort value from its min/max range into the min/max range of this attribute, + /// clamps and returns the result. + /// + /// + /// + public object RemapAndClamp(ushort value) + { + double intermediate = RemapToDouble(value); + return RemapAndClamp(intermediate); } #endregion diff --git a/ICD.Common.Utils/MathUtils.cs b/ICD.Common.Utils/MathUtils.cs index e4427ac..832ccba 100644 --- a/ICD.Common.Utils/MathUtils.cs +++ b/ICD.Common.Utils/MathUtils.cs @@ -138,42 +138,6 @@ namespace ICD.Common.Utils return (ushort)MapRange((double)inputStart, inputEnd, outputStart, outputEnd, value); } - /// - /// Returns the value after the input range has been mapped to a new range - /// - /// Input start. - /// Input end. - /// Output start. - /// Output end. - /// Value. - /// The newly mapped value - public static ulong MapRange(ulong inputStart, ulong inputEnd, ulong outputStart, ulong outputEnd, ulong value) - { - if (inputStart.Equals(inputEnd)) - throw new DivideByZeroException(); - - ulong slope = (outputEnd - outputStart) / (inputEnd - inputStart); - return outputStart + slope * (value - inputStart); - } - - /// - /// Returns the value after the input range has been mapped to a new range - /// - /// Input start. - /// Input end. - /// Output start. - /// Output end. - /// Value. - /// The newly mapped value - public static long MapRange(long inputStart, long inputEnd, long outputStart, long outputEnd, long value) - { - if (inputStart.Equals(inputEnd)) - throw new DivideByZeroException(); - - long slope = (outputEnd - outputStart) / (inputEnd - inputStart); - return outputStart + slope * (value - inputStart); - } - /// /// Maps the date in the given range to the float range 0.0f to 1.0f. /// 0.5f - The date is half way between the end points. From 8866a3e1e68d648aeb6e0216aff6ebaff6aef938 Mon Sep 17 00:00:00 2001 From: Chris Cameron Date: Thu, 13 Jun 2019 11:59:12 -0400 Subject: [PATCH 2/6] fix: Fixing tests, bad maths --- .../Attributes/RangeAttributeTest.cs | 11 ++-- ICD.Common.Utils.Tests/MathUtilsTest.cs | 6 --- ICD.Common.Utils/Attributes/RangeAttribute.cs | 50 +++++++++++-------- 3 files changed, 35 insertions(+), 32 deletions(-) diff --git a/ICD.Common.Utils.Tests/Attributes/RangeAttributeTest.cs b/ICD.Common.Utils.Tests/Attributes/RangeAttributeTest.cs index edc7a3c..e777821 100644 --- a/ICD.Common.Utils.Tests/Attributes/RangeAttributeTest.cs +++ b/ICD.Common.Utils.Tests/Attributes/RangeAttributeTest.cs @@ -31,8 +31,8 @@ namespace ICD.Common.Utils.Tests.Attributes #region Remap To - [TestCase(0, 0)] - [TestCase(1.0, 1.0)] + [TestCase((double)0, (double)0)] + [TestCase((double)1, (double)1)] [TestCase(ushort.MaxValue, double.MaxValue)] [TestCase(short.MinValue, double.MinValue)] public void RemapToDoubleTest(object value, double expected) @@ -40,8 +40,7 @@ namespace ICD.Common.Utils.Tests.Attributes Assert.AreEqual(expected, RangeAttribute.RemapToDouble(value)); } - [TestCase(0, (ushort)0)] - [TestCase(1.0, (ushort)1)] + [TestCase((double)0, (ushort)32767)] [TestCase(double.MaxValue, ushort.MaxValue)] [TestCase(double.MinValue, ushort.MinValue)] public void RemapToUShortTest(object value, ushort expected) @@ -53,7 +52,7 @@ namespace ICD.Common.Utils.Tests.Attributes #region Remap From - [TestCase(0, typeof(ushort), (ushort)0)] + [TestCase((double)0, typeof(ushort), (ushort)32767)] [TestCase(double.MinValue, typeof(ushort), ushort.MinValue)] [TestCase(double.MaxValue, typeof(ushort), ushort.MaxValue)] public void RemapFromDoubleTest(double value, Type type, object expected) @@ -61,7 +60,7 @@ namespace ICD.Common.Utils.Tests.Attributes Assert.AreEqual(expected, RangeAttribute.RemapFromDouble(value, type)); } - [TestCase((ushort)0, typeof(double), 0)] + [TestCase((ushort)0, typeof(double), double.MinValue)] [TestCase(ushort.MinValue, typeof(double), double.MinValue)] [TestCase(ushort.MaxValue, typeof(double), double.MaxValue)] public void RemapFromUShortTest(ushort value, Type type, object expected) diff --git a/ICD.Common.Utils.Tests/MathUtilsTest.cs b/ICD.Common.Utils.Tests/MathUtilsTest.cs index 7591083..ebf3280 100644 --- a/ICD.Common.Utils.Tests/MathUtilsTest.cs +++ b/ICD.Common.Utils.Tests/MathUtilsTest.cs @@ -40,12 +40,6 @@ namespace ICD.Common.Utils.Tests Assert.AreEqual(100, MathUtils.MapRange(0, 10, 0, 100, 10)); } - [TestCase(double.MinValue, double.MaxValue, 0.0, 1.0, double.MaxValue, 1.0)] - public void MapRangeTest(double inputStart, double inputEnd, double outputStart, double outputEnd, double value, double expected) - { - Assert.AreEqual(expected, MathUtils.MapRange(inputStart, inputEnd, outputStart, outputEnd, value)); - } - [Test, UsedImplicitly] public void GetRangesTest() { diff --git a/ICD.Common.Utils/Attributes/RangeAttribute.cs b/ICD.Common.Utils/Attributes/RangeAttribute.cs index 8cef7fa..9590381 100644 --- a/ICD.Common.Utils/Attributes/RangeAttribute.cs +++ b/ICD.Common.Utils/Attributes/RangeAttribute.cs @@ -19,16 +19,21 @@ namespace ICD.Common.Utils.Attributes private static readonly Dictionary> s_RemapToDouble = new Dictionary> { + // Duh { typeof(double), o => (double)o}, - { typeof(ushort), o => MathUtils.MapRange(ushort.MinValue, ushort.MaxValue, double.MinValue, double.MaxValue, (double)o)}, - { typeof(short), o => MathUtils.MapRange(short.MinValue, short.MaxValue, double.MinValue, double.MaxValue, (double)o)}, - { typeof(uint), o => MathUtils.MapRange(uint.MinValue, uint.MaxValue, double.MinValue, double.MaxValue, (double)o)}, - { typeof(int), o => MathUtils.MapRange(int.MinValue, int.MaxValue, double.MinValue, double.MaxValue, (double)o)}, - { typeof(ulong), o => MathUtils.MapRange(ulong.MinValue, ulong.MaxValue, double.MinValue, double.MaxValue, (double)o)}, - { typeof(long), o => MathUtils.MapRange(long.MinValue, long.MaxValue, double.MinValue, double.MaxValue, (double)o)}, - { typeof(float), o => MathUtils.MapRange(float.MinValue, float.MaxValue, double.MinValue, double.MaxValue, (double)o)}, - { typeof(decimal), o => MathUtils.MapRange((double)decimal.MinValue, (double)decimal.MaxValue, double.MinValue, double.MaxValue, (double)o)}, - { typeof(byte), o => MathUtils.MapRange(byte.MinValue, byte.MaxValue, double.MinValue, double.MaxValue, (double)o)}, + + // Signed - Clamping prevents an overflow due to loss of precision + { typeof(short), o => MathUtils.Clamp(Convert.ToDouble(o) / short.MaxValue, -1, 1) * double.MaxValue}, + { typeof(int), o => MathUtils.Clamp(Convert.ToDouble(o) / int.MaxValue, -1, 1) * double.MaxValue}, + { typeof(long), o => MathUtils.Clamp(Convert.ToDouble(o) / long.MaxValue, -1, 1) * double.MaxValue}, + { typeof(float), o => MathUtils.Clamp(Convert.ToDouble(o) / float.MaxValue, -1, 1) * double.MaxValue}, + { typeof(decimal), o => MathUtils.Clamp(Convert.ToDouble(o) / (double)decimal.MaxValue, -1, 1) * double.MaxValue}, + + // Unsigned + { typeof(ushort), o => MathUtils.Clamp((Convert.ToDouble(o) / ushort.MaxValue - 0.5) * 2, -1, 1) * double.MaxValue}, + { typeof(uint), o => MathUtils.Clamp((Convert.ToDouble(o) / uint.MaxValue - 0.5) * 2, -1, 1) * double.MaxValue}, + { typeof(ulong), o => MathUtils.Clamp((Convert.ToDouble(o) / ulong.MaxValue - 0.5) * 2, -1, 1) * double.MaxValue}, + { typeof(byte), o => MathUtils.Clamp((Convert.ToDouble(o) / byte.MaxValue - 0.5) * 2, -1, 1) * double.MaxValue} }; /// @@ -37,16 +42,21 @@ namespace ICD.Common.Utils.Attributes private static readonly Dictionary> s_RemapFromDouble = new Dictionary> { - { typeof(double), v => v}, - { typeof(ushort), v => (ushort)(MathUtils.MapRange(double.MinValue, double.MaxValue, 0, 1, v) * ushort.MaxValue)}, - { typeof(short), v => (short)(MathUtils.MapRange(double.MinValue, double.MaxValue, -1, 1, v) * short.MaxValue)}, - { typeof(uint), v => (uint)(MathUtils.MapRange(double.MinValue, double.MaxValue, 0, 1, v) * uint.MaxValue)}, - { typeof(int), v =>(int)(MathUtils.MapRange(double.MinValue, double.MaxValue, -1, 1, v) * int.MaxValue)}, - { typeof(ulong), v => (ulong)(MathUtils.MapRange(double.MinValue, double.MaxValue, 0, 1, v) * ulong.MaxValue)}, - { typeof(long), v => (long)(MathUtils.MapRange(double.MinValue, double.MaxValue, -1, 1, v) * long.MaxValue)}, - { typeof(float), v => (float)(MathUtils.MapRange(double.MinValue, double.MaxValue, -1, 1, v) * float.MaxValue)}, - { typeof(decimal), v => (decimal)MathUtils.MapRange(double.MinValue, double.MaxValue, -1, 1, v) * decimal.MaxValue}, - { typeof(byte), v => (byte)(MathUtils.MapRange(double.MinValue, double.MaxValue, 0, 1, v) * byte.MaxValue)}, + // Duh + {typeof(double), v => v}, + + // Signed + {typeof(short), v => (short)(v / double.MaxValue * short.MaxValue)}, + {typeof(int), v => (int)(v / double.MaxValue * int.MaxValue)}, + {typeof(long), v => (long)(v / double.MaxValue * long.MaxValue)}, + {typeof(float), v => (float)(v / double.MaxValue * float.MaxValue)}, + {typeof(decimal), v => (decimal)(v / double.MaxValue) * decimal.MaxValue}, + + // Unsigned + {typeof(ushort), v => (ushort)((v / double.MaxValue + 1) / 2 * ushort.MaxValue)}, + {typeof(uint), v => (uint)((v / double.MaxValue + 1) / 2 * uint.MaxValue)}, + {typeof(ulong), v => (ulong)((v / double.MaxValue + 1) / 2 * ulong.MaxValue)}, + {typeof(byte), v => (byte)((v / double.MaxValue + 1) / 2 * byte.MaxValue)} }; private readonly object m_Min; @@ -231,7 +241,7 @@ namespace ICD.Common.Utils.Attributes /// public static ushort RemapToUShort(object value) { - return (ushort)(ushort.MaxValue * RemapToDouble(value)); + return (ushort)(ushort.MaxValue * (RemapToDouble(value) / double.MaxValue + 1) / 2); } #endregion From c31b2b556bf1f682045225d4415f30a276ca51f6 Mon Sep 17 00:00:00 2001 From: Chris Cameron Date: Thu, 13 Jun 2019 16:56:49 -0400 Subject: [PATCH 3/6] feat: Adding methods for remapping and clamping numeric values to ranges --- .../Attributes/RangeAttributeTest.cs | 52 ---- ICD.Common.Utils/Attributes/RangeAttribute.cs | 283 +++++++++++++----- 2 files changed, 215 insertions(+), 120 deletions(-) diff --git a/ICD.Common.Utils.Tests/Attributes/RangeAttributeTest.cs b/ICD.Common.Utils.Tests/Attributes/RangeAttributeTest.cs index e777821..45c5a13 100644 --- a/ICD.Common.Utils.Tests/Attributes/RangeAttributeTest.cs +++ b/ICD.Common.Utils.Tests/Attributes/RangeAttributeTest.cs @@ -29,8 +29,6 @@ namespace ICD.Common.Utils.Tests.Attributes #region Methods - #region Remap To - [TestCase((double)0, (double)0)] [TestCase((double)1, (double)1)] [TestCase(ushort.MaxValue, double.MaxValue)] @@ -40,18 +38,6 @@ namespace ICD.Common.Utils.Tests.Attributes Assert.AreEqual(expected, RangeAttribute.RemapToDouble(value)); } - [TestCase((double)0, (ushort)32767)] - [TestCase(double.MaxValue, ushort.MaxValue)] - [TestCase(double.MinValue, ushort.MinValue)] - public void RemapToUShortTest(object value, ushort expected) - { - Assert.AreEqual(expected, RangeAttribute.RemapToUShort(value)); - } - - #endregion - - #region Remap From - [TestCase((double)0, typeof(ushort), (ushort)32767)] [TestCase(double.MinValue, typeof(ushort), ushort.MinValue)] [TestCase(double.MaxValue, typeof(ushort), ushort.MaxValue)] @@ -60,44 +46,6 @@ namespace ICD.Common.Utils.Tests.Attributes Assert.AreEqual(expected, RangeAttribute.RemapFromDouble(value, type)); } - [TestCase((ushort)0, typeof(double), double.MinValue)] - [TestCase(ushort.MinValue, typeof(double), double.MinValue)] - [TestCase(ushort.MaxValue, typeof(double), double.MaxValue)] - public void RemapFromUShortTest(ushort value, Type type, object expected) - { - Assert.AreEqual(expected, RangeAttribute.RemapFromUShort(value, type)); - } - - #endregion - - #region Remap To Attribute And Clamp - - public void RemapAndClampToDoubleTest(object value, object min, object max, double expected) - { - Assert.AreEqual(expected, new RangeAttribute(min, max).RemapAndClampToDouble(value)); - } - - public void RemapAndClampToUShortTest(object value, object min, object max, ushort expected) - { - Assert.AreEqual(expected, new RangeAttribute(min, max).RemapAndClampToDouble(value)); - } - - #endregion - - #region Remap And Clamp - - public void RemapAndClampFromDoubleTest(double value, object min, object max, object expected) - { - Assert.AreEqual(expected, new RangeAttribute(min, max).RemapAndClamp(value)); - } - - public void RemapAndClampFromUShortTest(ushort value, object min, object max, object expected) - { - Assert.AreEqual(expected, new RangeAttribute(min, max).RemapAndClamp(value)); - } - - #endregion - #endregion } } diff --git a/ICD.Common.Utils/Attributes/RangeAttribute.cs b/ICD.Common.Utils/Attributes/RangeAttribute.cs index 9590381..7220488 100644 --- a/ICD.Common.Utils/Attributes/RangeAttribute.cs +++ b/ICD.Common.Utils/Attributes/RangeAttribute.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using ICD.Common.Utils.Extensions; namespace ICD.Common.Utils.Attributes @@ -13,6 +14,29 @@ namespace ICD.Common.Utils.Attributes AttributeTargets.ReturnValue)] public sealed class RangeAttribute : AbstractIcdAttribute { + /// + /// Remaps from the source numeric min/max to double min/max. + /// + private static readonly Dictionary> s_Clamp = + new Dictionary> + { + // Duh + {typeof(double), o => o}, + + // Signed + {typeof(short), o => o < short.MinValue ? short.MinValue : o > short.MaxValue ? short.MaxValue : o}, + {typeof(int), o => o < int.MinValue ? int.MinValue : o > int.MaxValue ? int.MaxValue : o}, + {typeof(long), o => o < long.MinValue ? long.MinValue : o > long.MaxValue ? long.MaxValue : o}, + {typeof(float),o => o < float.MinValue ? float.MinValue : o > float.MaxValue ? float.MaxValue : o}, + {typeof(decimal), o => o < (double)decimal.MinValue ? (double)decimal.MinValue : o > (double)decimal.MaxValue ? (double)decimal.MaxValue : o}, + + // Unsigned + {typeof(ushort), o => o < ushort.MinValue ? ushort.MinValue : o > ushort.MaxValue ? ushort.MaxValue : o}, + {typeof(uint), o => o < uint.MinValue ? uint.MinValue : o > uint.MaxValue ? uint.MaxValue : o}, + {typeof(ulong), o => o < ulong.MinValue ? ulong.MinValue : o > ulong.MaxValue ? ulong.MaxValue : o}, + {typeof(byte), o => o < byte.MinValue ? byte.MinValue : o > byte.MaxValue ? byte.MaxValue : o} + }; + /// /// Remaps from the source numeric min/max to double min/max. /// @@ -59,6 +83,52 @@ namespace ICD.Common.Utils.Attributes {typeof(byte), v => (byte)((v / double.MaxValue + 1) / 2 * byte.MaxValue)} }; + /// + /// Gets the min value of a given numeric type as a double. + /// + private static readonly Dictionary s_MinAsDouble = + new Dictionary + { + // Duh + {typeof(double), double.MinValue}, + + // Signed + {typeof(short), Convert.ToDouble(short.MinValue)}, + {typeof(int), Convert.ToDouble(int.MinValue)}, + {typeof(long), Convert.ToDouble(long.MinValue)}, + {typeof(float), Convert.ToDouble(float.MinValue)}, + {typeof(decimal), Convert.ToDouble(decimal.MinValue)}, + + // Unsigned + {typeof(ushort), Convert.ToDouble(ushort.MinValue)}, + {typeof(uint), Convert.ToDouble(uint.MinValue)}, + {typeof(ulong), Convert.ToDouble(ulong.MinValue)}, + {typeof(byte), Convert.ToDouble(byte.MinValue)} + }; + + /// + /// Gets the min value of a given numeric type as a double. + /// + private static readonly Dictionary s_MaxAsDouble = + new Dictionary + { + // Duh + {typeof(double), double.MaxValue}, + + // Signed + {typeof(short), Convert.ToDouble(short.MaxValue)}, + {typeof(int), Convert.ToDouble(int.MaxValue)}, + {typeof(long), Convert.ToDouble(long.MaxValue)}, + {typeof(float), Convert.ToDouble(float.MaxValue)}, + {typeof(decimal), Convert.ToDouble(decimal.MaxValue)}, + + // Unsigned + {typeof(ushort), Convert.ToDouble(ushort.MaxValue)}, + {typeof(uint), Convert.ToDouble(uint.MaxValue)}, + {typeof(ulong), Convert.ToDouble(ulong.MaxValue)}, + {typeof(byte), Convert.ToDouble(byte.MaxValue)} + }; + private readonly object m_Min; private readonly object m_Max; @@ -215,8 +285,6 @@ namespace ICD.Common.Utils.Attributes #region Methods - #region Remap To - /// /// Remaps the given numeric value from its min/max range into double min/max range. /// @@ -234,20 +302,6 @@ namespace ICD.Common.Utils.Attributes return remap(value); } - /// - /// Remaps the given numeric value from its min/max range into ushort min/max range. - /// - /// - /// - public static ushort RemapToUShort(object value) - { - return (ushort)(ushort.MaxValue * (RemapToDouble(value) / double.MaxValue + 1) / 2); - } - - #endregion - - #region Remap From - /// /// Remaps the given double value from its min/max range into the target type min/max range. /// @@ -267,82 +321,175 @@ namespace ICD.Common.Utils.Attributes } /// - /// Remaps the given ushort value from its min/max range into the target type min/max range. + /// Clamps the given numeric value into the valid ranges of the target numeric type. /// /// /// /// - public static object RemapFromUShort(ushort value, Type type) + public static object Clamp(object value, Type type) { + if (value == null) + throw new ArgumentNullException("value"); + + if (type == null) + throw new ArgumentNullException("type"); + + if (!type.IsNumeric()) + throw new ArgumentException("Target type is not numeric"); + + if (!value.GetType().IsNumeric()) + throw new ArgumentException("Source value is not numeric"); + + double doubleValue = Convert.ToDouble(value); + double clamped = Clamp(doubleValue, type); + + return Convert.ChangeType(clamped, value.GetType(), CultureInfo.InvariantCulture); + } + + /// + /// Clamps the given double value into the valid ranges of the target numeric type. + /// + /// + /// + /// + public static double Clamp(double value, Type type) + { + if (type == null) + throw new ArgumentNullException("type"); + + Func clamp; + if (!s_Clamp.TryGetValue(type, out clamp)) + throw new NotSupportedException("Value type is not supported."); + + return clamp(value); + } + + /// + /// Remaps the numeric value into the min-max range of the target numeric type. + /// + /// + /// + /// + public static object Remap(object value, Type type) + { + if (value == null) + throw new ArgumentNullException("value"); + + if (type == null) + throw new ArgumentNullException("type"); + + if (!type.IsNumeric()) + throw new ArgumentException("Target type is not numeric"); + + if (!value.GetType().IsNumeric()) + throw new ArgumentException("Source value is not numeric"); + double intermediate = RemapToDouble(value); - return RemapFromDouble(intermediate, type); + object remapped = RemapFromDouble(intermediate, type); + + return Convert.ChangeType(remapped, value.GetType(), CultureInfo.InvariantCulture); + } + + /// + /// Clamps the given numeric value to the defined min/max then remaps to the target numeric type. + /// + /// + /// + /// + public object ClampMinMaxThenRemap(object value, Type type) + { + if (value == null) + throw new ArgumentNullException("value"); + + if (type == null) + throw new ArgumentNullException("type"); + + if (!type.IsNumeric()) + throw new ArgumentException("Target type is not numeric"); + + if (!value.GetType().IsNumeric()) + throw new ArgumentException("Source value is not numeric"); + + double min = Convert.ToDouble(Min); + double max = Convert.ToDouble(Max); + double doubleValue = Convert.ToDouble(value); + + double clamped = MathUtils.Clamp(doubleValue, min, max); + object remapped = Remap(clamped, type); + + return Convert.ChangeType(remapped, value.GetType(), CultureInfo.InvariantCulture); + } + + /// + /// Remaps the given numeric value to the defined min/max. + /// + /// + /// + public object RemapMinMax(object value) + { + if (value == null) + throw new ArgumentNullException("value"); + + if (!value.GetType().IsNumeric()) + throw new ArgumentException("Source value is not numeric"); + + double sourceMin = GetMinAsDouble(value.GetType()); + double sourceMax = GetMaxAsDouble(value.GetType()); + + double targetMin = Convert.ToDouble(Min); + double targetMax = Convert.ToDouble(Max); + + double doubleValue = Convert.ToDouble(value); + + double remapped = MathUtils.MapRange(sourceMin, sourceMax, targetMin, targetMax, doubleValue); + + return Convert.ChangeType(remapped, Min.GetType(), CultureInfo.InvariantCulture); } #endregion - #region Remap To Attribute And Clamp + #region Private Methods /// - /// Remaps the given numeric value from its min/max range into the min/max range of this attribute, - /// clamps, and then remaps the result to double. + /// Gets the min value for the given numeric type as a double. /// - /// + /// /// - public double RemapAndClampToDouble(object value) + private static double GetMinAsDouble(Type type) { - double intermediate = RemapToDouble(value); - double min = RemapToDouble(Min); - double max = RemapToDouble(Max); + if (type == null) + throw new ArgumentNullException("type"); - return MathUtils.Clamp(intermediate, min, max); + if (!type.IsNumeric()) + throw new ArgumentException("Target type is not numeric"); + + double min; + if (!s_MinAsDouble.TryGetValue(type, out min)) + throw new NotSupportedException("Type is not supported."); + + return min; } /// - /// Remaps the given numeric value from its min/max range into the min/max range of this attribute, - /// clamps, and then remaps the result to ushort. + /// Gets the max value for the given numeric type as a double. /// - /// + /// /// - public ushort RemapAndClampToUShort(object value) + private static double GetMaxAsDouble(Type type) { - double intermediate = RemapAndClampToDouble(value); - return RemapToUShort(intermediate); + if (type == null) + throw new ArgumentNullException("type"); + + if (!type.IsNumeric()) + throw new ArgumentException("Target type is not numeric"); + + double max; + if (!s_MaxAsDouble.TryGetValue(type, out max)) + throw new NotSupportedException("Type is not supported."); + + return max; } #endregion - - #region Remap And Clamp - - /// - /// Remaps the ushort value from its min/max range into the min/max range of this attribute, - /// clamps and returns the result. - /// - /// - /// - public object RemapAndClamp(double value) - { - double min = RemapToDouble(Min); - double max = RemapToDouble(Max); - - double clamp = MathUtils.Clamp(value, min, max); - - return RemapFromDouble(clamp, m_Min.GetType()); - } - - /// - /// Remaps the ushort value from its min/max range into the min/max range of this attribute, - /// clamps and returns the result. - /// - /// - /// - public object RemapAndClamp(ushort value) - { - double intermediate = RemapToDouble(value); - return RemapAndClamp(intermediate); - } - - #endregion - - #endregion } } \ No newline at end of file From f7740aaea23aaf8da9c3dbb3ad2b504da6ba6590 Mon Sep 17 00:00:00 2001 From: Chris Cameron Date: Thu, 13 Jun 2019 17:10:34 -0400 Subject: [PATCH 4/6] fix: Fixing overflow --- .../Attributes/RangeAttributeTest.cs | 31 +++++++++++++++++++ ICD.Common.Utils/Attributes/RangeAttribute.cs | 4 +-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/ICD.Common.Utils.Tests/Attributes/RangeAttributeTest.cs b/ICD.Common.Utils.Tests/Attributes/RangeAttributeTest.cs index 45c5a13..c53793f 100644 --- a/ICD.Common.Utils.Tests/Attributes/RangeAttributeTest.cs +++ b/ICD.Common.Utils.Tests/Attributes/RangeAttributeTest.cs @@ -46,6 +46,37 @@ namespace ICD.Common.Utils.Tests.Attributes Assert.AreEqual(expected, RangeAttribute.RemapFromDouble(value, type)); } + [TestCase(short.MinValue, typeof(ushort), ushort.MinValue)] + [TestCase(short.MaxValue, typeof(ushort), short.MaxValue)] + public static void Clamp(object value, Type type, object expected) + { + Assert.AreEqual(expected, RangeAttribute.Clamp(value, type)); + } + + [TestCase(double.MinValue, typeof(ushort), ushort.MinValue)] + [TestCase(double.MaxValue, typeof(ushort), ushort.MaxValue)] + public void Clamp(double value, Type type, double expected) + { + Assert.AreEqual(expected, RangeAttribute.Clamp(value, type)); + } + + [TestCase(short.MinValue, typeof(ushort), ushort.MinValue)] + [TestCase(short.MaxValue, typeof(ushort), ushort.MaxValue)] + public void RemapTest(object value, Type type, object expected) + { + Assert.AreEqual(expected, RangeAttribute.Remap(value, type)); + } + + public void ClampMinMaxThenRemapTest(object min, object max, object value, Type type, object expected) + { + Assert.AreEqual(expected, new RangeAttribute(min, max).ClampMinMaxThenRemap(value, type)); + } + + public void RemapMinMaxTest(object min, object max, object value, object expected) + { + Assert.AreEqual(expected, new RangeAttribute(min, max).RemapMinMax(value)); + } + #endregion } } diff --git a/ICD.Common.Utils/Attributes/RangeAttribute.cs b/ICD.Common.Utils/Attributes/RangeAttribute.cs index 7220488..ac304f5 100644 --- a/ICD.Common.Utils/Attributes/RangeAttribute.cs +++ b/ICD.Common.Utils/Attributes/RangeAttribute.cs @@ -385,9 +385,7 @@ namespace ICD.Common.Utils.Attributes throw new ArgumentException("Source value is not numeric"); double intermediate = RemapToDouble(value); - object remapped = RemapFromDouble(intermediate, type); - - return Convert.ChangeType(remapped, value.GetType(), CultureInfo.InvariantCulture); + return RemapFromDouble(intermediate, type); } /// From 7b6092c291b10a11c14ea46b7ad5172f55b71c39 Mon Sep 17 00:00:00 2001 From: Chris Cameron Date: Thu, 13 Jun 2019 17:35:03 -0400 Subject: [PATCH 5/6] fix: Fixing issues with Remap methods, added unit tests --- .../Attributes/RangeAttributeTest.cs | 4 +++ ICD.Common.Utils/Attributes/RangeAttribute.cs | 31 +++++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/ICD.Common.Utils.Tests/Attributes/RangeAttributeTest.cs b/ICD.Common.Utils.Tests/Attributes/RangeAttributeTest.cs index c53793f..b702d58 100644 --- a/ICD.Common.Utils.Tests/Attributes/RangeAttributeTest.cs +++ b/ICD.Common.Utils.Tests/Attributes/RangeAttributeTest.cs @@ -67,11 +67,15 @@ namespace ICD.Common.Utils.Tests.Attributes Assert.AreEqual(expected, RangeAttribute.Remap(value, type)); } + [TestCase(0, 100, ushort.MaxValue, typeof(ushort), ushort.MaxValue)] + [TestCase(0, 100, ushort.MaxValue, typeof(short), short.MaxValue)] public void ClampMinMaxThenRemapTest(object min, object max, object value, Type type, object expected) { Assert.AreEqual(expected, new RangeAttribute(min, max).ClampMinMaxThenRemap(value, type)); } + [TestCase(0, 100, ushort.MaxValue, 100)] + [TestCase(0, 100, ushort.MinValue, 0)] public void RemapMinMaxTest(object min, object max, object value, object expected) { Assert.AreEqual(expected, new RangeAttribute(min, max).RemapMinMax(value)); diff --git a/ICD.Common.Utils/Attributes/RangeAttribute.cs b/ICD.Common.Utils/Attributes/RangeAttribute.cs index ac304f5..db53ce9 100644 --- a/ICD.Common.Utils/Attributes/RangeAttribute.cs +++ b/ICD.Common.Utils/Attributes/RangeAttribute.cs @@ -413,7 +413,7 @@ namespace ICD.Common.Utils.Attributes double doubleValue = Convert.ToDouble(value); double clamped = MathUtils.Clamp(doubleValue, min, max); - object remapped = Remap(clamped, type); + object remapped = RemapMinMax(clamped, type); return Convert.ChangeType(remapped, value.GetType(), CultureInfo.InvariantCulture); } @@ -441,7 +441,34 @@ namespace ICD.Common.Utils.Attributes double remapped = MathUtils.MapRange(sourceMin, sourceMax, targetMin, targetMax, doubleValue); - return Convert.ChangeType(remapped, Min.GetType(), CultureInfo.InvariantCulture); + return Convert.ChangeType(remapped, value.GetType(), CultureInfo.InvariantCulture); + } + + private object RemapMinMax(object value, Type type) + { + if (value == null) + throw new ArgumentNullException("value"); + + if (type == null) + throw new ArgumentNullException("type"); + + if (!type.IsNumeric()) + throw new ArgumentException("Target type is not numeric"); + + if (!value.GetType().IsNumeric()) + throw new ArgumentException("Source value is not numeric"); + + double sourceMin = Convert.ToDouble(Min); + double sourceMax = Convert.ToDouble(Max); + + double targetMin = GetMinAsDouble(type); + double targetMax = GetMaxAsDouble(type); + + double doubleValue = Convert.ToDouble(value); + + double remapped = MathUtils.MapRange(sourceMin, sourceMax, targetMin, targetMax, doubleValue); + + return Convert.ChangeType(remapped, type, CultureInfo.InvariantCulture); } #endregion From 7666021925d937c8c348eec96862f7886bb3d751 Mon Sep 17 00:00:00 2001 From: Chris Cameron Date: Fri, 14 Jun 2019 10:25:47 -0400 Subject: [PATCH 6/6] chore: Updating changelog, incrementing minor version --- CHANGELOG.md | 4 ++++ ICD.Common.Utils/Properties/AssemblyInfo.cs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8340c01..9d9498f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [8.6.0] - 2019-06-14 +### Changed + - Overhaul of RangeAttribute remap methods to better avoid overflows + ## [8.5.0] - 2019-06-06 ### Added - Adding features to IcdEnvironment for tracking program initialization state diff --git a/ICD.Common.Utils/Properties/AssemblyInfo.cs b/ICD.Common.Utils/Properties/AssemblyInfo.cs index 9b3c993..1f3708a 100644 --- a/ICD.Common.Utils/Properties/AssemblyInfo.cs +++ b/ICD.Common.Utils/Properties/AssemblyInfo.cs @@ -4,4 +4,4 @@ using System.Reflection; [assembly: AssemblyCompany("ICD Systems")] [assembly: AssemblyProduct("ICD.Common.Utils")] [assembly: AssemblyCopyright("Copyright © ICD Systems 2019")] -[assembly: AssemblyVersion("8.5.0.0")] +[assembly: AssemblyVersion("8.6.0.0")]