diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Measure-Object.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Measure-Object.cs index 0a16b3a3155..62bebc4d212 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Measure-Object.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Measure-Object.cs @@ -34,7 +34,7 @@ public sealed class GenericMeasureInfo : MeasureInfo /// public GenericMeasureInfo() { - Average = Sum = Maximum = Minimum = null; + Average = Sum = Maximum = Minimum = StandardDeviation = null; } /// @@ -71,6 +71,11 @@ public GenericMeasureInfo() /// /// public double? Minimum { get; set; } + + /// + /// The Standard Deviation of property values. + /// + public double? StandardDeviation { get; set; } } /// @@ -90,7 +95,7 @@ public sealed class GenericObjectMeasureInfo : MeasureInfo /// public GenericObjectMeasureInfo() { - Average = Sum = null; + Average = Sum = StandardDeviation = null; Maximum = Minimum = null; } @@ -128,6 +133,11 @@ public GenericObjectMeasureInfo() /// /// public object Minimum { get; set; } + + /// + /// The Standard Deviation of property values. + /// + public double? StandardDeviation { get; set; } } /// @@ -225,6 +235,8 @@ private class Statistics // Generic/Numeric statistics internal double sum = 0.0; + internal double sumPrevious = 0.0; + internal double variance = 0.0; internal object max = null; internal object min = null; @@ -263,6 +275,24 @@ public MeasureObjectCommand() #endregion Common parameters in both sets + /// + /// Set to true if Standard Deviation is to be returned. + /// + [Parameter(ParameterSetName = GenericParameterSet)] + public SwitchParameter StandardDeviation + { + get + { + return _measureStandardDeviation; + } + set + { + _measureStandardDeviation = value; + } + } + + private bool _measureStandardDeviation; + /// /// Set to true is Sum is to be returned /// @@ -525,7 +555,7 @@ private void AnalyzeValue(string propertyName, object objValue) AnalyzeString(strValue, stat); } - if (_measureAverage || _measureSum) + if (_measureAverage || _measureSum || _measureStandardDeviation) { double numValue = 0.0; if (!LanguagePrimitives.TryConvertTo(objValue, out numValue)) @@ -708,8 +738,19 @@ private void AnalyzeString(string strValue, Statistics stat) /// private void AnalyzeNumber(double numValue, Statistics stat) { - if (_measureSum || _measureAverage) + if (_measureSum || _measureAverage || _measureStandardDeviation) + { + stat.sumPrevious = stat.sum; stat.sum += numValue; + } + if (_measureStandardDeviation && stat.count > 1) + { + // Based off of iterative method of calculating variance on + // https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online_algorithm + double avgPrevious = stat.sumPrevious / (stat.count - 1); + stat.variance *= (stat.count - 2.0) / (stat.count - 1); + stat.variance += (numValue - avgPrevious) * (numValue - avgPrevious) / stat.count; + } } /// @@ -790,6 +831,7 @@ private MeasureInfo CreateGenericMeasureInfo(Statistics stat, bool shouldUseGene { double? sum = null; double? average = null; + double? StandardDeviation = null; object max = null; object min = null; @@ -797,8 +839,14 @@ private MeasureInfo CreateGenericMeasureInfo(Statistics stat, bool shouldUseGene { if (_measureSum) sum = stat.sum; + if (_measureAverage && stat.count > 0) average = stat.sum / stat.count; + + if (_measureStandardDeviation) + { + StandardDeviation = Math.Sqrt(stat.variance); + } } if (_measureMax) @@ -835,6 +883,7 @@ private MeasureInfo CreateGenericMeasureInfo(Statistics stat, bool shouldUseGene gmi.Count = stat.count; gmi.Sum = sum; gmi.Average = average; + gmi.StandardDeviation = StandardDeviation; if (null != max) { gmi.Maximum = (double)max; @@ -887,7 +936,7 @@ private TextMeasureInfo CreateTextMeasureInfo(Statistics stat) /// /// Whether or not a numeric conversion error occurred. - /// If true, then average/sum will not be output. + /// If true, then average/sum/standard deviation will not be output. /// private bool _nonNumericError = false; diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Measure-Object.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Measure-Object.Tests.ps1 index 6791d790a1a..0a5b4af2a93 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Measure-Object.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Measure-Object.Tests.ps1 @@ -3,6 +3,7 @@ Describe "Measure-Object" -Tags "CI" { BeforeAll { $testObject = 1,3,4 + $testObject2 = 1..100 } It "Should be able to be called without error" { @@ -17,6 +18,51 @@ Describe "Measure-Object" -Tags "CI" { $($testObject | Measure-Object).Count | Should Be $testObject.Length } + It "Should calculate Standard Deviation" { + $actual = ($testObject | Measure-Object -StandardDeviation) + # We check this way since .StandardDeviation returns a double value + # 1.52752523165195 was calculated outside powershell using formula from + # http://mathworld.wolfram.com/StandardDeviation.html + [Math]::abs($actual.StandardDeviation - 1.52752523165195) | Should -BeLessThan .00000000000001 + } + + + It "Should calculate Standard Deviation" { + $actual = ($testObject2 | Measure-Object -StandardDeviation) + # We check this way since .StandardDeviation returns a double value + # 29.011491975882 was calculated outside powershell using formula from + # http://mathworld.wolfram.com/StandardDeviation.html + [Math]::abs($actual.StandardDeviation - 29.011491975882) | Should -BeLessThan .0000000000001 + } + + It "Should calculate Standard Deviation with -Sum" { + $actual = ($testObject | Measure-Object -Sum -StandardDeviation) + # We check this way since .StandardDeviation returns a double value + $actual.Sum | Should Be 8 + # 1.52752523165195 was calculated outside powershell using formula from + # http://mathworld.wolfram.com/StandardDeviation.html + [Math]::abs($actual.StandardDeviation - 1.52752523165195) | Should -BeLessThan .00000000000001 + } + + It "Should calculate Standard Deviation with -Average" { + $actual = ($testObject | Measure-Object -Average -StandardDeviation) + # We check this way since .StandardDeviation returns a double value + [Math]::abs($actual.Average - 2.66666666666667) | Should -BeLessThan .00000000000001 + # 1.52752523165195 was calculated outside powershell using formula from + # http://mathworld.wolfram.com/StandardDeviation.html + [Math]::abs($actual.StandardDeviation - 1.52752523165195) | Should -BeLessThan .00000000000001 + } + + It "Should calculate Standard Deviation with -Sum -Average" { + $actual = ($testObject2 | Measure-Object -Sum -Average -StandardDeviation) + # We check this way since .StandardDeviation returns a double value + $actual.Sum | Should Be 5050 + $actual.Average | Should Be 50.5 + # 29.011491975882 was calculated outside powershell using formula from + # http://mathworld.wolfram.com/StandardDeviation.html + [Math]::abs($actual.StandardDeviation - 29.011491975882) | Should -BeLessThan .0000000000001 + } + It "Should be able to count using the Property switch" { $expected = $(Get-ChildItem $TestDrive).Length $actual = $(Get-ChildItem $TestDrive | Measure-Object -Property Length).Count