ironwork-rt 0.2.0

ironwork for COBOL: the runtime a compiled program links, under the runtime exception
Documentation
//! Fixed-point helpers the storage semantics share: building, aligning and comparing `Fixed`
//! values, an item's places, and zoned digits.

use crate::storage::Kind;
use numeric::precision::{Fixed, Places};
use std::cmp::Ordering;
use zarch::wide::U256;

/// The most digits a numeric item holds, under ARITH(EXTEND): all an alphanumeric sender can give
/// one (LONG_ZONED_BY_PACKS in numeric::assumptions).
pub const MAX_DIGITS: usize = 31;

pub fn fixed(negative: bool, magnitude: U256, places: Places) -> Fixed {
    Fixed { negative: negative && !magnitude.is_zero(), magnitude, places }
}

pub fn pow10(n: u32) -> U256 {
    U256::pow10(n)
}

/// The value's magnitude at `scale` decimal places, truncated or rounded half away from zero.
pub fn align(value: &Fixed, scale: u32, rounded: bool) -> Option<U256> {
    let from = value.places.dec;
    if scale >= from {
        return value.magnitude.checked_mul(pow10(scale - from));
    }
    let (q, r) = value.magnitude.div_rem(pow10(from - scale));
    let half = pow10(from - scale - 1).checked_mul(U256::from_u128(5))?;
    Some(if rounded && r >= half { q + U256::from_u128(1) } else { q })
}

pub fn compare_fixed(a: &Fixed, b: &Fixed) -> Ordering {
    let dec = a.places.dec.max(b.places.dec);
    let (ma, mb) = (align(a, dec, false).unwrap_or_default(), align(b, dec, false).unwrap_or_default());
    match (a.negative, b.negative) {
        (false, false) => ma.cmp(&mb),
        (true, true) => mb.cmp(&ma),
        (true, false) => Ordering::Less,
        (false, true) => Ordering::Greater,
    }
}

/// The places of an item's stored digits; PICTURE Ps to the left of the digits make `scale`
/// exceed `digits`.
pub fn places_of(kind: Kind) -> Places {
    let (digits, scale) = kind.digits_scale().unwrap_or((0, 0));
    Places::new(digits.saturating_sub(scale), scale)
}

/// A value times ten to the `k`: the algebraic value of digits that PICTURE Ps follow.
pub fn scaled_up(f: Fixed, k: u32) -> Fixed {
    if k == 0 {
        return f;
    }
    fixed(f.negative, f.magnitude.checked_mul(pow10(k)).unwrap_or_default(), Places::new(f.places.int + k, f.places.dec))
}

/// A value divided by ten to the `k`, exactly: the same digits with `k` more decimal places.
pub fn scaled_down(f: Fixed, k: u32) -> Fixed {
    Fixed { places: Places::new(f.places.int.saturating_sub(k), f.places.dec + k), ..f }
}

pub fn zoned_digits(magnitude: u128, digits: usize, sign_zone: u8) -> Vec<u8> {
    let mut out = vec![0xF0u8; digits];
    let mut m = magnitude;
    for b in out.iter_mut().rev() {
        *b = 0xF0 | (m % 10) as u8;
        m /= 10;
    }
    if let Some(last) = out.last_mut() {
        *last = (sign_zone << 4) | (*last & 0x0F);
    }
    out
}