Chapter 6. Calculations and Math

Introduction

Math is an important feature in any scripting language. Math support in a language includes addition, subtraction, multiplication, and division of course, but extends further into more advanced mathematical operations. So, it should not surprise you that PowerShell provides a strong suite of mathematical and calculation-oriented features.

2 comments

  1. Johannes Rössel Posted 18 days ago

    I'd take this for review (and, in fact, already done). I just added the comments before the actual ready-announcement, so there was only one comment spot back then. I wonder whether I should re-add those comments in the appropriate places now.

  2. Lee Holmes Posted 15 days and 16 hours ago

    Thanks, I was able to figure out what you meant :)

Add a comment

Since PowerShell provides full access to its scripting language from the command line, though, this keeps a powerful and useful command-line calculator always at your fingertips!

In addition to its support for traditional mathematical operations, PowerShell also caters to system administrators by working natively with concepts such as megabytes and gigabytes, simple statistics (such as sum and average), and conversions between bases.

Perform Simple Arithmetic

Problem

You want to use PowerShell to calculate simple mathematical results.

Solution

Use PowerShell's arithmetic operators:

+

Addition

-

Subtraction

*

Multiplication

/

Division

%

Modulus

+=, -=, *=, /=, and %=

Assignment variations of the above

()

Precedence/Order of operations

For a detailed description of these mathematical operators, see the section called “Simple Operators”.

Discussion

One difficulty in many programming languages comes from the way that they handle data in variables. For example, this C# snippet stores the value of "1" in the result variable, when the user probably wanted the result to hold the floating point value of 1.5:

double result = 0;
result = 3/2;

This is because C# (along with many other languages) determines the result of the division from the type of data being used in the division. In the example above, it decides that you want the answer to be an integer since you used two integers in the division.

PowerShell, on the other hand, avoids this problem. Even if you use two integers in a division, PowerShell returns the result as a floating point number if required. This is called widening.

PS > $result = 0
PS > $result = 3/2
PS > $result
1.5

One exception to this automatic widening is when you explicitly tell PowerShell the type of result you want. For example, you might use an integer cast ([int]) to say that you want the result to be an integer after all:

PS > $result = [int] (3/2)
PS > $result
2

Many programming languages drop the portion after the decimal point when they convert them from floating point numbers to integers. This is called truncation. PowerShell, on the other hand, uses banker's rounding for this conversion. It converts floating point numbers to their nearest integer, rounding to the nearest even number in case of a tie.

Several programming techniques use truncation, though, so it is still important that a scripting language somehow support it. PowerShell does not have a built-in operator that performs a truncation-style division, but it does support it through the [Math]:: Truncate() method in the .NET Framework:

1 comment

  1. Johannes Rössel Posted 20 hours ago

    "[Math]:: Truncate()" → "[Math]::Truncate()" (superfluous space)

Add a comment

PS > $result = 3/2
PS > [Math]::Truncate($result)
1

If that syntax seems burdensome, the following example defines a trunc function that truncates its input:

PS > function trunc($number) { [Math]::Truncate($number) }
PS > $result = 3/2
PS > trunc $result
1

Perform Complex Arithmetic

Problem

You want to use PowerShell to calculate more complex or advanced mathematical results.

Solution

PowerShell supports more advanced mathematical tasks primarily through its support for the System.Math class in the .NET Framework.

To find the absolute value of a number, use the [Math]::Abs() method:

PS > [Math]::Abs(-10.6)
10.6

To find the power (such as the square or the cube) of a number, use the [Math]:: Pow() method. In this case, finding 123 squared:

1 comment

  1. Johannes Rössel Posted 20 hours ago

    "[Math]:: Pow()" → "[Math]::Pow()" (superfluous space)

Add a comment

PS > [Math]::Pow(123, 2)
15129

To find the square root of a number, use the [Math]::Sqrt() method:

PS > [Math]::Sqrt(100)
10

To find the sine, cosine, or tangent of an angle (given in radians), use the [Math]:: Sin(), [Math]::Cos(), or [Math]::Tan() method:

1 comment

  1. Johannes Rössel Posted 20 hours ago

    "[Math]:: Sin()" → "[Math]::Sin()" (superfluous space)

Add a comment

PS > [Math]::Sin( [Math]::PI / 2 )
1

