PowerShell Welcome to this addition of the PowerShell ABC's where you'll find 26 posts detailing a component of the PowerShell scripting language, one letter at a time.  For today's letter of "W", I'll talk about PowerShell's type-promiscuous feature of numeric widening.

WideLoad PowerShell is a type-promiscuous language in that PowerShell will do it's best to try to convert whatever type of literal you are using into the type you need with as little work on your part as possible.  This means that if you want to multiple a number by a string representation of a number, it will behave as you would expect by auto-converting the string into it's numeric representation before the multiplication occurs.

When you ware working with numeric calculations, PowerShell converts everything as needed as long as there is no loss in precision.  In that case, specific guidance from the user is needed in the form of a cast or specific type conversion.

Widening

Part of this dynamic type feature, is the concept of widening.  Widening refers to the act of converting a value to a representation that can handle larger, or wider, numbers.  As an example, a [long] (64 bit) is wider than a [int] (32 bit). If the result of the operation cannot be exactly represented using the base types of the operation, then the next widest type that can hold the result will be used, within the limitations of the .NET numeric types.

This is likely best illustrated with some examples.

PS C:\> (1 + "2").GetType().FullName
System.Int32
PS C:\> (2/1).GetType().FullName
System.Int32
PS C:\> (1 + "2.0").GetType().FullName
System.Double
PS C:\> (3 / 2).GetType().FullName
System.Double

In the first two examples, type resulting types can be converted to [int]'s without loss of data, but in example 3 "2.0" would lose precision by converting to a Int32 so it is treated as a [double] and thus the resulting addition will be treated as a [double] as well.  In the last example the resulting value of "1.5" again would lose precision by being treated as an [int] so it is treated as a [double] as well.  PowerShell does not use the [single] type (single-precision floating point) unless you specifically request it.  There generally isn't a performance improvement by using a [single] over a [double] so it was decided that more precision was better.

Overflow

When you explicitly try to declare a type of a particular value and the resulting value is too wide (ie. too large) for the resulting value, a runtime error will occur.  This can be shown by trying to add a exponential number to a explicitly declared decimal.

PS C:\> 1e100 + 1d
Cannot convert value "1E+100" to type "System.Decimal". Error: "Value was either too large or too small for a Decimal."
At line:1 char:8
+ 1e100 + <<<<  1d
    + CategoryInfo          : NotSpecified: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : RuntimeException

In this example, the declaration to treat the right hand value as a decimal would require the left be converted to a decimal as well.  Since 1e100 is too large to be represented as a decimal, the runtime exception is raised indicating as much.

The Unexpected

When string conversions occur, you'll need to be careful that the auto-conversions happen that they are as you would expect.

PS C:\> [int]"123" -lt 123.4
True
PS C:\> [int]"123" -lt "123.4"
False
PS C:\> [double]"123" -lt "123.4"
True

The first example is pretty straightforward in that the string "123" is cast into an integer.  Since the right hand number is a decimal, the left value will get widened to a decimal and the comparison of 123.0 -lt 123.4 will occur and that will return True. 

For the second example, the left and right hand values are both strings.  The left hand value is converted to an [int] and since the right hand value is a string and does not have a numeric type, it is converted to the type of the left hand value and thus will be truncated to 123.  The resulting operation will be 123 -lt 123 will then return False.

For the last example, by forcing the type of the left hand value to a double, the resulting operation will be 123.0 -lt 123.4 which again will return True.

Debugging Type Conversions

This can be confusion but in most cases you won't have any issues.  But when type conversions are not going your way, you can use the Trace-Command Cmdlet to list out all the operations.  If you specify the TypeConversions value for the -Name parameter, you'll get a detailed step-by-step on how conversions occur.

