# `Feel.Analysis.Grain`
[🔗](https://github.com/FeeJai/BDM/blob/feel-v0.1.0/apps/feel/lib/feel/analysis/grain.ex#L1)

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.5` has 1, `150` has 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 by `and` for 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 `abs` the same; a quotient has none;
  * a call of `floor`, `ceiling`, `round *` and `decimal` with a scale that is a number has that scale, and `count`,
    `string length`, `day of year` and `week of year` are 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

# `t`

```elixir
@type t() :: integer() | nil
```

A grain: every number is a multiple of `10^-d`, or `nil` for none that is known.

# `multiple?`

```elixir
@spec multiple?(Decimal.t(), integer()) :: boolean()
```

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

# `of`

```elixir
@spec of(Feel.IR.t()) :: t()
```

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

# `of_call`

```elixir
@spec of_call(String.t(), [Feel.IR.t()]) :: t()
```

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.

# `of_number`

```elixir
@spec of_number(Decimal.t()) :: non_neg_integer()
```

The decimals of a number: the grain of a literal.

# `of_tests`

```elixir
@spec of_tests(Feel.IR.t()) :: t()
```

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

# `product`

```elixir
@spec product(t(), t()) :: t()
```

The grain of a product: the two added.

# `snap`

```elixir
@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"

# `sum`

```elixir
@spec sum(t(), t()) :: t()
```

The grain of a sum or a difference: the larger of the two.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
