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.
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.
You want to use PowerShell to calculate simple mathematical results.
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”.
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:
"[Math]:: Truncate()" → "[Math]::Truncate()" (superfluous space)
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
1You want to use PowerShell to calculate more complex or advanced mathematical results.
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:
"[Math]:: Pow()" → "[Math]::Pow()" (superfluous space)
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:
"[Math]:: Sin()" → "[Math]::Sin()" (superfluous space)
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.
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.
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)
25This 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.
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
You want to measure the numeric (minimum, maximum, sum, average) or textual (characters, words, lines) features of a list of objects.
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:
–Character and –Line are starting with an en dash instead of a hyphen-minus
PS > Get-ChildItem > output.txt
PS > Get-Content output.txt | Measure-Object -Character -Word -Line
Lines Words Characters Property
----- ----- ---------- --------
964 6083 33484By 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 AMFor more information about the Measure-Object cmdlet, type Get-Help Measure-Object.
You want to work with the individual bits of a number, or work with a number built by combining a series of flags.
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)
1234To 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 > 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 |
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.
Section “Measure Statistical Properties of a List”, first code block: There should be a space between Measure-Object and -Average.
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.
In the line below Example 7.2 there should be a space between Get-Help and Measure-Object.
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() ...
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)
16417This 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 100000000000000Knowing 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”.
You want to work with common administrative numbers (that is, kilobytes, megabytes, and gigabytes) without having to remember or calculate those numbers.
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
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
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)
1234To convert an octal number into its decimal
representation, supply a base of 8 to
the [Convert]::ToInt32()
method:
PS > [Convert]::ToInt32("1234", 8)
668To 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
04D2To 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
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”.
2 comments
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.
Thanks, I was able to figure out what you meant :)
Add a comment