When you are dealing with big(ger) memory or storage values, it’s often not easy to present these in a human-readable format. I don’t know about you, but I find a number like 766509056 less readable, and meaningful, then 731 MB in a report.
You can introduce some nested If-Then-Else constructs in your scripts to do the conversion, but why repeat these in all your scripts ? That’s why I decided to write me a handy function, that would solve this problem once and for all.
Update November 14th 2011: handled passing a 0 to the Log function.
With the help of some Math functions, it turns out the process is quite easy.
The function
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
function Get-FriendlyUnit{ <# .SYNOPSIS Convert numbers into smaller binary multiples .DESCRIPTION The function accepts a value and will convert it into the biggest binary unit available. .NOTES Author: Luc Dekens .PARAMETER Value The value you want to convert. This number must be positive. .PARAMETER IEC A switch to indicate if the function shall return the IEC unit names, or the more commonly used unit names. The default is to use the commonly used unit names. .EXAMPLE PS> Get-FriendlyUnit -Value 123456 .EXAMPLE PS> 123456 | Get-FriendlyUnit -IEC .EXAMPLE PS> Get-FriendlyUnit -Value 123456,789123, 45678 #> param( [CmdletBinding()] [parameter(Mandatory = $true, ValueFromPipeline = $true)] [double[]]$Value, [switch]$IEC ) begin{ $OldUnits = "B","KB","MB","GB","TB","PB","EB","ZB","YB" $IecUnits = "B","KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB" if($IEC){$units = $IecUnits}else{$units=$OldUnits} } process{ $Value | %{ if($_ -lt 0){ write-Error "Numbers must be positive." break } if($value -gt 0){ $modifier = [math]::Floor([Math]::Log($_,1KB)) } else{ $modifier = 0 } New-Object PSObject -Property @{ Value = $_ / [math]::Pow(1KB,$modifier) Unit = &{if($modifier -lt $units.Count){$units[$modifier]}else{"1KB E{0}" -f $modifier}} } } } } |
Annotations
Line 24: In the section Big numbers and Accuracy, you will find the reason why the function uses a [double] type for the input value.
Line 29-30: The function can produce the IEC units or the more commonly used, but in fact incorrect, units. See the sectionUnit standards for the complete story.
Line 36-39: The function can only work with positive numbers and zero.
Line 41: The function uses the Log function to find out to which power of 1KB the passed value is. Since the function only needs the integer part of this number to index into the unit names array, the Floor function is applied to the number.
Line 44: The Log function returns ‘Infinite’ when you feed it 0 (zero). This If-Then-Else avoids that nuissance.
Line 47: The converted, more human-readable, number
Line 48: The unit name. If the value is greater than or equal to 1KB to the power 9, the function switches to a 1KB exponential notation. See the Unit standards section below.
Samples
Some simple examples should make it clear how the function can be used.
You can of course format the result to your own liking. This is an example on how to use the Format operator on the result.
1 2 3 4 5 |
Get-Datastore -Name DS1 | Select Name,@{N="Capacity";E={ Get-FriendlyUnit -Value ($_.CapacityMB * 1MB) | %{"{0,7:f2} {1,2}" -f $_.Value,$_.Unit}} } |
In this example we use the CapacityMB property that is present in the object returned from the Get-Datastore cmdlet. The value is passed through the Get-FriendlyUnit function, and the result is presented in a nice format. It looks like this
Discussion points
Unit standards
There is often confusion how the units for binary multiples should be named. Is 1024 bytes a kb, a kB, a KB or KiB ? You can find a good overview, and the correct answer, on this in the Binary Prefix article on Wikipedia.
With ISO/IEC 80000 this was standardised. Those 1024 bytes should be called 1 KiB, . But in reality people are still using KB instead of the official IEC name KiB. To cope with both, the function provides a switch, called IEC, that allows you to select the ISO/IEC 80000 naming standard.
By default, the function returns the customary used symbols.
The following table shows the IEC standard defined names and the customary, but technically incorrect, names.
Value | IEC prefix | IEC name | Customary Symbol | Customary Name |
1024 | Ki | kibi | K | kilo |
10242 | Mi | mebi | M | mega |
10243 | Gi | gibi | G | giga |
10244 | Ti | tebi | T | tera |
10245 | Pi | pebi | P | peta |
10246 | Ei | exbi | E | exa |
10247 | Zi | zebi | Z | zetta |
10248 | Yi | yobi | Y | yotta |
But what happens when you feed the function a number that is bigger than 1024 YB ?
In that case, the function will use a 1KB to the power expression. An example will make this clear.
This expression says, that the value is equal to 1 time 1KB to the power 9.
Big numbers and Accuracy
One question I had to answer was how to get “big” values into the function.
The following table shows what the maximum value are that you can pass with each of the ‘number’ types. I also included the ‘friendly’ names, to give you an idea where the maximum value gets you.
Maximum Value | Friendly Value | |
int | 2147483647 | 1.99 GB |
long | 9223372036854775807 | 8 EB |
float | 3.402823E+38 | 255.99 1KB E12 |
double | 1.79769313486232E+308 | 16 1KB E102 |
From the table is obvious that neither [int] nor [long] would be sufficient to cover the complete range of binary multiples. The solution is fairly simple, we use a [double] for the value that goes into the Get-FriendlyUnit function.
There is one disadvantage though, with big numbers the accuracy of the result will suffer. See the following example.
While the first example still seems to produce a valid result, the second example suffers from rounding errors. The example tells us that 1 EB is equal to (1 EB – 1). So watch out when using bigger numbers!
You can expect some practical use cases of this handy function in the coming days.
Tony
@LucD
Hi Luc
This is a great function. Thanks for all your hard work. How would you add Provisioned space? Is it possible to get provisoned space without using the Get-View Cmdlet. I can’t seem to get your function to work properly when I run it against my datastores using the get-view cmdlet.
Thanks
LucD
Thanks Tony.
I already replied to that question in the VMTN PowerCLI community thread.
I suspect there was a copy/paste that went wrong 🙂
Guilhermestela
hahahhahaa you should re-think better Carter….@LucD
Ethan M
Coolio. Now to integrate this into get-process. Exercise left to reader 🙂
LucD
@Ethan, thanks.
You can call the function in a calculated property. Or do you have something else in mind ?
Kyle Hanson
Quite a useful function. I wish this was an inbuilt PowerShell cmdlet.
Eugene
Great. Thanks as always. Btw you are missing a “<#" at the beginning of the comment section.
LucD
@Eugene, thanks. And thanks for noticing the missing lines.
jessie
Hi,
I really like your conversion to GB, I wonder how I can add free space to the example you gave on:
I’m really new to powercli so Im not too sure. much thanks!!!
Get-Datastore -Name DS1 |
Select Name,@{N=”Capacity”;E={
Get-FriendlyUnit -Value ($_.CapacityMB * 1MB) |
%{“{0,7:f2} {1,2}” -f $_.Value,$_.Unit}}
}
LucD
@Jessie, the Free space property is returned by the Get-Datastore cmdlet as well.
So it’s just a matter of including it in the Select cmdlet.
Something like this
Get-Datastore -Name DS1 |
Select Name,
@{N="Capacity";E={Get-FriendlyUnit -Value ($_.CapacityMB * 1MB) | %{"{0,7:f2} {1,2}" -f $_.Value,$_.Unit}}},
@{N="FreeSpace";E={Get-FriendlyUnit -Value ($_.FreeSpaceMB * 1MB) | %{"{0,7:f2} {1,2}" -f $_.Value,$_.Unit}}}
Carter Shanklin
I will punch the first person I meet who says gibibytes. Not even lying.
LucD
I tend to agree with you, but then …