To find the angle (given in radians) of a sine, cosine, or tangent value, use the [Math]::ASin(), [Math]::ACos(), or [Math]::ATan() method:

PS > [Math]::ASin(1)
1.5707963267949

See the section called “Learn About Types and Objects” to learn how to find out what other features the System.Math class provides.

Discussion

Once you start working with the System.Math class, it may seem as though its designers left out significant pieces of functionality. The class supports the square root of a number, but doesn't support other roots (such as the cube root). It supports sine, cosine, and tangent (and their inverses) in radians, but not in the more commonly used measure of degrees.

Working with any root

To determine any root (such as the cube root) of a number, you can use the function given in Example 6.1, “A root function and some example calculations”.

Example 6.1. A root function and some example calculations

PS > function root($number, $root) { [Math]::Exp($([Math]::Log($number) / $root)) }
PS > root 64 3
4
PS > root 25 5
1.90365393871588
PS > [Math]::Pow(1.90365393871588, 5)
25.0000000000001
PS > [Math]::Pow( $(root 25 5), 5)
25

This function applies the mathematical fact that the square root of a number is the same as raising that number to the power of 1/2, the cube of a number is the same as raising it to the power of 1/3, etc.

The example also illustrates a very important point about math on computers. When you use this function (or anything else that manipulates floating point numbers), always be aware that the results of floating point answers are only ever approximations of the actual result. If you combine multiple calculations in the same statement (or store intermediate results into variables), programming and scripting languages can sometimes keep an accurate answer (such as in the second [Math]::Pow() attempt), but that exception is rare.

Some mathematical systems avoid this problem by working with equations and calculations as symbols (and not numbers). Like humans, these systems know that taking the square of a number that you just took the square root of gives you the original number right back—so they don't actually have to do either of those operations. These systems, however, are extremely specialized and usually very expensive.

Working with degrees instead of radians

Converting radians (the way that mathematicians commonly measure angles) to degrees (the way that most people commonly measure angles) is much more straight-forward than the root function. A circle has 2 * Pi radians if you measure in radians, and 360 degrees if you measure in degrees. That gives the following two functions:

PS > function Convert-RadiansToDegrees($angle) { $angle / (2 * [Math]::Pi) * 360 }
PS > function Convert-DegreesToRadians($angle) { $angle / 360 * (2 * [Math]::Pi) }

and their usage:

PS > Convert-RadiansToDegrees ([Math]::Pi)
180
PS > Convert-RadiansToDegrees ([Math]::Pi / 2)
90
PS > Convert-DegreesToRadians 360
6.28318530717959
PS > Convert-DegreesToRadians 45
0.785398163397448
PS > [Math]::Tan( (Convert-DegreesToRadians 45) )
1

Measure Statistical Properties of a List

Problem

You want to measure the numeric (minimum, maximum, sum, average) or textual (characters, words, lines) features of a list of objects.

Solution

Use the Measure-Object cmdlet to measure these statistical properties of a list.

To measure the numeric features of a stream of objects, pipe those objects to the Measure-Object cmdlet:

PS > 1..10 | Measure-Object -Average -Sum


Count    : 10
Average  : 5.5
Sum      : 55
Maximum  :
Minimum  :
Property :

To measure the numeric features of a specific property in a stream of objects, supply that property name to the –Property parameter of the Measure-Object cmdlet. For example, in a directory with files:

PS > Get-ChildItem | Measure-Object -Property Length -Max -Min -Average -Sum


Count    : 427
Average  : 10617025.4918033
Sum      : 4533469885
Maximum  : 647129088
Minimum  : 0
Property : Length

To measure the textual features of a stream of objects, use the –Character, -Word, and –Line parameters of the Measure-Object cmdlet:

1 comment

  1. Johannes Rössel Posted 20 hours ago

    –Character and –Line are starting with an en dash instead of a hyphen-minus

Add a comment

PS > Get-ChildItem > output.txt
PS > Get-Content output.txt | Measure-Object -Character -Word -Line

            Lines             Words       Characters Property
            -----             -----       ---------- --------
              964              6083            33484

Discussion

By default, the Measure-Object cmdlet counts only the number of objects it receives. If you want to measure additional properties (such as the maximum, minimum, average, sum, characters, words, or lines) of those objects, then you need to specify them as options to the cmdlet.

