Module:RiskRange
Appearance
This module converts two probability values into a human-friendly range string with a common denominator, making it easy to compare the low and high estimates.
Usage
From wikitext (via Template:RiskRange):
{{RiskRange|probability1|probability2}}
Direct module invocation:
{{#invoke:RiskRange|convert|probability1|probability2}}
Parameters
| Parameter | Description |
|---|---|
1 |
First probability (decimal between 0 and 1) |
2 |
Second probability (decimal between 0 and 1) |
The module automatically orders the probabilities so the lower risk appears first and higher risk appears second.
Output Format
The module outputs ranges in the format "N to M in D" where:
- N = lower numerator (lower risk)
- M = higher numerator (higher risk)
- D = common denominator
Denominator Selection
- If either probability is ≥ 0.1, the denominator is 10 (e.g., "2 to 6 in 10")
- Otherwise, the denominator is chosen based on the smaller probability, rounded to a "nice" value (10, 20, 50, 100, 200, 500, 1,000, etc.)
Special Cases
| Condition | Output |
|---|---|
| Both probabilities ≤ 0 | "no chance" |
| Both probabilities ≥ 1 | "100% chance" |
| Both probabilities ≥ 0.95 | "almost certain" |
| One is 0, other is 1 | "anywhere from no chance to certain" |
| Probabilities within 5% of each other | Single value, e.g., "about 5 in 10" |
| Lower probability ≈ 0 | "up to about N in D" |
| Higher probability ≥ 1 | "at least N in D" |
Examples
| Input | Output |
|---|---|
{{RiskRange|0.2|0.6}} |
2 to 6 in 10 |
{{RiskRange|0.001|0.01}} |
1 to 10 in 1,000 |
{{RiskRange|0.21|0.6}} |
2 to 6 in 10 |
{{RiskRange|0.33|0.82}} |
3 to 8 in 10 |
{{RiskRange|0.01|0.05}} |
1 to 5 in 100 |
{{RiskRange|0.0001|0.001}} |
1 to 10 in 10,000 |
{{RiskRange|0.5|0.5}} |
about 5 in 10 |
{{RiskRange|0|0.5}} |
up to about 5 in 10 |
Algorithm
- Validate inputs and handle error cases
- Order probabilities (lower first)
- Check for special cases (zero, one, equal, almost certain)
- Select common denominator:
- Use 10 if higher probability ≥ 0.1
- Otherwise use
nice_denominator(1/lower_probability)
- Calculate numerators:
N = probability × denominator - Round numerators to integers
- If both round to same value, try larger denominator
- Format output string
See Also
- Template:RiskRange – Template wrapper for this module
- Template:One_In_X – For single probability values
- Module:ProbabilityFormat – Single probability formatting
- Module:RiskRange/testcases – Unit tests (30 test cases)
- Test/RiskRange – Test runner page
local p = {}
-- Format an integer with commas as thousands separators
local function format_with_commas(n)
local s = tostring(math.floor(n))
local sign, int = s:match("^([%-]?)(%d+)$")
if not int then return s end
int = int:reverse():gsub("(%d%d%d)", "%1,")
int = int:reverse():gsub("^,", "")
return sign .. int
end
-- Round to a "nice" denominator (power of 10, or 2/5 times a power of 10)
local function nice_denominator(recip)
if recip <= 1 then return 10 end
local exponent = math.floor(math.log10(recip))
local base = 10 ^ exponent
-- Try nice values: 1, 2, 5, 10 times the base
local nice_values = { base, base * 2, base * 5, base * 10 }
for _, val in ipairs(nice_values) do
if val >= recip then
return val
end
end
return base * 10
end
-- Convert a probability range to a human-friendly string with common denominator
-- @param frame.args[1] First probability (lower or higher)
-- @param frame.args[2] Second probability (lower or higher)
-- @return A string like "1 to 10 in 1,000" or "2 to 6 in 10"
function p.convert(frame)
local prob1_str = frame.args[1]
local prob2_str = frame.args[2]
-- Validate inputs
if prob1_str == nil or prob1_str == '' then
return "Error: First probability not provided"
end
if prob2_str == nil or prob2_str == '' then
return "Error: Second probability not provided"
end
local prob1 = tonumber(prob1_str)
local prob2 = tonumber(prob2_str)
if not prob1 then
return "Error: Invalid first probability"
end
if not prob2 then
return "Error: Invalid second probability"
end
-- Ensure prob_low <= prob_high (lower risk first, higher risk second)
local prob_low, prob_high
if prob1 <= prob2 then
prob_low, prob_high = prob1, prob2
else
prob_low, prob_high = prob2, prob1
end
-- Handle special cases
if prob_low <= 0 and prob_high <= 0 then
return "no chance"
end
if prob_low >= 1 and prob_high >= 1 then
return "100% chance"
end
if prob_low >= 0.95 and prob_high >= 0.95 then
return "almost certain"
end
if prob_low <= 0 and prob_high >= 1 then
return "anywhere from no chance to certain"
end
-- Handle case where probabilities are effectively equal
local ratio = prob_high / math.max(prob_low, 1e-10)
if ratio < 1.05 then
-- They're essentially the same, use single value format
-- For probabilities >= 0.1, use "N in 10" for consistency
if prob_low >= 0.1 then
local n = math.floor(prob_low * 10 + 0.5)
return "about " .. n .. " in 10"
else
local recip = 1 / prob_low
local denom = nice_denominator(recip)
local numer = math.floor(prob_low * denom + 0.5)
if numer < 1 then numer = 1 end
return "about " .. numer .. " in " .. format_with_commas(denom)
end
end
-- Handle case where low probability is zero or nearly zero
if prob_low <= 1e-9 then
-- Just describe the high end
-- For probabilities >= 0.1, use "N in 10" for consistency
if prob_high >= 0.1 then
local n_high = math.floor(prob_high * 10 + 0.5)
return "up to about " .. n_high .. " in 10"
else
local recip_high = 1 / prob_high
local denom = nice_denominator(recip_high)
local n_high = math.floor(prob_high * denom + 0.5)
if n_high < 1 then n_high = 1 end
return "up to about " .. n_high .. " in " .. format_with_commas(denom)
end
end
-- Handle case where high probability is >= 1
if prob_high >= 1 then
-- For probabilities >= 0.1, use "N in 10" for consistency
if prob_low >= 0.1 then
local n_low = math.floor(prob_low * 10 + 0.5)
return "at least " .. n_low .. " in 10"
else
local recip_low = 1 / prob_low
local denom = nice_denominator(recip_low)
local n_low = math.floor(prob_low * denom + 0.5)
if n_low < 1 then n_low = 1 end
return "at least " .. n_low .. " in " .. format_with_commas(denom)
end
end
-- Standard case: both probabilities are in (0, 1)
-- Prefer denominator of 10 when the higher probability is >= 0.1
local denom
if prob_high >= 0.1 then
denom = 10
else
-- Pick denominator based on the smaller probability (larger reciprocal)
local recip_low = 1 / prob_low
denom = nice_denominator(recip_low)
end
-- Calculate numerators
local n_low = prob_low * denom
local n_high = prob_high * denom
-- Round to integers
n_low = math.floor(n_low + 0.5)
n_high = math.floor(n_high + 0.5)
-- Ensure minimum of 1 for low end
if n_low < 1 then n_low = 1 end
-- If they rounded to the same value, adjust
if n_low == n_high then
-- Try to differentiate by using a larger denominator
denom = denom * 10
n_low = math.floor(prob_low * denom + 0.5)
n_high = math.floor(prob_high * denom + 0.5)
if n_low < 1 then n_low = 1 end
-- If still equal, just return single value
if n_low == n_high then
return "about " .. n_low .. " in " .. format_with_commas(denom)
end
end
-- Format output
return n_low .. " to " .. n_high .. " in " .. format_with_commas(denom)
end
return p