Consider the following PowerShell code:
> $null -gt 0
False
> $null -ge 0
False
> $null -eq 0
False
> $null -le 0
True
> $null -lt 0
True
Of course the same is true for a $variable explicitly set to $null
or for a non-existent variable.
$null
? Or is one of those methods considered pretty standard?Thanks for your time. Apologies in advance if this is too "squishy" of a question for this venue.
P.S. For what it's worth my usual environment is PowerShell v5.1.
Why is that?
The behavior is counterintuitive:
Operators -lt
, -le
, -gt
, -ge
, even though they can also have numeric meaning, seemingly treat a $null
operand as if it were the empty string (''
), i.e. they default to string comparison, as the sample commands in postanote's helpful answer imply.
That is, $null -lt 0
is in effect evaluated the same as '' -lt '0'
, which explains the $true
result, because in lexical comparison the condition is met.
While you can conceive of $null -eq 0
as '' -eq '0'
too, the -eq
case is special - see below.
Additionally, placing the 0
on the LHS still acts like a string comparison (except with -eq
see below) - even though it is normally the type of the LHS that causes the RHS to be coerced to the same type.
That is, 0 -le $null
too seems to act like '0' -le ''
and therefore returns $false
.
While such behavior is to be expected in operators that are exclusively string-based, such as -match
and -like
, it is surprising for operators that also support numbers, especially given that other such operators - as well as those that are exclusively numeric - default to numeric interpretation of $null
, as 0
.
+
, -
, and /
do force a LHS $null
to 0
([int]
by default); e.g. $null + 0
is 0
*
does not; e.g., $null * 0
is again $null
.Of these, -
and /
are exclusively numeric, whereas +
and *
also work in string and array contexts.
There is an additional inconsistency: -eq
never performs type coercion on a $null
operand:
$null -eq <RHS>
is only ever $true
if <RHS>
is also $null
(or "automation null" - see below), and is currently the only way to reliably test a value for being $null
. (To put it differently: $null -eq ''
is not the same as '' -eq ''
- no type coercion takes place here.)
$null
tests, such as <LHS> -is $null
.Similarly, <LHS> -eq $null
also performs no type coercion on $null
and returns $true
only with $null
as the LHS;
<LHS>
, -eq
acts as filter (as most operators do), returning the subarray of elements that are $null
; e.g., 1, $null, 2, $null, 3 -eq $null
returs 2-element array $null, $null
.$null -eq <RHS>
- with $null
as the scalar LHS - is reliable as a test for (scalar) $null
.Note that the behaviors equally apply to the "automation null" value that PowerShell uses to express the (non-)output from commands (technically, the [System.Management.Automation.Internal.AutomationNull]::Value
singleton), because this value is treated the same as $null
in expressions; e.g. $(& {}) -lt 0
is also $true
- see this answer for more information.
Similarly, the behaviors also apply to instances of nullable value types that happen to contain $null
(e.g., [System.Nullable[int]] $x = $null; $x -lt 0
is also $true
)Thanks, Dávid Laczkó., though note that their use in PowerShell is rare.
Can and should this result be relied on?
Since the behavior is inconsistent across operators, I wouldn't rely on it, not least because it's also hard to remember which rules apply when - and there's at least a hypothetical chance that the inconsistency will be fixed; given that this would amount to a breaking change, however, that may not happen.
If backward compatibility weren't a concern, the following behavior would remove the inconsistencies and make for rules that are easy to conceptualize and remember:
When a (fundamentally scalar) binary operator is given a $null
operand as well as a non-$null
operand - irrespective of which is the LHS and which is the RHS:
For operators that operate exclusively on numeric / Boolean / string operands (e.g. /
/ -and
/ -match
): coerce the $null
operand to the type implied by the operator.
For operators that operate in multiple "domains" - both textual and numeric (e.g. -eq
) - coerce the $null
operand to the other operand's type.
Note that this would then additionally require a dedicated $null
test with different syntax, such as the -is $null
from the above-mentioned PR.
Note: The above does not apply to the collection operators, -in
and -contains
(and their negated variants -notin
and -notcontains
), because their element-wise equality comparison acts like -eq
and therefore never applies type coercion to $null
values.
what is the best (i.e. most concise, best performing, etc.) way to reliably test for integer values (or other types for that matter) in a variable that might have a value of $null?
The following solutions force a $null
operand to 0
:
(...)
around the LHS of the -lt
operations below is used for conceptual clarity, but isn't strictly necessary - see about_Operator_Precedence.In PowerShell (Core) 7+, use ??
, the null-coalescing operator, which works with operands of any type:
# PowerShell 7+ only
($null ?? 0) -lt 0 # -> $false
In Windows PowerShell, where this operator isn't supported, use a dummy calculation:
# Windows PowerShell
(0 + $null) -lt 0 # -> $false
While something like [int] $null -lt 0
works too, it requires you to commit to a specific numeric type, so if the operand happens to be higher than [int]::MaxValue
, the expression will fail; [double] $null -lt 0
would minimize that risk, though could at least hypothetically result in loss of accuracy.
The dummy addition (0 +
) bypasses this problem and lets PowerShell apply its usual on-demand type-widening.
As an aside: This automatic type-widening can exhibit unexpected behavior too, because an all-integer calculation whose result requires a wider type than either operand's type can fit is always widened to [double]
, even when a larger integer type would suffice; e.g. ([int]::MaxValue + 1).GetType().Name
returns Double
, even though a [long]
result would have sufficed, resulting in potential loss of accuracy - see this answer for more information.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With