For the numeric properties, though, you usually don't want to measure the objects themselves. Instead, you probably want to measure a specific property from the list—such as the Length property of a file. For that purpose, the Measure-Object cmdlet supports the –Property parameter to which you provide the property you want to measure.

Sometimes, you might want to measure a property that isn't a simple number—such as the LastWriteTime property of a file. Since the LastWriteTime property is a DateTime, you can't determine its average immediately. However, if any property allows you to convert it to a number and back in a meaningful way (such as the Ticks property of a DateTime), then you can still compute its statistical properties. Example 6.2, “Using the Ticks property of the DateTime class to determine the average LastWriteTime of a list of files” shows how to get the average LastWriteTime from a list of files.

Example 6.2. Using the Ticks property of the DateTime class to determine the average LastWriteTime of a list of files

PS > ## Get the LastWriteTime from each file
PS > $times = dir | Foreach-Object { $_.LastWriteTime }

PS > ## Measure the average Ticks property of those LastWriteTime
PS > $results = $times | Measure-Object Ticks -Average

PS > ## Create a new DateTime out of the average Ticks
PS > New-Object DateTime $results.Average

Sunday, June 11, 2006 6:45:01 AM

For more information about the Measure-Object cmdlet, type Get-Help Measure-Object.

Work with Numbers As Binary

Problem

You want to work with the individual bits of a number, or work with a number built by combining a series of flags.

Solution

To directly enter a hexadecimal number, use the 0x prefix:

PS > $hexNumber = 0x1234
PS > $hexNumber
4660

To convert a number to its binary representation, supply a base of 2 to the [Convert]::ToString() method:

PS > [Convert]::ToString(1234, 2)
10011010010

To convert a binary number into its decimal representation, supply a base of 2 to the [Convert]::ToInt32() method:

PS > [Convert]::ToInt32("10011010010", 2)
1234

To manage the individual bits of a number, use PowerShell's binary operators. In this case, the Archive flag is just one of the many possible attributes that may be true of a given file:

PS > $archive = [System.IO.FileAttributes] "Archive"
PS > attrib +a test.txt
PS > Get-ChildItem | Where { $_.Attributes -band $archive } | Select Name

Name
----
test.txt
PS > attrib -a test.txt
PS > Get-ChildItem | Where { $_.Attributes -band $archive } | Select Name
PS > 

Discussion

In some system administration tasks, it is common to come across numbers that seem to mean nothing by themselves. The attributes of a file are a perfect example:

PS > (Get-Item test.txt).Encrypt()
PS > (Get-Item test.txt).IsReadOnly = $true
PS > [int] (Get-Item test.txt -force).Attributes
16417
PS > (Get-Item test.txt -force).IsReadOnly = $false
PS > (Get-Item test.txt).Decrypt()
PS > [int] (Get-Item test.txt).Attributes
32

What can the numbers 16417 and 32 possibly tell us about the file?

The answer to this comes from looking at the attributes in another light—as a set of features that can be either True or False. Take, for example, the possible attributes for an item in a directory shown by Example 6.3, “Possible attributes of a file”.

Example 6.3. Possible attributes of a file

PS > [Enum]::GetNames([System.IO.FileAttributes])
ReadOnly
Hidden
System
Directory
Archive
Device
Normal
Temporary
SparseFile
ReparsePoint
Compressed
Offline
NotContentIndexedEncrypted

If a file is ReadOnly, Archive, and Encrypted, then you might consider this as a succinct description of the attributes on that file:

ReadOnly = True
Archive = True
Encrypted = True

It just so happens that computers have an extremely concise way of representing sets of true and false values—a representation known as binary. To represent the attributes of a directory item as binary, you simply put them in a table. We give the item a "1" if the attribute applies to the item and a "0" otherwise (see Table 6.1, “Attributes of a directory item”).

Table 6.1. Attributes of a directory item

Attribute

True (1) or False (0)

Encrypted

1

NotContentIndexed

0

Offline

0

Compressed

0

ReparsePoint

0

SparseFile

0

Temporary

0

Normal

0

Device

0

Archive

1

Directory

0

<Unused>

0

System

0

Hidden

0

ReadOnly

1