PS D:\> Trace-Command -Name TypeConversion -pshost -Expression {[int]"123" -lt 123.4}
DEBUG: TypeConversion Information: 0 : Converting "System.Object[]" to "System.Object[]".
DEBUG: TypeConversion Information: 0 :     Result type is assignable from value to convert's type
DEBUG: TypeConversion Information: 0 : Converting "int" to "System.Type".
DEBUG: TypeConversion Information: 0 :     Original type before getting BaseObject: "System.String".
DEBUG: TypeConversion Information: 0 :     Original type after getting BaseObject: "System.String".
DEBUG: TypeConversion Information: 0 :     Conversion to System.Type
DEBUG: TypeConversion Information: 0 :     Cached conversion succeeded
DEBUG: TypeConversion Information: 0 : Converting "123" to "System.Int32".
DEBUG: TypeConversion Information: 0 :     Original type before getting BaseObject: "System.String".
DEBUG: TypeConversion Information: 0 :     Original type after getting BaseObject: "System.String".
DEBUG: TypeConversion Information: 0 :     Converting to integer.
DEBUG: TypeConversion Information: 0 :     Cached conversion succeeded
True
PS D:\> Trace-Command -Name TypeConversion -pshost -Expression {[int]"123" -lt "123.4"}
DEBUG: TypeConversion Information: 0 : Converting "System.Object[]" to "System.Object[]".
DEBUG: TypeConversion Information: 0 :     Result type is assignable from value to convert's type
DEBUG: TypeConversion Information: 0 : Converting "int" to "System.Type".
DEBUG: TypeConversion Information: 0 :     Original type before getting BaseObject: "System.String".
DEBUG: TypeConversion Information: 0 :     Original type after getting BaseObject: "System.String".
DEBUG: TypeConversion Information: 0 :     Conversion to System.Type
DEBUG: TypeConversion Information: 0 :     Cached conversion succeeded
DEBUG: TypeConversion Information: 0 : Converting "123" to "System.Int32".
DEBUG: TypeConversion Information: 0 :     Original type before getting BaseObject: "System.String".
DEBUG: TypeConversion Information: 0 :     Original type after getting BaseObject: "System.String".
DEBUG: TypeConversion Information: 0 :     Converting to integer.
DEBUG: TypeConversion Information: 0 :     Cached conversion succeeded
DEBUG: TypeConversion Information: 0 : Converting "123.4" to "System.Int32".
DEBUG: TypeConversion Information: 0 :     Original type before getting BaseObject: "System.String".
DEBUG: TypeConversion Information: 0 :     Original type after getting BaseObject: "System.String".
DEBUG: TypeConversion Information: 0 :     Converting to integer.
DEBUG: TypeConversion Information: 0 :     Exception converting to integer: "Input string was not in a correct
format.".
DEBUG: TypeConversion Information: 0 :     Converting to integer passing through double.
DEBUG: TypeConversion Information: 0 :     Numeric Conversion through Syustem.Double.
DEBUG: TypeConversion Information: 0 :     Cached conversion succeeded
False
PS D:\> Trace-Command -Name TypeConversion -pshost -Expression {[double]"123" -lt "123.4"}
DEBUG: TypeConversion Information: 0 : Converting "System.Object[]" to "System.Object[]".
DEBUG: TypeConversion Information: 0 :     Result type is assignable from value to convert's type
DEBUG: TypeConversion Information: 0 : Converting "double" to "System.Type".
DEBUG: TypeConversion Information: 0 :     Original type before getting BaseObject: "System.String".
DEBUG: TypeConversion Information: 0 :     Original type after getting BaseObject: "System.String".
DEBUG: TypeConversion Information: 0 :     Conversion to System.Type
DEBUG: TypeConversion Information: 0 :     Cached conversion succeeded
DEBUG: TypeConversion Information: 0 : Converting "123" to "System.Double".
DEBUG: TypeConversion Information: 0 :     Original type before getting BaseObject: "System.String".
DEBUG: TypeConversion Information: 0 :     Original type after getting BaseObject: "System.String".
DEBUG: TypeConversion Information: 0 :     Converting to double or single.
DEBUG: TypeConversion Information: 0 :     Cached conversion succeeded
DEBUG: TypeConversion Information: 0 : Converting "123.4" to "System.Double".
DEBUG: TypeConversion Information: 0 :     Original type before getting BaseObject: "System.String".
DEBUG: TypeConversion Information: 0 :     Original type after getting BaseObject: "System.String".
DEBUG: TypeConversion Information: 0 :     Converting to double or single.
DEBUG: TypeConversion Information: 0 :     Cached conversion succeeded
True