What an operation does to a set of numbers (D-39).
Given the sets of numbers that the operands can be (Feel.Type.NumberSet), each function
gives the set of numbers the result can be, and whether the result can be null without an
operand being so: an overflow past decimal128, a division by zero, and a result that is too
small to write. The runtime (Feel.Runtime.Number) computes the ends of the intervals, so the
rounding to 34 digits is the run's own; rounding keeps the order of numbers, so the end of an
interval is the end of what the run can give. The ends of a result are always included, which
can only make a set larger.
Too small to write (D-161). The run gives null for a product or a quotient that is not zero
and whose value needs a last digit below 1E-6176 (D-59): 37.5 / 9.99E+6144, and, for the
smallest numbers there are, 1E-6176 / 2. The ends of an interval do not show that for a number
inside one, so it is counted from the sizes. A result of 1E-6143 or more is written with its 34
digits from the first one, and they reach down to -6176, so only a smaller result can be too
small; and it can be unless an operand is always a whole number (a multiple of 1E-6176 times a
whole number is one again), or the divisor is 1 or -1. So x / 2 is possibly null for every x
in [0..150], 1E-6176 being one of them, and x * 2 is not. The allowed values of x decide:
[0.001..150] rules it out. Where both sets are single numbers the ends are exact, and nothing
is added.
An overflow does not leave the set: a result outside the range of decimal128 is null (the run
also says so for one too small to write, D-59), so the set keeps the numbers inside
the range, up to its ends and down to zero, and the second part of the result says that null
is possible.
iex> alias Feel.Type.NumberSet
iex> a = NumberSet.interval(Decimal.new(0), Decimal.new(150))
iex> {set, null?} = Feel.Analysis.Intervals.add(a, NumberSet.point(Decimal.new(1)))
iex> {NumberSet.format(set), null?}
{"[1..151]", false}
Summary
Types
What a comparison of two sets of numbers is, for every pair of numbers.
whole: true says that both operands are whole numbers, such as months. grains: {a, b} says how fine the numbers of each
operand are (Feel.Analysis.Grain, D-161): a multiple of 10^-a, and of 10^-b; nil for none that is known.
The numbers of a result, and whether it can be null because of the operation.
Functions
The numbers a + b can be.
Whether a op b is true for every pair of numbers of the two sets (:always), for none
(:never), or for some (:unknown). op is one of :eq, :neq, :lt, :lte, :gt,
:gte. Neither set may be empty.
The numbers a / b can be. The result can be null when b has zero in it, when the
quotient can pass the range of decimal128, and when it can be too small to write (D-161).
The numbers a * b can be. The result can be null when it can pass the range of decimal128,
and when it can be too small to write (D-161).
The numbers -a can be. An overflow is not possible, and the ends keep whether they are
included.
The numbers x op x can be for one number x of a that is read twice (D-170): the two operands are one value, so the
pairs of two different numbers of a that add/2 and the rest work out are not there. x - x is 0, x / x is 1 (and
null for the x that is 0), x * x is never below zero, and x + x is 2x, which is not the sum of two numbers of
a set that has a gap in it. The second part says, as for the others, whether the result can be null because of the
operation: an overflow, a result too small to write, a zero divisor. The set may not be empty.
The numbers a ** b can be, and whether the result can be null without an operand being so (D-161).
The numbers a - b can be.
Types
@type decision() :: :always | :never | :unknown
What a comparison of two sets of numbers is, for every pair of numbers.
whole: true says that both operands are whole numbers, such as months. grains: {a, b} says how fine the numbers of each
operand are (Feel.Analysis.Grain, D-161): a multiple of 10^-a, and of 10^-b; nil for none that is known.
@type result() :: {Feel.Type.NumberSet.t(), boolean()}
The numbers of a result, and whether it can be null because of the operation.
Functions
@spec add(Feel.Type.NumberSet.t(), Feel.Type.NumberSet.t()) :: result()
The numbers a + b can be.
@spec decide(atom(), Feel.Type.NumberSet.t(), Feel.Type.NumberSet.t()) :: decision()
Whether a op b is true for every pair of numbers of the two sets (:always), for none
(:never), or for some (:unknown). op is one of :eq, :neq, :lt, :lte, :gt,
:gte. Neither set may be empty.
iex> alias Feel.Type.NumberSet
iex> low = NumberSet.interval(Decimal.new(0), Decimal.new(150))
iex> high = NumberSet.point(Decimal.new(200))
iex> {Feel.Analysis.Intervals.decide(:gt, low, high), Feel.Analysis.Intervals.decide(:lt, low, high)}
{:never, :always}
iex> Feel.Analysis.Intervals.decide(:eq, low, NumberSet.point(Decimal.new(5)))
:unknown
@spec divide(Feel.Type.NumberSet.t(), Feel.Type.NumberSet.t(), options()) :: result()
The numbers a / b can be. The result can be null when b has zero in it, when the
quotient can pass the range of decimal128, and when it can be too small to write (D-161).
iex> alias Feel.Type.NumberSet
iex> a = NumberSet.interval(Decimal.new(10), Decimal.new(20))
iex> b = NumberSet.interval(Decimal.new(2), Decimal.new(5))
iex> {set, null?} = Feel.Analysis.Intervals.divide(a, b)
iex> {NumberSet.format(set), null?}
{"[2..10]", false}
iex> {set, null?} = Feel.Analysis.Intervals.divide(a, NumberSet.point(Decimal.new(0)))
iex> {set, null?}
{[], true}
iex> c = NumberSet.interval(Decimal.new(0), Decimal.new(150))
iex> {_set, null?} = Feel.Analysis.Intervals.divide(c, NumberSet.point(Decimal.new(2)))
iex> null?
true
@spec multiply(Feel.Type.NumberSet.t(), Feel.Type.NumberSet.t(), options()) :: result()
The numbers a * b can be. The result can be null when it can pass the range of decimal128,
and when it can be too small to write (D-161).
iex> alias Feel.Type.NumberSet
iex> a = NumberSet.interval(Decimal.new(0), Decimal.new(150))
iex> {set, null?} = Feel.Analysis.Intervals.multiply(a, NumberSet.point(Decimal.new(2)))
iex> {NumberSet.format(set), null?}
{"[0..300]", false}
iex> {_set, null?} = Feel.Analysis.Intervals.multiply(a, NumberSet.point(Decimal.new("0.5")))
iex> null?
true
@spec negate(Feel.Type.NumberSet.t()) :: Feel.Type.NumberSet.t()
The numbers -a can be. An overflow is not possible, and the ends keep whether they are
included.
iex> alias Feel.Type.NumberSet
iex> Feel.Analysis.Intervals.negate(NumberSet.interval({Decimal.new(0), :open}, Decimal.new(5))) |> NumberSet.format()
"[-5..0)"
@spec of_one( :add | :subtract | :multiply | :divide, Feel.Type.NumberSet.t(), options() ) :: result()
The numbers x op x can be for one number x of a that is read twice (D-170): the two operands are one value, so the
pairs of two different numbers of a that add/2 and the rest work out are not there. x - x is 0, x / x is 1 (and
null for the x that is 0), x * x is never below zero, and x + x is 2x, which is not the sum of two numbers of
a set that has a gap in it. The second part says, as for the others, whether the result can be null because of the
operation: an overflow, a result too small to write, a zero divisor. The set may not be empty.
iex> alias Feel.Type.NumberSet
iex> a = NumberSet.interval(Decimal.new(-5), Decimal.new(3))
iex> {set, null?} = Feel.Analysis.Intervals.of_one(:multiply, a, [])
iex> {NumberSet.format(set), null?}
{"[0..25]", true}
iex> {set, null?} = Feel.Analysis.Intervals.of_one(:subtract, a, [])
iex> {NumberSet.format(set), null?}
{"0", false}
iex> {set, null?} = Feel.Analysis.Intervals.of_one(:divide, a, [])
iex> {NumberSet.format(set), null?}
{"1", true}
@spec power(Feel.Type.NumberSet.t(), Feel.Type.NumberSet.t()) :: result()
The numbers a ** b can be, and whether the result can be null without an operand being so (D-161).
A power is null when the run has no value for it or no room for it: a zero to a negative exponent
(a division by zero), a negative number to an exponent that is not a whole number (no real value), a
result past the range of decimal128, and one that is too small to write (D-59), which is not zero and
needs a last digit below 1E-6176. The last two are counted in both directions, so x ** 2 can be null
for an x in [0..150] (1E-6176 squared is far below the smallest number) and not for one in
[0.001..150].
For a positive base x ** y is exp(y * ln x), which is linear in each of ln x and y, so the least
and the greatest result for a pair of intervals are at their corners, and the run's own Number.power/2
gives them. A base that only gets near zero is at least 1E-6176, the smallest number there is. A negative
base has a value for a whole exponent only, and then it is the size of the positive one with the sign of
the parity of the exponent. A zero base is 0 for a positive exponent and 1 for the exponent 0, and any base
to the exponent 0 is 1. A result that overflows stands for the end of the range when it is too large and
for zero when it is too small, as for a product (D-161). Where the intervals are not single numbers the
corners do not show a number that is too small to write inside them, so the result can be null when its
smallest corner is below the size that is always written.
iex> alias Feel.Type.NumberSet
iex> five_to_ten = NumberSet.interval(Decimal.new(5), Decimal.new(10))
iex> {set, null?} = Feel.Analysis.Intervals.power(NumberSet.point(Decimal.new(0)), five_to_ten)
iex> {NumberSet.format(set), null?}
{"0", false}
iex> {set, null?} = Feel.Analysis.Intervals.power(five_to_ten, NumberSet.point(Decimal.new(2)))
iex> {NumberSet.format(set), null?}
{"[25..100]", false}
iex> {set, null?} = Feel.Analysis.Intervals.power(NumberSet.point(Decimal.new(2)), five_to_ten)
iex> {NumberSet.format(set), null?}
{"[32..1024]", false}
iex> to_150 = NumberSet.interval(Decimal.new(0), Decimal.new(150))
iex> {set, null?} = Feel.Analysis.Intervals.power(to_150, NumberSet.point(Decimal.new(2)))
iex> {NumberSet.format(set), null?}
{"[0..22500]", true}
iex> {set, null?} = Feel.Analysis.Intervals.power(NumberSet.point(Decimal.new(0)), NumberSet.interval(Decimal.new(-1), Decimal.new(1)))
iex> {NumberSet.format(set), null?}
{"0, 1", true}
@spec subtract(Feel.Type.NumberSet.t(), Feel.Type.NumberSet.t()) :: result()
The numbers a - b can be.