5 comments

  1. Johannes Rössel Posted 19 days and 20 hours ago

    Example 7.1: While calculating the square root through e^x and the natural logarithm works fine, I think most people (at least considering what I learnt in school) would be more comfortable with just representing the nth root of x as x^(1/n) (which is what the code there does anyway, just through the more convoluted way of chaining e^x and the natural logarithm and applying some transformations which are basic logarithm knowledge but probably not everyone remembers). So, long story short, I'd say writing the root function as

    function root($number, $root) { [Math]::Pow($number, 1 / $root) }
    

    is a little easier to understand.

    If there are possible accuracy reasons advocating explicitly for the ln/e^x variant (which is what ::Pow probably uses behind the scenes as well), then mentioning that would be nice. Currently I think the method is more complex than it needs to be.

  2. Johannes Rössel Posted 19 days and 20 hours ago

    Section “Measure Statistical Properties of a List”, first code block: There should be a space between Measure-Object and -Average.

  3. Johannes Rössel Posted 19 days and 20 hours ago

    Another comment on Example 7.1 and the explanations of it: The problem with the code

    PS >[Math]::Pow(1.90365393871588, 5)
    25.0000000000001
    

    is rather that it uses a copy/pasted decimal approximation of a number which, as the result of the previous operation has more accuracy than is displayed. However, when storing the result in a variable, the result is exact:

    PS C:\Users\Joey> $d = root 25 5
    PS C:\Users\Joey> [Math]::Pow($d, 5)
    25
    

    Generally, performing multiple operations on floating-point numbers rather decrease the accuracy; nearly every language short of a computer-algebra system will not optimize function calls like

    [Math]::Pow( $(root 25 5), 5)
    

    The perceived gain in accuracy is rather because this code does not use a decimal approximation for the double value; its function is the same as above code using the temporary double variable $d. Usually, less operations keep most of the initial accuracy of the value. However, I'm unable right now to come up with a nice analogy or example to bring that point across in an understandable and concise manner.

  4. Johannes Rössel Posted 19 days and 20 hours ago

    In the line below Example 7.2 there should be a space between Get-Help and Measure-Object.

  5. Johannes Rössel Posted 19 days and 19 hours ago

    Regarding section Simplify Math with Administrative Constants: Discussion: This might be a nice point to introduce exponential notation for floating-point values–it would make a calculation involving gigabytes and gibibytes a little less elaborate:

    PS> $kilobyte = 1e3
    PS> $megabyte = 1e6
    PS> $gigabyte = 1e9
    PS> Get-Variable *byte | sort Value
    
    Name                           Value
    ----                           -----
    kilobyte                       1000
    megabyte                       1000000
    gigabyte                       1000000000
    

    It has the added benefit that one doesn't need to count zeroes or resort to calls to [Math]::Pow (which, with a base of 10 does exactly the same).

    It also reduces the question of how many gibibytes a 300 gigabyte hard drive has to:

    PS> 300e9 / 1gb
    279,396772384644
    

    However, this probably requires more immediate familiarity with SI prefixes and their associated exponents of 10. Then again, so do all those calls to [Math]::Pow() ...

Add a comment

If we treat those features as the individual binary digits in a number, that gives us the number 100000000100001. If we convert that number to its decimal form, it becomes clear where the number 16417 came from:

PS > [Convert]::ToInt32("100000000100001", 2)
16417

This technique sits at the core of many properties that you can express as a combination of features or flags. Rather than list the features in a table, though, documentation usually describes the number that would result from that feature being the only one active—such as FILE_ATTRIBUTE_REPARSEPOINT = 0x400. Example 6.4, “Integer, hexadecimal, and binary representations of possible file attributes” shows the various representations of these file attributes.

Example 6.4. Integer, hexadecimal, and binary representations of possible file attributes

