# `Feel.Runtime.Arithmetic`
[🔗](https://github.com/FeeJai/BDM/blob/feel-v0.1.0/apps/feel/lib/feel/runtime/arithmetic.ex#L1)

The arithmetic operators of FEEL: `+`, `-`, `*`, `/`, and negation (DMN 1.6,
Tables 57 to 61 and 63).

Each function takes the operand values and returns an `t:Feel.Runtime.Env.outcome/0`.
The result is `null`, with an error, when an operand is `null` (`:null_operand`, D-103: the TCK flags
every `null` operand of an operator of `0100`) and when the kinds of
the operands are not one of the pairs that the tables define:
`"a" - "b"`, `1 + "1"`, and `[1] + [2]` are all `null` with a diagnostic.
Operators take operands as they are and do not apply declaration conversions (D-24);
a one-element list is not unwrapped.

## What is defined

| Operator | Operands | Result |
| -------- | -------- | ------ |
| `+`      | number, number | number (`Feel.Runtime.Number`) |
|          | string, string | the strings joined |
|          | years and months duration, the same | years and months duration |
|          | days and time duration, the same | days and time duration |
|          | date, time, or date and time, and a duration, either way (not a time and a years and months duration) | the date, time, or date and time moved (`Feel.Runtime.Temporal`) |
| `-`      | number, number | number |
|          | the two durations of one kind | duration of that kind |
|          | date, time, or date and time, minus a duration (not a time minus a years and months duration) | the date, time, or date and time moved |
|          | two dates, two times, two dates and times, or a date and a date and time | a days and time duration |
| `*`      | number, number | number |
|          | days and time duration and number, either way | days and time duration |
|          | years and months duration and number, either way | years and months duration, rounded to a whole month (D-54) |
| `/`      | number, number | number; `null` when the divisor is zero |
|          | days and time duration, number | days and time duration; `null` for zero |
|          | years and months duration, number | years and months duration, rounded to a whole month; `null` for zero |
|          | days and time duration, the same | number; `null` when the divisor is zero |
|          | years and months duration, the same | number; `null` when the divisor is zero |
| `**`     | number, number | number, as one correctly rounded power (`Feel.Runtime.Number.power/2`, D-62); `null` for a zero base and a negative exponent, and for a negative base and an exponent that is not a whole number |
| `-x`     | number, either duration | the same kind, as `x * -1` |

Division of a days and time duration by a duration is the quotient of their lengths
in seconds. The table in the specification prints `*` for the division of a duration
by a number (Table 60, row 5); that is a copy of the row above, and division is meant
(notes 05, [E31]).

A number of months that is the product or the quotient of a years and months duration is
rounded to the nearest whole number of months, a half towards positive infinity, as XPath's
`fn:round` does it: `P1Y11M * -2.5` is -57.5 months, and -57 is what the TCK has (D-54).

## Results that are not a number

A result that is a number outside the range of decimal128 is `null` with an
`:overflow` diagnostic, and a division by zero is `null` with `:division_by_zero`. The power of a
negative number to an exponent that is not a whole number has no real value: it is `null` with an
`:argument_out_of_domain` diagnostic.

# `add`

```elixir
@spec add(Feel.Value.t(), Feel.Value.t()) :: Feel.Runtime.Env.outcome()
```

Adds two values: numbers, strings, or durations of one kind.

    iex> Feel.Runtime.Arithmetic.add("ab", "c")
    {:ok, "abc"}
    iex> {:error, diagnostic} = Feel.Runtime.Arithmetic.add(Decimal.new(1), nil)
    iex> diagnostic.code
    :null_operand
    iex> {:error, diagnostic} = Feel.Runtime.Arithmetic.add(Decimal.new(1), "1")
    iex> diagnostic.message
    "cannot apply + to a number and a string"

# `divide`

```elixir
@spec divide(Feel.Value.t(), Feel.Value.t()) :: Feel.Runtime.Env.outcome()
```

Divides the first value by the second.

    iex> {:ok, quotient} = Feel.Runtime.Arithmetic.divide(Decimal.new(3), Decimal.new(2))
    iex> Decimal.to_string(quotient)
    "1.5"
    iex> {:error, diagnostic} = Feel.Runtime.Arithmetic.divide(Decimal.new(1), Decimal.new(0))
    iex> diagnostic.code
    :division_by_zero

# `multiply`

```elixir
@spec multiply(Feel.Value.t(), Feel.Value.t()) :: Feel.Runtime.Env.outcome()
```

Multiplies two values: two numbers, or a days and time duration and a number.

    iex> {:ok, product} = Feel.Runtime.Arithmetic.multiply(Decimal.new("0.10"), Decimal.new("30.00"))
    iex> Decimal.to_string(product)
    "3.0000"

# `negate`

```elixir
@spec negate(Feel.Value.t()) :: Feel.Runtime.Env.outcome()
```

Negates a number or a duration.

    iex> {:ok, negated} = Feel.Runtime.Arithmetic.negate(Decimal.new(3))
    iex> Decimal.to_string(negated)
    "-3"
    iex> Feel.Runtime.Arithmetic.negate(Feel.Value.YearsAndMonthsDuration.new(14))
    {:ok, Feel.Value.YearsAndMonthsDuration.new(-14)}

# `operate`

```elixir
@spec operate(Feel.IR.arithmetic_operator(), Feel.Value.t(), Feel.Value.t()) ::
  Feel.Runtime.Env.outcome()
```

Applies the arithmetic operator of an IR `:arithmetic` node to two values.

    iex> Feel.Runtime.Arithmetic.operate(:add, "a", "b")
    {:ok, "ab"}
    iex> {:error, diagnostic} = Feel.Runtime.Arithmetic.operate(:divide, Decimal.new(1), Decimal.new(0))
    iex> diagnostic.code
    :division_by_zero

# `power`

```elixir
@spec power(Feel.Value.t(), Feel.Value.t()) :: Feel.Runtime.Env.outcome()
```

Raises a number to a power: two numbers, as one correctly rounded power (D-62).

    iex> {:ok, power} = Feel.Runtime.Arithmetic.power(Decimal.new(5), Decimal.new(2))
    iex> Decimal.to_string(power)
    "25"
    iex> {:error, diagnostic} = Feel.Runtime.Arithmetic.power(Decimal.new(5), nil)
    iex> diagnostic.code
    :null_operand
    iex> {:error, diagnostic} = Feel.Runtime.Arithmetic.power(Decimal.new(0), Decimal.new(-1))
    iex> diagnostic.code
    :division_by_zero
    iex> {:error, diagnostic} = Feel.Runtime.Arithmetic.power("5", Decimal.new(2))
    iex> diagnostic.message
    "cannot apply ** to a string and a number"

# `subtract`

```elixir
@spec subtract(Feel.Value.t(), Feel.Value.t()) :: Feel.Runtime.Env.outcome()
```

Subtracts the second value from the first: numbers, or durations of one kind.

A date and a date and time: the date is a date and time at UTC midnight (Table 58), and the outcome says it, as
`{:ok, difference, [info]}` (`Feel.Runtime.Env.settle/3`, D-56).

    iex> {:ok, difference} = Feel.Runtime.Arithmetic.subtract(Decimal.new(5), Decimal.new(2))
    iex> Decimal.to_string(difference)
    "3"
    iex> date_time = %Feel.Value.DateTime{date: ~D[2020-01-01], time: %Feel.Value.Time{hour: 1, minute: 0, second: Decimal.new(0), zone: {:offset, 0}}}
    iex> {:ok, _difference, [info]} = Feel.Runtime.Arithmetic.subtract(date_time, ~D[2020-01-01])
    iex> info.message
    "the right operand of - is converted to a date and time: a date becomes a date and time at midnight UTC"

---

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