// CAST to BOOL and the number types (spec M4d2 §5.2).
///|
/// The number a source value stands for: BOOL is 1 or 0, text is read by
/// `numeric_text`. An interval has no number in this version: its value does
/// not keep the field it counts (spec M4d2 §2, Q15).
fn numeric_source(x : @value.Value, target : String) -> Num raise OpFailure {
match x {
Bool(b) => Whole(if b { 1N } else { 0N })
String(s) => numeric_text(s)
IntervalYM(_) | IntervalDT(_) =>
raise Unsupported("CAST of an interval to \{target}")
_ =>
match number(x) {
Some(n) => n
None => raise TypeFailure("CAST of \{kind_name(x)} to \{target}")
}
}
}
///|
/// `x` as an exact decimal. A float becomes the decimal of its shortest
/// text (spec M4d2 §2, Q9); NaN and the infinities have no exact value.
fn exact_of(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 has no exact value")
}
match parse_decimal(f.to_string()) {
Some(d) => d
None => raise DataFailure("a float without a decimal")
}
}
}
}
///|
/// `c / 10^drop`, rounded half to even. `drop` is at least 1.
fn shift_round(c : @bigint.BigInt, drop : Int32) -> @bigint.BigInt {
// |c| < 10^digits <= 10^(drop - 2): the quotient is under 0.01.
if drop > digit_count(c) + 1 {
return 0N
}
let unit = pow10(drop)
let q = c / unit
let r = c - q * unit
let twice = abs_int(r) * 2N
let c2 = twice.compare(unit)
let up = c2 > 0 || (c2 == 0 && !(q % 2N).is_zero())
if !up {
q
} else if is_negative_int(c) {
q - 1N
} else {
q + 1N
}
}
///|
/// The integer nearest to `d`, ties to even. A value of more than 40
/// digits is out of the range of every integer type.
fn decimal_to_integer(d : @ion_core.Decimal) -> @bigint.BigInt raise OpFailure {
let c = d.coefficient()
if c.is_zero() {
return 0N
}
let e = d.exponent().to_int64()
if e >= 0L {
if digit_count(c).to_int64() + e > 40L {
raise DataFailure("a number out of the integer range")
}
c * pow10(e.to_int())
} else if -e > (digit_count(c) + 1).to_int64() {
0N
} else {
shift_round(c, (-e).to_int())
}
}
///|
fn width_range(width : IntWidth) -> (Int64, Int64) {
match width {
Bits8 => (-128L, 127L)
Bits16 => (-32768L, 32767L)
Bits32 => (-2147483648L, 2147483647L)
Bits64 => (-9223372036854775808L, 9223372036854775807L)
}
}
///|
fn to_integer(
x : @value.Value,
width : IntWidth,
) -> @value.Value raise OpFailure {
let name = target_name(Integer(width))
let n = match numeric_source(x, name) {
Whole(n) => n
Exact(d) => decimal_to_integer(d)
// The exact binary value: the shortest text of -2^63 is not -2^63.
Approx(f) => {
if f.is_nan() || f.is_inf() {
raise DataFailure("NaN or infinity is out of the range of \{name}")
}
decimal_to_integer(exact_decimal(f))
}
}
let (low, high) = width_range(width)
if n.compare_int64(low) < 0 || n.compare_int64(high) > 0 {
raise DataFailure("\{n} is out of the range of \{name}")
}
@value.Value::from_int(n.to_int64())
}
///|
fn is_zero_number(n : Num) -> Bool {
match n {
Whole(w) => w.is_zero()
Exact(d) => d.coefficient().is_zero()
Approx(f) => f == 0.0
}
}
///|
/// BOOL: text is TRUE, FALSE or UNKNOWN (NULL) in any case (SQL); a number
/// is FALSE when zero (spec M4d2 §2, Q8). NaN has no truth value.
fn to_bool(x : @value.Value) -> @value.Value raise OpFailure {
match x {
Bool(_) => x
String(s) =>
match s.trim(chars=" ").to_owned().to_lower() {
"true" => @value.Value::from_bool(true)
"false" => @value.Value::from_bool(false)
"unknown" => Null
_ => raise TypeFailure("'\{s}' is not a boolean")
}
_ =>
match number(x) {
Some(Approx(f)) if f.is_nan() => raise TypeFailure("NaN as BOOL")
Some(n) => @value.Value::from_bool(!is_zero_number(n))
None => raise TypeFailure("CAST of \{kind_name(x)} to BOOL")
}
}
}
///|
/// DECIMAL(p, s): `d` rounded half to even to scale `s`; more than `p - s`
/// integer digits is out of range.
fn fixed_decimal(
d : @ion_core.Decimal,
precision : Int32,
scale : Int32,
) -> @ion_core.Decimal raise OpFailure {
let c = d.coefficient()
if c.is_zero() {
return @ion_core.Decimal::Decimal(0N, -scale)
}
let too_long = DataFailure(
"a value out of the range of DECIMAL(\{precision},\{scale})",
)
let e = d.exponent().to_int64()
let target = -scale.to_int64()
// Integer digits before rounding; rounding adds at most one.
if digit_count(c).to_int64() + e > (precision - scale).to_int64() + 1L {
raise too_long
}
let rounded = if e >= target {
c * pow10((e - target).to_int())
} else if target - e > (digit_count(c) + 1).to_int64() {
0N
} else {
shift_round(c, (target - e).to_int())
}
if !rounded.is_zero() && digit_count(rounded) > precision {
raise too_long
}
@ion_core.Decimal::Decimal(rounded, -scale)
}
///|
fn to_decimal_target(
x : @value.Value,
scale : Scale,
) -> @value.Value raise OpFailure {
let d = exact_of(numeric_source(x, target_name(Decimal(scale))))
match scale {
Unconstrained => @value.Value::from_decimal(round_decimal(d))
Fixed(precision~, scale~) =>
@value.Value::from_decimal(fixed_decimal(d, precision, scale))
}
}
///|
/// The binary64 value nearest to `d` (`parse_double` rounds correctly).
fn exact_double(d : @ion_core.Decimal) -> Double raise OpFailure {
let f = @string.parse_double("\{d.coefficient()}e\{d.exponent()}") catch {
_ => raise DataFailure("a number out of the range of DOUBLE PRECISION")
}
if f.is_inf() {
raise DataFailure("a number out of the range of DOUBLE PRECISION")
}
f
}
///|
/// `f` rounded to binary32. A finite value that rounds to infinity is out of
/// the range of REAL.
fn to_real(f : Double) -> Double raise OpFailure {
let r = Float::from_double(f).to_double()
if r.is_inf() && !f.is_inf() {
raise DataFailure("\{f} is out of the range of REAL")
}
r
}
///|
/// The binary32 value nearest to the exact `d`, given `f`, the binary64 value
/// nearest to `d`. Rounding `f` again is wrong only when `f` lies exactly
/// halfway between two binary32 values: then the side of `d` decides.
fn nearest_real(d : @ion_core.Decimal, f : Double) -> Double raise OpFailure {
let r = to_real(f)
let other = 2.0 * f - r
if r == f || other == r || Float::from_double(other).to_double() != other {
return r
}
// Both values at one exponent; `Decimal::compare` gives no order here.
let (x, y, _) = aligned(d, exact_decimal(f))
let c = x.compare(y)
if c > 0 {
if r > other {
r
} else {
other
}
} else if c < 0 {
if r < other {
r
} else {
other
}
} else {
r
}
}
///|
fn to_float_target(
x : @value.Value,
width : FloatWidth,
) -> @value.Value raise OpFailure {
match numeric_source(x, target_name(Float(width))) {
Approx(f) =>
@value.Value::from_float(
match width {
Double => f
Real => to_real(f)
},
)
n => {
let d = exact_of(n)
let f = exact_double(d)
@value.Value::from_float(
match width {
Double => f
Real => nearest_real(d, f)
},
)
}
}
}