PS > $attributes = [Enum]::GetValues([System.IO.FileAttributes])
PS > $attributes | Select-Object `
>>    @{"Name"="Property";
>>        "Expression"= { $_ } },
>>    @{"Name"="Integer";
>>        "Expression"= { [int] $_ } },
>>    @{"Name"="Hexadecimal";
>>        "Expression"= { [Convert]::ToString([int] $_, 16) } },
>>    @{"Name"="Binary";
>>        "Expression"= { [Convert]::ToString([int] $_, 2) } } |
>>    Format-Table -auto
>>

          Property Integer Hexadecimal Binary
          -------- ------- ----------- ------
          ReadOnly       1 1           1
            Hidden       2 2           10
            System       4 4           100
         Directory      16 10          10000
           Archive      32 20          100000
            Device      64 40          1000000
            Normal     128 80          10000000
         Temporary     256 100         100000000
        SparseFile     512 200         1000000000
      ReparsePoint    1024 400         10000000000
        Compressed    2048 800         100000000000
           Offline    4096 1000        1000000000000
 NotContentIndexed    8192 2000        10000000000000
         Encrypted   16384 4000        100000000000000

Knowing how that 16417 number was formed, you can now use the properties in meaningful ways. For example, PowerShell's –band operator allows you to check if a certain bit has been set:

PS > $encrypted = 16384
PS > $attributes = (Get-Item test.txt -force).Attributes
PS > ($attributes -band $encrypted) -eq $encrypted
True
PS > $compressed = 2048
PS > ($attributes -band $compressed) -eq $compressed
False
PS > 

Although the example above uses the numeric values explicitly, it would be more common to enter the number by its name:

PS > $archive = [System.IO.FileAttributes] "Archive"
PS > ($attributes -band $archive) -eq $archive
True

For more information about PowerShell's binary operators, see the section called “Simple Operators”.

Simplify Math with Administrative Constants

Problem

You want to work with common administrative numbers (that is, kilobytes, megabytes, and gigabytes) without having to remember or calculate those numbers.

Solution

Use PowerShell's administrative constants (KB, MB, GB, TB, and PB) to help work with these common numbers.

Calculate the download time (in seconds) of a 10.18 megabyte file over a connection that gets 215 kilobytes per second:

PS > 10.18mb / 215kb
48.4852093023256

Discussion

PowerShell's administrative constants are based on powers of two, since those are the kind most commonly used when working with computers. Each is 1,024 times bigger than the one before it:

1kb = 1024
1mb = 1024 * 1 kb
1gb = 1024 * 1 mb
1tb = 1024 * 1 gb
1pb = 1024 * 1 tb

Some people (such as hard drive manufacturers) prefer to call numbers based on powers of two "kibibytes," "mebibytes," and "gibibytes." They use the terms "kilobytes," "megabytes," and "gigabytes" to mean numbers that are 1,000 times bigger than the one before it—numbers based on powers of 10.

Although not represented by administrative constants, PowerShell still makes it easy to work with these numbers in powers of 10—for example, to figure out how big a "300 GB" hard drive is when reported by Windows. To do this, use scientific (exponential) notation:

PS > $kilobyte = 10e3
PS > $kilobyte
1000
PS > $megabyte = 10e6
PS > $megabyte
1000000
PS > $gigabyte = 10e9
PS > $gigabyte
1000000000
PS > (300 * $gigabyte) / 1GB
279.396772384644

Convert Numbers Between Bases

Problem

You want to convert a number to a different base.

Solution

The PowerShell scripting language allows you to enter both decimal and hexadecimal numbers directly. It does not natively support other number bases, but its support for interaction with the .NET Framework enables conversion both to and from binary, octal, decimal, and hexadecimal.

To convert a hexadecimal number into its decimal representation, prefix the number by 0x to enter the number as hexadecimal:

PS > $myErrorCode = 0xFE4A
PS > $myErrorCode
65098

To convert a binary number into its decimal representation, supply a base of 2 to the [Convert]::ToInt32() method:

PS > [Convert]::ToInt32("10011010010", 2)
1234

To convert an octal number into its decimal representation, supply a base of 8 to the [Convert]::ToInt32() method:

PS > [Convert]::ToInt32("1234", 8)
668

To convert a number into its hexadecimal representation, use either the [Convert] class or PowerShell's format operator:

PS > ## Use the [Convert] class
PS > [Convert]::ToString(1234, 16)
4d2

PS > ## Use the formatting operator
PS > "{0:X4}" -f 1234
04D2

To convert a number into its binary representation, supply a base of 2 to the [Convert]::ToString() method:

PS > [Convert]::ToString(1234, 2)
10011010010

To convert a number into its octal representation, supply a base of 8 to the [Convert]::ToString() method:

PS > [Convert]::ToString(1234, 8)
2322

Discussion

It is most common to want to convert numbers between bases when you are dealing with numbers that represent binary combinations of data, such as the attributes of a file. For more information on how to work with binary data like this, see the section called “Work with Numbers As Binary”.

You must sign in or register before commenting
*
*
*
*
*

Atom Icon Comments on this page or Comments on the whole book.