TimZed
TimZed

Reputation: 29

Powershell: Limit input to a range of numeric values (int or decimal)

I have seen this here for different scripting languages, however I have my problem in Powershell and as follows: I want to limit the input of a numeric value, which can either be a decimal or an integer one, to a specific range.

I tried the following:

Do { $value = Read-host "Specify a value between 23.976 and 60"}
while ((23.976..60) -notcontains $value )

However it seems that like this it doesn't accept any decimal values at all but only integers. For instance, when I try to enter 29.97 or 29.970 it stays in the loop.

How can I do this right?

Upvotes: 2

Views: 5167

Answers (3)

mklement0
mklement0

Reputation: 437953

PowerShell's range operator - .. - generates an array of discrete values[1] from the range endpoints, which can have the following types (in a given expression, both endpoints must have the same type):

  • [int] (System.Int32)

    • e.g., 1..3 creates array 1, 2, 3
    • Note: While the minimum and maximum endpoint values allowed are [int]::MinValue and [int]::MaxValue, respectively, the resulting array must additionally not have more than 2,146,435,071 elements (2+ billion), which is the max. element count for a single-dimensional [int] array in .NET - see this answer for background information.
  • Alternatively, only in PowerShell [Core, 6+]: characters, which are enumerated between the endpoints by their underlying code points ("ASCII values").

    • e.g., 'a'..'c' creates array [char] 'a', [char] 'b', [char] 'c'

Instances of numeric types other than [int] are quietly coerced to the latter - if they can fit into the [int] range - in which case half-to-even midpoint rounding is performed for non-integral types; e.g., implicit [double] instances 23.5 and 24.5 are both coerced to 24.

Because [int] 23.976 is 24, your 23.976..60 expression creates array 24, 25, 26, ..., 60 which is not your intent.

In short: You cannot use .. to describe an uncountable range of non-integers (fractional numbers).

Instead, use -ge (greater than or equal) and -le (less than or equal) to test against the endpoints:

-not ($value -ge 23.976 -and $value -le 60)

Additionally, in order to make the -ge and -le operations work as intended, convert the return value from Read-Host, which is always a string, to a number. If you were to use $value as directly returned by Read-Host, you'd get lexical (text sort order-based) comparison, not numeric comparison.

Therefore, cast the Read-Host return value to [double]:

$value = try { [double] (Read-Host 'Specify a value between 23.976 and 60') }
         catch {}

Note: The try / catch handles the case when the user enters text that cannot be interpreted as a [double] representation.


To put it all together:

do {
  $value = try { [double] (Read-Host 'Specify a value between 23.976 and 60') }
           catch {}
} while (-not ($value -ge 23.976 -and $value -le 60))

[1] Strictly speaking, .. creates a lazy enumerable that only becomes an [object[]] array when enumeration is performed and the results are collected, such as when a .. operation participates in a larger expression or it is captured in a variable.

Upvotes: 2

Alain Q
Alain Q

Reputation: 11

Function Get-NumberInRange {
  try {
    [ValidateRange(23.976,60)]$NumberInRange = Read-host "Specify a value between 23.976 and 60"
    }
  Catch {
    Get-NumberInRange
    }
  return $NumberInRange
  }

$Value = Get-NumberInRange

Upvotes: 0

TimZed
TimZed

Reputation: 29

OK guys, this is how it works ;)

Do { $value = Read-host "Specify a value between 23.976 and 60"}
while (( $value -gt 60 ) -or ( $value -lt 23.976 )) 

Upvotes: -1

Related Questions