Feel.Analysis.Intervals (feel v0.1.0)

Copy Markdown View Source

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

decision()

@type decision() :: :always | :never | :unknown

What a comparison of two sets of numbers is, for every pair of numbers.

options()

@type options() :: [whole: boolean(), grains: {integer() | nil, integer() | nil}]

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.

result()

@type result() :: {Feel.Type.NumberSet.t(), boolean()}

The numbers of a result, and whether it can be null because of the operation.

Functions

add(a, b)

The numbers a + b can be.

decide(atom, a, b)

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

divide(a, b, options \\ [])

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

multiply(a, b, options \\ [])

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

negate(a)

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)"

of_one(operator, a, options)

@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}

power(a, b)

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}

subtract(a, b)

The numbers a - b can be.