PoshBytes: Numeric Precision in PowerShell

PoshBytes: Numeric Precision in PowerShell

In this PoshBytes short you will see how PowerShell can silently lose precision when big integer math is promoted to double and how using the right numeric types and decimal literals helps keep your results exact.

This post is a companion for the video embedded below. Scroll down to see the code from the video.

Tiny numbers, tiny problems

Small integers stay as Int32 and are exact

2 + 2
(2 + 2).GetType().FullName
10 * 10
(10 * 10).GetType().FullName
9 / 3
(9 / 3).GetType().FullName

When big integers trigger type promotion

Max value of a 64-bit signed integer (System.Int64)

[long]::MaxValue
[long]::MaxValue.GetType().FullName

Add a small number – looks harmless…

$max = 9223372036854775807
$max.GetType().FullName
$sum = $max + 2
$sum
$sum.GetType().FullName

Confirm lost of precision

$max,$sum | Foreach-Object{
    "{0:N0}" -f $_
}

Casting AFTER promotion can not fix lost precision

[ulong]$sum

Casting BEFORE promotion can not fix lost precision

[long]$sum = $max + 2

Compare with doing it “right” up front using an unsigned literal

$exact = 9223372036854775807ul + 2
$exact
$exact.GetType().FullName

Decimal: the precision bodyguard

Decimal keeps precision and throws on overflow

[decimal]::MaxValue
[decimal]::MaxValue.GetType().FullName
[decimal]::MaxValue + 1

Money and measurements: double vs decimal

Binary floating point: fast, but not always exact in base 10

0.1 + 0.2
(0.1 + 0.2) - 0.3

(0.1).ToString("G17")

Decimal literals for big math or money math

$priceTotal = 0.10d + 0.20d
$priceTotal
$priceTotal.GetType().FullName

Decimal behaves exactly here

$priceTotal - 0.30d

Wrap Up

• Large integer arithmetic can promote values to double,
• Doubles can cause precision loss when the result exceeds the integer range
• Casting after promotion does not restore the lost integer bits
• Use the correct literal suffixes (ul, l, d) to control the types
• Prefer decimal for money or other values that must be exact
• Always check your result types with .GetType().FullName