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