Feel.Runtime.Arithmetic (feel v0.1.0)

Copy Markdown View Source

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

OperatorOperandsResult
+number, numbernumber (Feel.Runtime.Number)
string, stringthe strings joined
years and months duration, the sameyears and months duration
days and time duration, the samedays 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, numbernumber
the two durations of one kindduration 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 timea days and time duration
*number, numbernumber
days and time duration and number, either waydays and time duration
years and months duration and number, either wayyears and months duration, rounded to a whole month (D-54)
/number, numbernumber; null when the divisor is zero
days and time duration, numberdays and time duration; null for zero
years and months duration, numberyears and months duration, rounded to a whole month; null for zero
days and time duration, the samenumber; null when the divisor is zero
years and months duration, the samenumber; null when the divisor is zero
**number, numbernumber, 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
-xnumber, either durationthe 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

add(a, b)

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(a, b)

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(a, b)

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(value)

@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(atom, left, right)

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(a, b)

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(a, b)

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"