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 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.
Summary
Functions
Adds two values: numbers, strings, or durations of one kind.
Divides the first value by the second.
Multiplies two values: two numbers, or a days and time duration and a number.
Negates a number or a duration.
Applies the arithmetic operator of an IR :arithmetic node to two values.
Raises a number to a power: two numbers, as one correctly rounded power (D-62).
Subtracts the second value from the first: numbers, or durations of one kind.
Functions
@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"
@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
@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"
@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)}
@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
@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"
@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"