diff --git a/src/Numerics.Tests/PrecisionTest.cs b/src/Numerics.Tests/PrecisionTest.cs index e151a4c7..d6b8905d 100644 --- a/src/Numerics.Tests/PrecisionTest.cs +++ b/src/Numerics.Tests/PrecisionTest.cs @@ -1172,13 +1172,13 @@ namespace MathNet.Numerics.UnitTests } [Test] - [TestCase(100.3123, -3, 0)] - [TestCase(1100.6123, -3, 1000)] - [TestCase(1500.8123, -3, 2000)] - [TestCase(110003245.3123, -3, 110003000)] - [TestCase(110003245.3123, -1, 110003250)] - [TestCase(110003245.3123, 2, 110003245.31)] - [TestCase(110003245.3123, 0, 110003245)] + [TestCase(100.3123d, -3, 0d)] + [TestCase(1100.6123d, -3, 1000d)] + [TestCase(1500.8123d, -3, 2000d)] + [TestCase(110003245.3123d, -3, 110003000d)] + [TestCase(110003245.3123d, -1, 110003250d)] + [TestCase(110003245.3123d, 2, 110003245.31d)] + [TestCase(110003245.3123d, 0, 110003245d)] public void RoundDouble(double number, int digit, double expectedResult) { Assert.AreEqual(expectedResult,number.Round(digit)); @@ -1212,19 +1212,18 @@ namespace MathNet.Numerics.UnitTests [Test] public void RoundUint32() { - Assert.AreEqual(110003250,((uint)110003245).Round(-1)); + Assert.AreEqual(110003250U,110003245U.Round(-1)); } - [Test] public void RoundUInt64() { - Assert.AreEqual(3250, ((ulong)3245).Round(-1)); + Assert.AreEqual(3250UL, 3245UL.Round(-1)); } [Test] - [TestCase(110003245, -1, 110003250)] - public void RoundInt64(long number, int digit, int expectedResult) + [TestCase(110003245L, -1, 110003250L)] + public void RoundInt64(long number, int digit, long expectedResult) { Assert.AreEqual(expectedResult, number.Round(digit)); } @@ -1252,5 +1251,34 @@ namespace MathNet.Numerics.UnitTests Assert.AreEqual(new BigInteger(110003245), new BigInteger(110003245).Round(2)); Assert.AreEqual(new BigInteger(110003245), new BigInteger(110003245).Round(0)); } + + [Test] + [TestCase(0.0, 2.0, 0.0)] + [TestCase(0.1, 2.0, 0.125)] + [TestCase(0.7, 2.0, 0.5)] + [TestCase(0.8, 2.0, 1.0)] + [TestCase(1.1, 2.0, 1.0)] + [TestCase(1.6, 2.0, 2.0)] + [TestCase(3.2, 2.0, 4.0)] + [TestCase(14.0, 2.0, 16.0)] + [TestCase(-14.0, 2.0, -16.0)] + public void RoundToPowerDouble(double number, double basis, double expectedResult) + { + Assert.AreEqual(expectedResult, number.RoundToPower(basis), 0.0); + } + + [Test] + [TestCase(0.0, 0.3, 0.0)] + [TestCase(0.1, 0.3, 0.0)] + [TestCase(0.2, 0.3, 0.3)] + [TestCase(0.8, 0.3, 0.9)] + [TestCase(1.1, 0.3, 1.2)] + [TestCase(1.6, 0.3, 1.5)] + [TestCase(3.2, 0.3, 3.3)] + [TestCase(-3.2, 0.3, -3.3)] + public void RoundToMultipleDouble(double number, double basis, double expectedResult) + { + Assert.AreEqual(expectedResult, number.RoundToMultiple(basis), Precision.DoublePrecision); + } } } diff --git a/src/Numerics/Precision.cs b/src/Numerics/Precision.cs index 122f817f..1c0dc7bb 100644 --- a/src/Numerics/Precision.cs +++ b/src/Numerics/Precision.cs @@ -746,7 +746,74 @@ namespace MathNet.Numerics } /// - /// Round to the number closest to 10^(-decimals). Supports negative decimals to round within the integer part. + /// Calculates the actual positive double precision machine epsilon - the smallest number that can be added to 1, yielding a results different than 1. + /// This is also known as unit roundoff error. According to the definition of Prof. Higham. + /// + /// Machine epsilon + static double MeasurePositiveMachineEpsilon() + { + double eps = 1.0d; + + while ((1.0d + (eps / 2.0d)) > 1.0d) + eps /= 2.0d; + + return eps; + } + + /// + /// Round to a multiple of the provided positive basis. + /// + /// Number to be rounded. + /// The basis to whose multiples to round to. Must be positive. + public static double RoundToMultiple(this double number, double basis) + { + return Math.Round(number / basis, MidpointRounding.AwayFromZero) * basis; + } + + /// + /// Round to a multiple of the provided positive basis. + /// + /// Number to be rounded. + /// The basis to whose multiples to round to. Must be positive. + public static float RoundToMultiple(this float number, float basis) + { + return (float) RoundToMultiple((double) number, basis); + } + + /// + /// Round to a multiple of the provided positive basis. + /// + /// Number to be rounded. + /// The basis to whose multiples to round to. Must be positive. + public static decimal RoundToMultiple(this decimal number, decimal basis) + { + return Math.Round(number / basis, MidpointRounding.AwayFromZero) * basis; + } + + /// + /// Round to a multiple of the provided positive basis. + /// + /// Number to be rounded. + /// The basis to whose powers to round to. Must be positive. + public static double RoundToPower(this double number, double basis) + { + return number < 0.0 + ? -Math.Pow(basis, Math.Round(Math.Log(-number, basis), MidpointRounding.AwayFromZero)) + : Math.Pow(basis, Math.Round(Math.Log(number, basis), MidpointRounding.AwayFromZero)); + } + + /// + /// Round to a multiple of the provided positive basis. + /// + /// Number to be rounded. + /// The basis to whose powers to round to. Must be positive. + public static float RoundToPower(this float number, float basis) + { + return (float) RoundToPower((double) number, basis); + } + + /// + /// Round to the number closest to 10^(-decimals). Negative decimals to round within the integer part. /// /// Number to be rounded /// Number of decimals to round to. Negative to round within the integer part, e.g. -3 will wound to the closes 1000. @@ -754,16 +821,13 @@ namespace MathNet.Numerics /// Rounded number public static double Round(this double number, int decimals) { - if (decimals >= 0) - { - return Math.Round(number, decimals, MidpointRounding.AwayFromZero); - } - - return Math.Round(number / Math.Pow(10, -decimals), MidpointRounding.AwayFromZero) * Math.Pow(10, -decimals); + return decimals >= 0 + ? Math.Round(number, decimals, MidpointRounding.AwayFromZero) + : RoundToMultiple(number, Math.Pow(10.0, -decimals)); } /// - /// Round to the number closest to 10^(-decimals). Supports negative decimals to round within the integer part. + /// Round to the number closest to 10^(-decimals). Negative decimals to round within the integer part. /// /// Number to be rounded /// Number of decimals to round to. Negative to round within the integer part, e.g. -3 will wound to the closes 1000. @@ -771,46 +835,39 @@ namespace MathNet.Numerics /// Rounded number public static float Round(this float number, int decimals) { - return (float) Round((decimal) number, decimals); + return (float) Round((double) number, decimals); } /// - /// Round to the number closest to 10^(-decimals). Supports negative decimals to round within the integer part. + /// Round to the number closest to 10^(-decimals). Negative decimals to round within the integer part. /// /// Number to be rounded /// Number of decimals to round to. Negative to round within the integer part, e.g. -3 will wound to the closes 1000. /// To round 123456789 to hundreds Round(123456789, -2) = 123456800 /// Rounded number public static decimal Round(this decimal number, int decimals) - { - if (decimals >= 0) - { - return Math.Round(number, decimals, MidpointRounding.AwayFromZero); - } - - decimal roundTo = (decimal) Math.Pow(10, -decimals); - return Math.Round(number / roundTo, MidpointRounding.AwayFromZero) * roundTo; + { + return decimals >= 0 + ? Math.Round(number, decimals, MidpointRounding.AwayFromZero) + : RoundToMultiple(number, (decimal) Math.Pow(10.0, -decimals)); } /// - /// Round to the number closest to 10^(-decimals). Supports negative decimals to round within the integer part. + /// Round to the number closest to 10^(-decimals). Negative decimals to round within the integer part. /// /// Number to be rounded /// Number of decimals to round to. Negative to round within the integer part, e.g. -3 will wound to the closes 1000. /// To round 123456789 to hundreds Round(123456789, -2) = 123456800 /// Rounded number public static int Round(this int number, int decimals) - { - if (decimals >= 0) - { - return number; - } - - return (int) Round((decimal) number, decimals); - } + { + return decimals >= 0 + ? number + : (int) Round((decimal) number, decimals); + } /// - /// Round to the number closest to 10^(-decimals). Supports negative decimals to round within the integer part. + /// Round to the number closest to 10^(-decimals). Negative decimals to round within the integer part. /// /// Number to be rounded /// Number of decimals to round to. Negative to round within the integer part, e.g. -3 will wound to the closes 1000. @@ -819,16 +876,13 @@ namespace MathNet.Numerics [CLSCompliant(false)] public static uint Round(this uint number, int decimals) { - if (decimals >= 0) - { - return number; - } - - return (uint) Round((decimal) number, decimals); + return decimals >= 0 + ? number + : (uint) Round((decimal) number, decimals); } /// - /// Round to the number closest to 10^(-decimals). Supports negative decimals to round within the integer part. + /// Round to the number closest to 10^(-decimals). Negative decimals to round within the integer part. /// /// Number to be rounded /// Number of decimals to round to. Negative to round within the integer part, e.g. -3 will wound to the closes 1000. @@ -836,16 +890,13 @@ namespace MathNet.Numerics /// Rounded number public static long Round(this long number, int decimals) { - if (decimals >= 0) - { - return number; - } - - return (long) Round((decimal) number, decimals); + return decimals >= 0 + ? number + : (long) Round((decimal) number, decimals); } /// - /// Round to the number closest to 10^(-decimals). Supports negative decimals to round within the integer part. + /// Round to the number closest to 10^(-decimals). Negative decimals to round within the integer part. /// /// Number to be rounded /// Number of decimals to round to. Negative to round within the integer part, e.g. -3 will wound to the closes 1000. @@ -854,15 +905,13 @@ namespace MathNet.Numerics [CLSCompliant(false)] public static ulong Round(this ulong number, int decimals) { - if (decimals >= 0) - { - return number; - } - - return (ulong) Round((decimal) number, decimals); + return decimals >= 0 + ? number + : (ulong) Round((decimal) number, decimals); } + /// - /// Round to the number closest to 10^(-decimals). Supports negative decimals to round within the integer part. + /// Round to the number closest to 10^(-decimals). Negative decimals to round within the integer part. /// /// Number to be rounded /// Number of decimals to round to. Negative to round within the integer part, e.g. -3 will wound to the closes 1000. @@ -870,16 +919,13 @@ namespace MathNet.Numerics /// Rounded number public static short Round(this short number, int decimals) { - if (decimals >= 0) - { - return number; - } - - return (short) Round((decimal) number, decimals); + return decimals >= 0 + ? number + : (short) Round((decimal) number, decimals); } /// - /// Round to the number closest to 10^(-decimals). Supports negative decimals to round within the integer part. + /// Round to the number closest to 10^(-decimals). Negative decimals to round within the integer part. /// /// Number to be rounded /// Number of decimals to round to. Negative to round within the integer part, e.g. -3 will wound to the closes 1000. @@ -888,16 +934,13 @@ namespace MathNet.Numerics [CLSCompliant(false)] public static ushort Round(this ushort number, int decimals) { - if (decimals >= 0) - { - return number; - } - - return (ushort) Round((decimal) number, decimals); + return decimals >= 0 + ? number + : (ushort) Round((decimal) number, decimals); } /// - /// Round to the number closest to 10^(-decimals). Supports negative decimals to round within the integer part. + /// Round to the number closest to 10^(-decimals). Negative decimals to round within the integer part. /// /// Number to be rounded /// Number of decimals to round to. Negative to round within the integer part, e.g. -3 will wound to the closes 1000. @@ -921,21 +964,6 @@ namespace MathNet.Numerics return divided * BigInteger.Pow(10, -decimals); } - /// - /// Calculates the actual positive double precision machine epsilon - the smallest number that can be added to 1, yielding a results different than 1. - /// This is also known as unit roundoff error. According to the definition of Prof. Higham. - /// - /// Machine epsilon - static double MeasurePositiveMachineEpsilon() - { - double eps = 1.0d; - - while ((1.0d + (eps / 2.0d)) > 1.0d) - eps /= 2.0d; - - return eps; - } - [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] static double Truncate(double value) {