///|
/// Exact base-ten coefficient/scale. No binary floating-point in standard checks.
priv struct LasDecimal {
coefficient : @bigint.BigInt
scale : Int
}
///|
fn decimal_grammar(text : String) -> Bool {
let chars = text.iter().to_array()
let mut digits = 0
let mut dots = 0
for i in 0..= '0' && c <= '9' {
digits += 1
} else if c == '.' {
dots += 1
if dots > 1 {
return false
}
} else if i != 0 || (c != '+' && c != '-') {
return false
}
}
digits > 0
}
///|
fn exact_decimal(text : String) -> LasDecimal {
let digits = StringBuilder()
let mut scale = 0
let mut fraction = false
for c in text.iter() {
if c == '.' {
fraction = true
}
if c >= '0' && c <= '9' {
digits.write_char(c)
if fraction {
scale += 1
}
}
}
let n = @bigint.BigInt::from_string(digits.to_string())
{ coefficient: if text.has_prefix("-") { -n } else { n }, scale, }
}
///|
fn ten_power(n : Int) -> @bigint.BigInt {
@bigint.BigInt::from_int(10).pow(@bigint.BigInt::from_int(n))
}
///|
fn decimal_scaled(n : LasDecimal, scale : Int) -> @bigint.BigInt {
n.coefficient * ten_power(scale - n.scale)
}
///|
fn decimal_compare(a : LasDecimal, b : LasDecimal) -> Int {
let scale = if a.scale > b.scale { a.scale } else { b.scale }
decimal_scaled(a, scale).compare(decimal_scaled(b, scale))
}
///|
fn decimal_difference(a : LasDecimal, b : LasDecimal) -> LasDecimal {
let scale = if a.scale > b.scale { a.scale } else { b.scale }
{ coefficient: decimal_scaled(a, scale) - decimal_scaled(b, scale), scale, }
}
///|
fn decimal_multiple(a : LasDecimal, step : LasDecimal) -> Bool {
if step.coefficient.is_zero() {
return true
}
let scale = if a.scale > step.scale { a.scale } else { step.scale }
(decimal_scaled(a, scale) % decimal_scaled(step, scale)).is_zero()
}
///|
fn decimal_string(n : LasDecimal) -> String {
let text = n.coefficient.to_string()
let negative = text.has_prefix("-")
let chars = text.iter().filter(c => c != '-').to_array()
let out = StringBuilder()
if negative {
out.write_char('-')
}
let position = chars.length() - n.scale
if position <= 0 {
out.write_string("0.")
for _ in 0..<(0 - position) {
out.write_char('0')
}
}
for i in 0.. 0 && i == position {
out.write_char('.')
}
out.write_char(chars[i])
}
out.to_string()
}
///|
/// Expand the shortest round-trippable Double spelling without exponent notation.
/// Does not round to a chosen precision or change observed values.
pub fn las_decimal_text(value : Double) -> String raise WellError {
if !finite(value) {
fail("nonfinite LAS value")
}
let text = value.to_string().to_upper()
match text.split_once("E") {
None => text
Some((mantissa, exponent)) => {
let exp = @string.parse_int(exponent.to_owned()) catch {
_ => {
fail("invalid numeric exponent from runtime")
0
}
}
let m = exact_decimal(mantissa.to_owned())
if exp <= m.scale {
decimal_string({ coefficient: m.coefficient, scale: m.scale - exp, })
} else {
decimal_string({
coefficient: m.coefficient * ten_power(exp - m.scale),
scale: 0,
})
}
}
}
}
///|
fn serialized_step(log : Log) -> String raise WellError {
if log.rows.length() < 2 {
return "0"
}
let first = exact_decimal(las_decimal_text(log.rows[0][0].unwrap()))
let second = exact_decimal(las_decimal_text(log.rows[1][0].unwrap()))
let step = decimal_difference(second, first)
let mut previous = second
for i in 2..