///|
/// A number, by its kind: arithmetic widens integer to decimal to float.
priv enum Num {
Whole(@bigint.BigInt)
Exact(@ion_core.Decimal)
Approx(Double)
}
///|
fn number(v : @value.Value) -> Num? {
match v {
Int(n) => Some(Whole(@bigint.BigInt::from_int64(n)))
BigInt(n) => Some(Whole(n))
Decimal(d) => Some(Exact(d))
Float(f) => Some(Approx(f))
_ => None
}
}
///|
fn whole_to_double(n : @bigint.BigInt) -> Double raise OpFailure {
try @string.parse_double(n.to_string()) catch {
_ => raise DataFailure("an integer out of the float range")
} noraise {
d => d
}
}
///|
fn to_double(n : Num) -> Double raise OpFailure {
match n {
Whole(w) => whole_to_double(w)
Exact(d) =>
match d.to_double() {
Some(x) => x
None => raise DataFailure("a decimal out of the float range")
}
Approx(f) => f
}
}
///|
/// A float becomes the decimal of its shortest text. NaN and the infinities
/// have none.
fn to_decimal(n : Num) -> @ion_core.Decimal raise OpFailure {
match n {
Whole(w) => @ion_core.Decimal::Decimal(w, 0)
Exact(d) => d
Approx(f) =>
if f.is_nan() || f.is_inf() {
raise DataFailure("NaN or infinity in decimal arithmetic")
} else {
match parse_decimal(f.to_string()) {
Some(d) => d
None => raise DataFailure("a float without a decimal")
}
}
}
}
///|
/// An integer result: `Int` when it fits in Int64. A larger result is an
/// error (partiql-tests extended int.ion, `int_overflow_error`).
fn whole_value(n : @bigint.BigInt) -> @value.Value raise OpFailure {
if n.compare_int64(9223372036854775807L) <= 0 &&
n.compare_int64(-9223372036854775808L) >= 0 {
@value.Value::from_int(n.to_int64())
} else {
raise DataFailure("integer overflow")
}
}
///|
/// The absent-value rules, then the number kinds. `whole`, `exact` and
/// `approx` compute on two integers, two decimals and two floats. An integer
/// widens to a float, and a float or an integer widens to a decimal when the
/// other operand is a decimal (partiql-tests nary-operators.ion,
/// `subIntFloatDecimal`).
fn arithmetic(
a : @value.Value,
b : @value.Value,
whole~ : (@bigint.BigInt, @bigint.BigInt) -> @value.Value raise OpFailure,
exact~ : (@ion_core.Decimal, @ion_core.Decimal) -> @ion_core.Decimal raise OpFailure,
approx~ : (Double, Double) -> Double raise OpFailure,
) -> @value.Value raise OpFailure {
let x = boxed(a)
let y = boxed(b)
if x is Missing || y is Missing {
return Missing
}
if x is Null || y is Null {
return Null
}
match (number(x), number(y)) {
(Some(Whole(m)), Some(Whole(n))) => whole(m, n)
(Some(p), Some(q)) =>
if p is Exact(_) || q is Exact(_) {
@value.Value::from_decimal(exact(to_decimal(p), to_decimal(q)))
} else {
@value.Value::from_float(approx(to_double(p), to_double(q)))
}
_ => raise TypeFailure("arithmetic on a value that is not a number")
}
}
///|
/// The datetime and interval rules, for two operands that are neither NULL
/// nor MISSING; `None` leaves the pair to the number rules.
fn datetime_first(
a : @value.Value,
b : @value.Value,
rule : (@value.Value, @value.Value) -> @value.Value? raise OpFailure,
) -> @value.Value? raise OpFailure {
let x = boxed(a)
let y = boxed(b)
if x is (Missing | Null) || y is (Missing | Null) {
return None
}
rule(x, y)
}
///|
/// Add numbers or a supported datetime/interval pair. Unbox Ion and propagate
/// MISSING before NULL. Numeric operands widen to decimal when either is
/// decimal, otherwise to float when either is float. Raise `TypeFailure` for
/// incompatible operands or `DataFailure` for overflow.
pub fn add(a : @value.Value, b : @value.Value) -> @value.Value raise OpFailure {
if datetime_first(a, b, datetime_add) is Some(v) {
return v
}
arithmetic(a, b, whole=(m, n) => whole_value(m + n), exact=decimal_add, approx=(
p,
q,
) => p + q)
}
///|
/// Subtract numbers or a supported datetime/interval pair. Use `add`'s
/// unboxing, absence, numeric widening and failure rules.
pub fn sub(a : @value.Value, b : @value.Value) -> @value.Value raise OpFailure {
if datetime_first(a, b, datetime_sub) is Some(v) {
return v
}
arithmetic(a, b, whole=(m, n) => whole_value(m - n), exact=decimal_sub, approx=(
p,
q,
) => p - q)
}
///|
/// Multiply numbers or a supported interval/number pair. Use `add`'s
/// unboxing, absence, numeric widening and failure rules.
pub fn mul(a : @value.Value, b : @value.Value) -> @value.Value raise OpFailure {
if datetime_first(a, b, datetime_mul) is Some(v) {
return v
}
arithmetic(
a,
b,
whole=(m, n) => whole_value(m * n),
exact=(p, q) => decimal_mul(p, q),
approx=(p, q) => p * q,
)
}
///|
/// Integer division truncates toward zero; a zero divisor of an integer or
/// decimal is a `DataFailure`, a float divides as IEEE does.
pub fn div(a : @value.Value, b : @value.Value) -> @value.Value raise OpFailure {
if datetime_first(a, b, datetime_div) is Some(v) {
return v
}
arithmetic(
a,
b,
whole=(m, n) => {
if n.is_zero() {
raise DataFailure("division by zero")
} else {
whole_value(m / n)
}
},
exact=decimal_div,
approx=(p, q) => p / q,
)
}
///|
/// The remainder has the sign of the dividend.
pub fn rem(a : @value.Value, b : @value.Value) -> @value.Value raise OpFailure {
arithmetic(
a,
b,
whole=(m, n) => {
if n.is_zero() {
raise DataFailure("division by zero")
} else {
whole_value(m % n)
}
},
exact=decimal_rem,
approx=(p, q) => p % q,
)
}
///|
/// `-x`, and `+x` when `negate` is false.
pub fn neg(v : @value.Value, negate~ : Bool) -> @value.Value raise OpFailure {
let x = boxed(v)
match x {
Null | Missing => x
IntervalYM(_) | IntervalDT(_) =>
if negate {
match interval_neg(x) {
Some(v) => v
None => x
}
} else {
x
}
_ =>
match number(x) {
None => raise TypeFailure("a sign on a value that is not a number")
Some(_) if !negate => x
Some(Whole(n)) => whole_value(-n)
Some(Exact(d)) =>
@value.Value::from_decimal(
@ion_core.Decimal::Decimal(-d.coefficient(), d.exponent()),
)
Some(Approx(f)) => @value.Value::from_float(-f)
}
}
}
///|
/// `a || b`: the concatenation of two strings.
pub fn concat(
a : @value.Value,
b : @value.Value,
) -> @value.Value raise OpFailure {
let x = boxed(a)
let y = boxed(b)
match (x, y) {
(Missing, _) | (_, Missing) => Missing
(Null, _) | (_, Null) => Null
(String(p), String(q)) => @value.Value::from_string(p + q)
_ => raise TypeFailure("|| on a value that is not a string")
}
}
///|
/// `ABS(x)`: `+x` when x >= 0, else `-x`, so an integer result outside Int64
/// is an overflow as for `-x`. A decimal keeps its exponent; a negative zero
/// becomes positive (partiql-tests ion/abs.ion). An interval gets sign `+`.
pub fn abs(args : Array[@value.Value]) -> @value.Value raise OpFailure {
apply("ABS", [Numeric], args, xs => {
let v = xs[0]
match v {
Int(n) =>
if n < 0L {
whole_value(-@bigint.BigInt::from_int64(n))
} else {
v
}
BigInt(n) => if is_negative_int(n) { whole_value(-n) } else { v }
Decimal(x) =>
@value.Value::from_decimal(
@ion_core.Decimal::Decimal(abs_int(x.coefficient()), x.exponent()),
)
Float(f) => @value.Value::from_float(f.abs())
IntervalYM(i) if i.is_negative() => interval_abs(v)
IntervalDT(i) if i.is_negative() => interval_abs(v)
IntervalYM(_) | IntervalDT(_) => v
_ => raise TypeFailure("ABS of a value that is not a number")
}
})
}
///|
fn interval_abs(v : @value.Value) -> @value.Value raise OpFailure {
match interval_neg(v) {
Some(x) => x
None => raise TypeFailure("ABS of a value that is not an interval")
}
}
///|
/// `MOD(a, b)`: the remainder with the sign of the dividend, for integers and
/// scale-0 decimals (SQL 6.28). A zero divisor is a `DataFailure`.
pub fn modulo(args : Array[@value.Value]) -> @value.Value raise OpFailure {
apply("MOD", [Exact, Exact], args, xs => rem(xs[0], xs[1]))
}