Sunday, October 27, 2013

[Math] in PowerShell

Basic arithmetic is, of course, built-in to PowerShell.

3 + 2  yields  5
3 - 2  yields  1
3 * 2  yields  6
3 * ( 3 + 2 )   yields  15
3 / 2   yields  1.5

That last one is nice.  In some programming languages, if you divide two integers, it will give you an integer result; if you don't want to lose your precision, you need to convert your variable types before the calculation, which is messy.  But in PowerShell, the result is of a logical type, and in those rare circumstances when you need to change it, it's always very easy.

And that's about all of the math PowerShell can do.

Wait, what?  That's it?

That's pathetic.  My granddaughter can do more advanced math than that.  Why can't PowerShell do better than that?

It would have been nice if PowerShell had more advanced mathematical operators built in to it to allow us to write pretty mathematical equations that look like what we expect them to look like.  But that would have been difficult to do without breaking PowerShell's language parsing paradigms, forcing the whole thing to get klunkier and slower.  So they didn't.

But we can live without it, because anything you can do with .Net, we can leverage with PowerShell.

There is a System class named [math], with 30 static methods we can use for doing math.  (I think that's where they got the name from.)

[math] is a static class, which means you can't create a [math] object.  It is a collection of methods, code snippets that we can leverage in our scripts.

Because it is a static class, we can't use our usual trick of piping a variable to Get-Member to see what it can do. Instead, use:

[math].GetMethods()

If you actually tried that, you saw more than was useful.  Try piping it to a Select command to see the names of the 30 methods:

[math].GetMethods() | Select -Property Name -Unique

We use the -Unique switch to eliminate duplicates from our list.  There are duplicates because it has separate methods for each type of input parameters we might give it.  For example, there is a .Min() method for 32-bit integers, another for 64-bit integers, another for double length decimals, etc.  We won't have to differentiate between them, because .Net will dynamically choose the correct one based on what we feed into it.  And if none of them quite match, such as if we feed in strings that can be converted to numbers, PowerShell is kind enough to convert them to an appropriate number class for us. 

The other way to get details on what a class can do and how to use it, is to find the official Microsoft article about it.  Search for MSDN, the class name (including the namespace, if you know it), and the word "methods", for example.  In this case, Google "MSDN system.math methods" without the quotes.

Or just click here.  https://www.google.com/#q=MSDN+system.math+methods

Constants Pi and E


In addition to the 30 methods, there are also 2 useful fields, which give you the two most used mathematical constants.

To access a static method or field, we use the name of the class and the name of the method or field, separated by two colons.

[math]::e  yields  2.71828182845905
[math]::pi  yields  3.14159265358979

You can use them thusly:

$Area = [math]::pi * $Radius * $Radius

Powers and exponents and logarithms


You place the parameters for methods in parenthesis after the method name, separated by commas.  This is a little clunky if you are used to standard mathematical notation.  But if you are experienced with Excel formulas, you will feel right at home. One advantage these have over Excel formulas is that you can add spaces, making them actually readable.

::Pow(), short for "power" is the method for raising a number to a power.  For example, to raise two to the third power, two cubed:

[math]::pow( 2, 3 )
yields 8

So we could rewrite our area of a circle formula:

$Area = [math]::pi * [math]::pow( $Radius, 2 )

But I just stick to $x * $x when I need to square something, and save ::pow() for higher or fractional powers.

::exp() is specifically for raising e to a power, and not for more general exponents as you might logically think.  So it's not very useful, but if you need to square e, use:

[math]::exp( 2 )

::sqrt() is for square roots.

[math]::sqrt( 64 )
yields 8

[math] doesn't have a method for doing other roots directly.  This is when you have to dredge up the math you never thought you would use, and remember that taking a root is the same as raising to the power of the reciprocal of the root.

So, to find the cube root of 27:

[math]::pow( 27, 1/3 )
yields 3

Use ::log() to calculate the base e logarithm of a number and ::log10() to calculate the base 10 logarithm.

To calculate the logarithm in any other base, divide the logarithm of your number by the logarithm of the desired base.  Thus, to find the base 2 logarithm of $X, use:

[math]::log( $X ) / [math]::log( 2 )

Is that useful?  Sometimes.  The base 2 logarithm of a number tells you how many bits you need to represent that number.

Rounding and remainders


While there is a glaring lack of some types of mathematical functions, there is a bunch of them for rounding.

::round() is the one you will likely use most.  Give it a number and a number of places to the right of the decimal, and away it goes.

[math]::round( 1234.5678 , 2 )  yields  1234.57
[math]::round( 1234.5678 , 0 )  yields  1235
[math]::round( 1234.5678 , -2 )  yields  1200

I don't use [math]::round() when rounding to zero decimal places.  In that case it's much neater (pun intended) to take advantage of PowerShell's dynamic type conversion.

Instead of
[math]::round( $X, 0 )

I use
[int]( $X )

This causes PowerShell to convert the value to an integer, which is essentially what rounding to zero does anyway.

One caveat.  By default, both ::round() and [int] do "midpoint rounding" differently from what you may have learned in school.

I was taught that when the remainder was exactly 5, always round up.  That is what we will call "away from zero" midpoint rounding.

There is also something called banker's rounding, used by statisticians and the financial industry.  They would prefer to round up only half the time, so they always round to the nearest even number, what we'll call "to even" midpoint rounding.

::round() and [int] use "to even" rounding.

This means that:

[math]::round( 12.345 , 2 )  yields  12.34
[math]::round( 12.355 , 2 )  yields  12.36
[math]::round( 12.365 , 2 )  yields  12.36
[math]::round( 12.375 , 2 )  yields  12.38

If you want to use "away from zero" rounding, you can add an additional parameter to specify your desired rounding method.  The parameter takes the [system.midpointrounding] enumeration.  Or we can use a string and force PowerShell to do the conversion, as it's a little less messy that way.  Or, you can just use the integer behind the enumeration, which in this case is 1, but then the average person looking at your script won't know what that means.  All of these examples work to force "away from zero" midpoint rounding.

[math]::round( 12.345 , 2 , [system.midpointrounding]::AwayFromZero )  yields  12.35
[math]::round( 12.345 , 2 , [midpointrounding]::AwayFromZero )  yields  12.35
[math]::round( 12.345 , 2 , "AwayFromZero" )  yields  12.35
[math]::round( 12.345 , 2 , 1 )  yields  12.35

(If you really like extra typing or if you are using a variable to store your midpoint rounding method, you can use [midpointrounding]::ToEven  or "ToEven" to specify "to even" midpoint rounding.)

[int] cannot use anything other than "to even " midpoint rounding.

::truncate() simply truncates or chops off the decimal.  This is effectively toward zero rounding.  Note that this is NOT midpoint rounding.  Everything rounds toward zero, not just 5’s.

[math]::truncate( 2.2 )  yields  2
[math]::truncate( 2.8 )  yields  2
[math]::truncate( -2.2 )  yields  -2
[math]::truncate( -2.8 )  yields  -2

::ceiling() rounds everything up, towards positive infinity.

[math]::ceiling( 2.2 )  yields  3
[math]::ceiling( 2.8 )  yields  3
[math]::ceiling( -2.2 )  yields  -2
[math]::ceiling( -2.8 )  yields  -2

::floor() rounds everything down, towards negative infinity.

[math]::floor( 2.2 )  yields  2
[math]::floor( 2.8 )  yields  2
[math]::floor( -2.2 )  yields  -3
[math]::floor( -2.8 )  yields  -3

(And if those aren't enough, in my next article I talk about for using Excel's plethora of worksheet functions in PowerShell, which gives you another umpteen ways to round things.)

Minimum and maximum

::min() and ::max() have two uses.

The first is to choose the larger or smaller of two values.
[math]::min( 2 , 3 )   yields  2
[math]::max( 2 , 3 )   yields  3

The second is to set a maximum or minimum for a given value.  The weird trick is, when you're using them for this, you use the function with opposite name from the effect you want your limit to have.

To set a minimum of zero, for example, you take the maximum of your value and zero:

[math]::max( 20 , 0 )   yields  20
[math]::max( -20 , 0 )   yields  0

This seems obvious when you are looking at those two commands, but when writing these things off the top of your head with variables, it's easy to do them backwards.

$XButAMinimumOfZero = [math]::max( 0, $X )
$XButAMaximumOfTen = [math]::min( 10, $X )

Trigonometry


All of the basic trig functions are here.  Keep in mind that they assume the input parameter to be in radians, not degrees.  If you are using degrees, multiply your variable by pi divided by 180.

Sine of 45 degree would be:
[math]::sin( 45 / 180 * [math]::pi() )

Sine  ::sin()
Cosine  ::cos()
Tangent ::tan()

The "arc" functions are for doing it backwards.  They are for when you know the sine or cosine or tangent of the angle, and you want to know the angle.

Arcsine  ::asin()
Arccosine ::acos()
Arctangent ::atan()

The results are going to be in radians, so if you need degrees, you need to multiply the results by 180 divided by pi.

$AngleInDegrees = [math]::asin( $X ) * 180 / [math]::pi

::atan2() is for when you know the lengths of the sides of the triangle opposite and adjacent to a given angle, and you want to know the size of the angle.  Since dividing the two gives you the tangent, you can easily use ::atan() for this, but commas are better than forward slashes?  Whatever.

These both do the same thing:

$MouseAngleInDegrees = [math]::atan2( $DeltaY, $DeltaX ) * 180 / [math]::pi
$MouseAngleInDegrees = [math]::atan( $DeltaY / $DeltaX ) * 180 / [math]::pi

And then there are the hyperbolic trig functions, but if you need to use those, you already understand what those are for better than I do.  (It has been a long time since high school trig, and they are hardly ever needed for administering servers.)

Hyperbolic sine ::sinh()
Hyperbolic cosine ::cosh()
Hyperbolic tangent ::tanh()

Miscellaneous


::equals() compares two values and gives you a [boolean] (true/false) result.

Be careful with this one.  Unlike many of these methods, your input parameters will NOT dynamically adjust the input parameters so that the types match.

As far as this method is concerned, integer 7 does NOT equal decimal 7.0!

$X = 7
[math]::equals( $X, 0 )  yields  False
[math]::equals( $X, 7 )  yields  True
[math]::equals( $X, 7 )  yields  False
[math]::equals( $X, "7" ) yields False

It’s generally preferable to use PowerShell’s –eq comparison operator.  The –eq operator does convert between types and give the results you are likely expecting.

$X -eq 0   yields  False
$X -eq 7   yields  True
$X -eq 7.0   yields  True
$X -eq "7"   yields True

Plus syntactically it's closer to human-speak, so it's easier for  humans to understand when you say stuff like:

If ( $x -eq 7 ) { Stop-Computer }

::abs() returns the absolute value of a number.

[math]::abs( 10 )   yields  10
[math]::abs( -10 )   yields  10

::sign() returns a 1 for positive numbers, a 0 for zero, and a -1 for negative numbers.

[math]::sign( 45.2 )  yields  1
[math]::sign( 10 - 6 - 4 )  yields  0
[math]::sign( 5 - 72 )  yields  -1

::IEEERemainder() calculates the remainder of a division, but a little oddly.  Normally, when dividing X / Y, you find the greatest multiple of divisor X less than the dividend Y, and subtract it from dividend Y to get the remainder.  This function instead calculates the multiple of divisor Y that is closest to dividend X, even if it is greater than dividend X, and then subtracts.  So the IEEERemainder can be positive or negative, but its absolute value is always less than half of divisor Y.  Another way to look at it is that it tells you the minimum amount you would have to add or subtract from dividend X to make it an exact multiple of divisor Y.

[math]::ieeeremainder( 23, 7 )  yields  2
[math]::ieeeremainder( 21, 7 )  yields  0
[math]::ieeeremainder( 19, 7 )  yields  -2

::BigMul() is an oddball you'll never use.  It's just for multiplying and it's only useful if you need more then 32-bit precision, but oddly it only takes 32-bit precision numbers as input.  You will never need that much precision. If you do, you probably already have it in your multiplicands, and this function will fail.  It's better to just use PowerShell to dynamically convert your numbers, and multiply normally.

Instead of ::bigmul(), use:

$X = 1234567891
$Y = 1234567891
[int64]$X *[int64]$Y

Which yields 1524157877488187881

::divrem() will do division and give you the quotient and remainder in separate variables.  This one is for the software developers looking to shave milliseconds off of their processing time.  We're scripters.  We prefer simple and elegant.

Let's say we need to divide $X by $Y to get integer quotient $Q and remainder $R.  (::divrem() requires variable $R to exist before we use it, so we'll need to create it.  Developers always do that, but we're scripters, and we prefer simple and lazy.). We could use:

$R = 0
$Q = [math]::divrem( $X, $Y, [ref]$R )

But I would just use modulus (%) to calculate the reminder, and then use it to calculate the integer quotient.

$R = $X % $Y
$Q = ( $X -$R ) / $Y

Excel in Powershell


We can do most things with the [math] functions above.  But sometimes we need to get a little crazy.  Sometimes you wish you could do something that's easy in Excel, but next to impossible in PowerShell.

I’ll cover that in my next article.

5 comments:

  1. Nice post!

    Jeffrey Snover[MSFT]
    Distinguished Engineer

    ReplyDelete
  2. Atan2 is useful when the X component may be zero. Take the Atan of the point (0,1)... if you use division, you get a divide-by-zero. But not with Atan2!

    And, to get the methods, you can do [math]|get-member -static

    And if you need greater precision than the 15 digits of [Double], while still allowing floating-point values, you can use the [Decimal] type... 29 digits of precision, but with a smaller exponent range (10 to the +/- 29 instead of 308). A trailing D makes a constant [Decimal] - 123456789.123456789D / 987654321.987654321D

    -- mrento

    ReplyDelete
  3. Atan2 is useful when the X component may be zero. Take the Atan of the point (0,1)... if you use division, you get a divide-by-zero. But not with Atan2!

    And, to get the methods, you can do [math]|get-member -static

    And if you need greater precision than the 15 digits of [Double], while still allowing floating-point values, you can use the [Decimal] type... 29 digits of precision, but with a smaller exponent range (10 to the +/- 29 instead of 308). A trailing D makes a constant [Decimal] - 123456789.123456789D / 987654321.987654321D

    -- mrento

    ReplyDelete