How fine the numbers of a node are (D-161, step D).
A grain d of a node says that every number the node can have, null apart, is a multiple of 10^-d: d = 2 is a
number of at most two decimals (a price), d = 0 a whole number, and d = -3 a multiple of a thousand. The smaller the
grain, the more it says. A node with no grain (nil) says nothing. It is a fact about the node and not a part of its type,
as the length of a list is (D-193): the sets of numbers are intervals, and a set of the multiples of a step is not one.
What it is for: a product or a quotient of numbers is null when its value is not zero and needs a last digit below
1E-6176 (D-59), and the sets say that this can happen for any number near zero (D-161). A number of a grain is not near
zero but at a distance of 10^-d at least, so price * quantity for prices of two decimals and whole quantities cannot be
too small to write. The grain comes from what a model says of a number:
- a literal has its decimals (
0.5has 1,150has 0); - a name has the grain of its allowed values, when they say it:
round half up(?, 2) = ?,floor(?) = ?,modulo(?, 5) = 0,even(?)(of_tests/1), and so do plain DMN constraints, joined byandfor the least of them; - an operation has the grain of its operands: a sum or a difference the larger, a product their sum, a negation or
an
absthe same; a quotient has none; - a call of
floor,ceiling,round *anddecimalwith a scale that is a number has that scale, andcount,string length,day of yearandweek of yearare whole numbers.
A grain is as true as the constraint it comes from, which the run checks before the expression runs (D-138), and
Feel.Paranoid holds every node that has one to it.
iex> Feel.Analysis.Grain.of_number(Decimal.new("0.25"))
2
iex> Feel.Analysis.Grain.sum(2, 0)
2
iex> Feel.Analysis.Grain.product(2, 3)
5
iex> Feel.Analysis.Grain.multiple?(Decimal.new("1.25"), 2)
true
iex> Feel.Analysis.Grain.multiple?(Decimal.new("1.255"), 2)
false
Summary
Functions
Whether a number is a multiple of 10^-d.
The grain of an IR node: a literal has its decimals, and any other node has the one it was given when it was made
(:grain in its metadata).
The grain of the number that a call gives, if it is known: the scale of round half up(x, 2), 0 for floor(x) and
count(xs), that of the argument for abs. arguments are the IR nodes of the call.
The decimals of a number: the grain of a literal.
The grain of the numbers that unary tests accept (the allowed values of a name), if they say it. The tests are
alternatives, so every one has to say it, and the grain is the coarsest of them; a test joined by and has the finest
grain of any part that says one. round half up(?, 2) = ? is 2, floor(?) = ? and even(?) 0, and modulo(?, 0.25) = 0
the decimals of 0.25, which is 2.
The grain of a product: the two added.
The numbers of a set that a grain allows: each end that is not a multiple of 10^-d is brought in to the nearest one that is
inside the interval, and an interval that has none is left out. [0..1E-6176] of whole numbers is 0, and (0..1] of two
decimals is [0.01..1]. The ends of the set are the numbers an operation is worked out at (Feel.Analysis.Intervals), so an end
that the node cannot have must not be one.
The grain of a sum or a difference: the larger of the two.
Types
@type t() :: integer() | nil
A grain: every number is a multiple of 10^-d, or nil for none that is known.
Functions
Whether a number is a multiple of 10^-d.
iex> Feel.Analysis.Grain.multiple?(Decimal.new(300), -2)
true
iex> Feel.Analysis.Grain.multiple?(Decimal.new(350), -2)
false
The grain of an IR node: a literal has its decimals, and any other node has the one it was given when it was made
(:grain in its metadata).
The grain of the number that a call gives, if it is known: the scale of round half up(x, 2), 0 for floor(x) and
count(xs), that of the argument for abs. arguments are the IR nodes of the call.
@spec of_number(Decimal.t()) :: non_neg_integer()
The decimals of a number: the grain of a literal.
The grain of the numbers that unary tests accept (the allowed values of a name), if they say it. The tests are
alternatives, so every one has to say it, and the grain is the coarsest of them; a test joined by and has the finest
grain of any part that says one. round half up(?, 2) = ? is 2, floor(?) = ? and even(?) 0, and modulo(?, 0.25) = 0
the decimals of 0.25, which is 2.
iex> {:ok, ast} = Feel.parse("? >= 0 and ? <= 150 and round half up(?, 2) = ?", entry: :unary_tests)
iex> {:ok, %{ir: tests}} = Feel.Analysis.Analyzer.analyze(ast, input: :number, declarations: :ignore)
iex> Feel.Analysis.Grain.of_tests(tests)
2
iex> {:ok, ast} = Feel.parse("[0..150], round half up(?, 2) = ?", entry: :unary_tests)
iex> {:ok, %{ir: tests}} = Feel.Analysis.Analyzer.analyze(ast, input: :number, declarations: :ignore)
iex> Feel.Analysis.Grain.of_tests(tests)
nil
The grain of a product: the two added.
@spec snap(Feel.Type.NumberSet.t(), integer()) :: Feel.Type.NumberSet.t()
The numbers of a set that a grain allows: each end that is not a multiple of 10^-d is brought in to the nearest one that is
inside the interval, and an interval that has none is left out. [0..1E-6176] of whole numbers is 0, and (0..1] of two
decimals is [0.01..1]. The ends of the set are the numbers an operation is worked out at (Feel.Analysis.Intervals), so an end
that the node cannot have must not be one.
iex> alias Feel.Type.NumberSet
iex> set = NumberSet.interval({Decimal.new(0), :open}, Decimal.new(1))
iex> set |> Feel.Analysis.Grain.snap(2) |> NumberSet.format()
"[0.01..1]"
iex> set |> Feel.Analysis.Grain.snap(0) |> NumberSet.format()
"1"
iex> NumberSet.interval(Decimal.new(0), Decimal.new("0.4")) |> Feel.Analysis.Grain.snap(0) |> NumberSet.format()
"0"
The grain of a sum or a difference: the larger of the two.