use crate::fixed_point::universal::tier_types::CompactShadow;
use crate::fixed_point::universal::fasc::stack_evaluator::StackValue;
use crate::fixed_point::universal::fasc::lazy_expr::LazyExpr;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum DomainChoice {
Binary = 0,
Decimal = 1,
Symbolic = 2,
}
pub const TERNARY_BIT: u8 = 1 << 0; pub const BINARY_BIT: u8 = 1 << 1; pub const DECIMAL_BIT: u8 = 1 << 2; pub const SYMBOLIC_BIT: u8 = 1 << 3;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct OperandClass {
pub exact_in: u8,
}
impl OperandClass {
pub const ALL: Self = Self {
exact_in: TERNARY_BIT | BINARY_BIT | DECIMAL_BIT | SYMBOLIC_BIT,
};
pub const BINARY_ONLY: Self = Self { exact_in: BINARY_BIT };
pub const DECIMAL_SYMBOLIC: Self = Self {
exact_in: DECIMAL_BIT | SYMBOLIC_BIT,
};
pub const SYMBOLIC_ONLY: Self = Self { exact_in: SYMBOLIC_BIT };
#[inline(always)]
pub const fn index(self) -> usize {
(self.exact_in & 0x0F) as usize
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum OpId {
Add = 0,
Sub = 1,
Mul = 2,
Div = 3,
Exp = 4,
Ln = 5,
Sqrt = 6,
Sin = 7,
Cos = 8,
Tan = 9,
Atan = 10,
Atan2 = 11,
Pow = 12,
Asin = 13,
Acos = 14,
Sinh = 15,
Cosh = 16,
Tanh = 17,
Asinh = 18,
Acosh = 19,
Atanh = 20,
}
const NUM_OPS: usize = 21;
#[inline]
pub fn classify(value: &StackValue) -> OperandClass {
let shadow = value.shadow();
if let Some((_num, den)) = shadow.as_rational() {
return classify_denominator(den);
}
if shadow.constant_id().is_some() {
return OperandClass {
exact_in: BINARY_BIT | SYMBOLIC_BIT,
};
}
classify_by_variant(value)
}
#[inline]
pub fn classify_denominator(den: u128) -> OperandClass {
if den == 0 {
return OperandClass::SYMBOLIC_ONLY;
}
let mut exact_in: u8 = SYMBOLIC_BIT;
let mut d = den;
while d & 1 == 0 {
d >>= 1;
}
if d == 1 {
exact_in |= BINARY_BIT;
}
d = den;
while d % 2 == 0 {
d /= 2;
}
while d % 5 == 0 {
d /= 5;
}
if d == 1 {
exact_in |= DECIMAL_BIT;
}
d = den;
while d % 3 == 0 {
d /= 3;
}
if d == 1 {
exact_in |= TERNARY_BIT;
}
OperandClass { exact_in }
}
#[inline]
fn classify_by_variant(value: &StackValue) -> OperandClass {
match value {
StackValue::Binary(..) | StackValue::BinaryCompute(..) => OperandClass::BINARY_ONLY,
StackValue::Decimal(..) | StackValue::DecimalCompute(..) => OperandClass {
exact_in: DECIMAL_BIT,
},
StackValue::Ternary(..) => OperandClass {
exact_in: TERNARY_BIT,
},
StackValue::Symbolic(_) => OperandClass::SYMBOLIC_ONLY,
StackValue::Error(_) => OperandClass { exact_in: 0 },
}
}
static ROUTING_TABLE: [[[DomainChoice; 16]; 16]; NUM_OPS] = build_routing_table();
const fn build_routing_table() -> [[[DomainChoice; 16]; 16]; NUM_OPS] {
let mut table = [[[DomainChoice::Symbolic; 16]; 16]; NUM_OPS];
let mut op = 0usize;
while op < NUM_OPS {
let mut left = 0u8;
while left < 16 {
let mut right = 0u8;
while right < 16 {
table[op][left as usize][right as usize] =
compute_route(op, left, right);
right += 1;
}
left += 1;
}
op += 1;
}
table
}
const fn compute_route(op: usize, left_class: u8, right_class: u8) -> DomainChoice {
if op <= 3 {
let intersection = left_class & right_class;
lowest_rank_domain(intersection)
} else {
transcendental_route(left_class)
}
}
const fn lowest_rank_domain(exact_in: u8) -> DomainChoice {
if exact_in & BINARY_BIT != 0 {
DomainChoice::Binary
} else if exact_in & DECIMAL_BIT != 0 {
DomainChoice::Decimal
} else {
DomainChoice::Symbolic
}
}
const fn transcendental_route(class: u8) -> DomainChoice {
if class & DECIMAL_BIT != 0 && class & BINARY_BIT == 0 {
DomainChoice::Decimal
} else {
DomainChoice::Binary
}
}
#[inline(always)]
pub fn route_binary_op(op: OpId, left: OperandClass, right: OperandClass) -> DomainChoice {
ROUTING_TABLE[op as usize][left.index()][right.index()]
}
#[inline(always)]
pub fn route_unary_op(op: OpId, operand: OperandClass) -> DomainChoice {
ROUTING_TABLE[op as usize][operand.index()][0]
}
pub fn coerce_to_decimal(value: &StackValue) -> Option<StackValue> {
match value {
StackValue::Decimal(..) | StackValue::DecimalCompute(..) => Some(value.clone()),
_ => {
let shadow = value.shadow();
if let Some((num, den)) = shadow.as_rational() {
shadow_to_decimal(num, den, shadow)
} else {
None
}
}
}
}
fn shadow_to_decimal(num: i128, den: u128, shadow: CompactShadow) -> Option<StackValue> {
if den == 0 {
return None;
}
let mut d = den;
let mut count2 = 0u32;
let mut count5 = 0u32;
while d % 2 == 0 {
d /= 2;
count2 += 1;
}
while d % 5 == 0 {
d /= 5;
count5 += 1;
}
if d != 1 {
return None; }
let dp = count2.max(count5);
if dp > 38 {
return None; }
let mut scaled = num;
for _ in 0..(dp - count2) {
scaled = scaled.checked_mul(2)?;
}
for _ in 0..(dp - count5) {
scaled = scaled.checked_mul(5)?;
}
use crate::fixed_point::universal::fasc::stack_evaluator::conversion::to_binary_storage;
let storage = to_binary_storage(scaled);
Some(StackValue::Decimal(dp as u8, storage, shadow))
}
pub fn route_expression(expr: &LazyExpr) -> OperandClass {
match expr {
LazyExpr::Literal(s) => classify_literal_string(s),
LazyExpr::Value(v) => classify(v),
LazyExpr::Constant(_) => OperandClass {
exact_in: BINARY_BIT | SYMBOLIC_BIT,
},
LazyExpr::Variable(_) => OperandClass::ALL,
LazyExpr::Negate(inner) => route_expression(inner),
LazyExpr::Add(l, r)
| LazyExpr::Sub(l, r)
| LazyExpr::Mul(l, r)
| LazyExpr::Div(l, r) => {
let lc = route_expression(l);
let rc = route_expression(r);
OperandClass {
exact_in: lc.exact_in & rc.exact_in,
}
}
LazyExpr::Pow(base, _exp) | LazyExpr::Atan2(base, _exp) => {
let child = route_expression(base);
let domain = transcendental_route(child.exact_in);
domain_to_class(domain)
}
LazyExpr::Exp(inner)
| LazyExpr::Ln(inner)
| LazyExpr::Sqrt(inner)
| LazyExpr::Sin(inner)
| LazyExpr::Cos(inner)
| LazyExpr::Tan(inner)
| LazyExpr::Asin(inner)
| LazyExpr::Acos(inner)
| LazyExpr::Atan(inner)
| LazyExpr::Sinh(inner)
| LazyExpr::Cosh(inner)
| LazyExpr::Tanh(inner)
| LazyExpr::Asinh(inner)
| LazyExpr::Acosh(inner)
| LazyExpr::Atanh(inner) => {
let child = route_expression(inner);
let domain = transcendental_route(child.exact_in);
domain_to_class(domain)
}
}
}
fn domain_to_class(domain: DomainChoice) -> OperandClass {
match domain {
DomainChoice::Binary => OperandClass::BINARY_ONLY,
DomainChoice::Decimal => OperandClass {
exact_in: DECIMAL_BIT,
},
DomainChoice::Symbolic => OperandClass::SYMBOLIC_ONLY,
}
}
pub fn classify_literal_string(s: &str) -> OperandClass {
let bytes = s.as_bytes();
if bytes.is_empty() {
return OperandClass::SYMBOLIC_ONLY;
}
if bytes.len() > 1 && bytes[0] == b'0' {
match bytes[1] {
b'x' | b'X' | b'b' | b'B' | b't' | b'T' => return OperandClass::ALL,
_ => {}
}
}
if bytes[0].is_ascii_alphabetic() || bytes[0] > 127 {
return OperandClass {
exact_in: BINARY_BIT | SYMBOLIC_BIT,
};
}
if let Some(slash_pos) = bytes.iter().position(|&b| b == b'/') {
let den_str = &s[slash_pos + 1..];
let den_str = den_str.trim();
if let Ok(den) = den_str.parse::<u128>() {
return classify_denominator(den);
}
return OperandClass::SYMBOLIC_ONLY;
}
if s.len() > 3 && s.ends_with("...") {
return OperandClass::SYMBOLIC_ONLY;
}
if bytes.contains(&b'.') {
return OperandClass::DECIMAL_SYMBOLIC;
}
OperandClass::ALL
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn classify_den_1_is_all_domains() {
let c = classify_denominator(1);
assert_ne!(c.exact_in & BINARY_BIT, 0, "den=1 should be binary-exact");
assert_ne!(c.exact_in & DECIMAL_BIT, 0, "den=1 should be decimal-exact");
assert_ne!(c.exact_in & TERNARY_BIT, 0, "den=1 should be ternary-exact");
assert_ne!(c.exact_in & SYMBOLIC_BIT, 0, "den=1 should be symbolic-exact");
}
#[test]
fn classify_den_2_is_binary_decimal() {
let c = classify_denominator(2);
assert_ne!(c.exact_in & BINARY_BIT, 0, "1/2 should be binary-exact");
assert_ne!(c.exact_in & DECIMAL_BIT, 0, "1/2 should be decimal-exact");
assert_eq!(c.exact_in & TERNARY_BIT, 0, "1/2 should NOT be ternary-exact");
}
#[test]
fn classify_den_10_is_decimal_only() {
let c = classify_denominator(10);
assert_eq!(c.exact_in & BINARY_BIT, 0, "1/10 should NOT be binary-exact");
assert_ne!(c.exact_in & DECIMAL_BIT, 0, "1/10 should be decimal-exact");
assert_eq!(c.exact_in & TERNARY_BIT, 0, "1/10 should NOT be ternary-exact");
}
#[test]
fn classify_den_100_is_decimal_only() {
let c = classify_denominator(100);
assert_eq!(c.exact_in & BINARY_BIT, 0);
assert_ne!(c.exact_in & DECIMAL_BIT, 0);
}
#[test]
fn classify_den_3_is_ternary_only() {
let c = classify_denominator(3);
assert_eq!(c.exact_in & BINARY_BIT, 0);
assert_eq!(c.exact_in & DECIMAL_BIT, 0);
assert_ne!(c.exact_in & TERNARY_BIT, 0);
}
#[test]
fn classify_den_27_is_ternary_only() {
let c = classify_denominator(27); assert_ne!(c.exact_in & TERNARY_BIT, 0);
assert_eq!(c.exact_in & BINARY_BIT, 0);
}
#[test]
fn classify_den_7_is_symbolic_only() {
let c = classify_denominator(7);
assert_eq!(c.exact_in & BINARY_BIT, 0);
assert_eq!(c.exact_in & DECIMAL_BIT, 0);
assert_eq!(c.exact_in & TERNARY_BIT, 0);
assert_ne!(c.exact_in & SYMBOLIC_BIT, 0);
}
#[test]
fn classify_den_8_is_binary_decimal() {
let c = classify_denominator(8); assert_ne!(c.exact_in & BINARY_BIT, 0, "1/8 binary-exact");
assert_ne!(c.exact_in & DECIMAL_BIT, 0, "1/8 = 0.125, decimal-exact");
}
#[test]
fn classify_den_5_is_decimal_only() {
let c = classify_denominator(5);
assert_eq!(c.exact_in & BINARY_BIT, 0, "1/5 not binary-exact");
assert_ne!(c.exact_in & DECIMAL_BIT, 0, "1/5 = 0.2, decimal-exact");
}
#[test]
fn classify_den_6_is_symbolic() {
let c = classify_denominator(6); assert_eq!(c.exact_in & BINARY_BIT, 0);
assert_eq!(c.exact_in & DECIMAL_BIT, 0);
assert_eq!(c.exact_in & TERNARY_BIT, 0);
assert_ne!(c.exact_in & SYMBOLIC_BIT, 0);
}
#[test]
fn route_decimal_plus_integer_is_decimal() {
let lc = OperandClass::DECIMAL_SYMBOLIC;
let rc = OperandClass::ALL;
assert_eq!(route_binary_op(OpId::Add, lc, rc), DomainChoice::Decimal);
}
#[test]
fn route_integer_plus_integer_is_binary() {
let lc = OperandClass::ALL;
let rc = OperandClass::ALL;
assert_eq!(route_binary_op(OpId::Add, lc, rc), DomainChoice::Binary);
}
#[test]
fn route_decimal_plus_ternary_fraction_is_symbolic() {
let lc = OperandClass::DECIMAL_SYMBOLIC;
let rc = OperandClass {
exact_in: TERNARY_BIT | SYMBOLIC_BIT,
};
assert_eq!(
route_binary_op(OpId::Add, lc, rc),
DomainChoice::Symbolic
);
}
#[test]
fn route_binary_half_plus_decimal_is_decimal() {
let lc = OperandClass {
exact_in: BINARY_BIT | DECIMAL_BIT | SYMBOLIC_BIT,
};
let rc = OperandClass::DECIMAL_SYMBOLIC;
assert_eq!(route_binary_op(OpId::Mul, lc, rc), DomainChoice::Decimal);
}
#[test]
fn route_mul_all_domains() {
assert_eq!(
route_binary_op(OpId::Mul, OperandClass::ALL, OperandClass::ALL),
DomainChoice::Binary
);
}
#[test]
fn route_div_decimal_by_integer() {
let lc = OperandClass::DECIMAL_SYMBOLIC;
let rc = OperandClass::ALL;
assert_eq!(route_binary_op(OpId::Div, lc, rc), DomainChoice::Decimal);
}
#[test]
fn route_exp_decimal_input() {
let c = OperandClass::DECIMAL_SYMBOLIC;
assert_eq!(route_unary_op(OpId::Exp, c), DomainChoice::Decimal);
}
#[test]
fn route_exp_integer_input() {
let c = OperandClass::ALL;
assert_eq!(route_unary_op(OpId::Exp, c), DomainChoice::Binary);
}
#[test]
fn route_exp_binary_only() {
let c = OperandClass::BINARY_ONLY;
assert_eq!(route_unary_op(OpId::Exp, c), DomainChoice::Binary);
}
#[test]
fn literal_decimal() {
let c = classify_literal_string("0.1");
assert_ne!(c.exact_in & DECIMAL_BIT, 0);
assert_eq!(c.exact_in & BINARY_BIT, 0);
}
#[test]
fn literal_integer() {
assert_eq!(classify_literal_string("255"), OperandClass::ALL);
}
#[test]
fn literal_negative_integer() {
assert_eq!(classify_literal_string("-42"), OperandClass::ALL);
}
#[test]
fn literal_hex() {
assert_eq!(classify_literal_string("0xFF"), OperandClass::ALL);
}
#[test]
fn literal_fraction_third() {
let c = classify_literal_string("1/3");
assert_ne!(c.exact_in & TERNARY_BIT, 0);
assert_eq!(c.exact_in & BINARY_BIT, 0);
assert_eq!(c.exact_in & DECIMAL_BIT, 0);
}
#[test]
fn literal_fraction_half() {
let c = classify_literal_string("1/2");
assert_ne!(c.exact_in & BINARY_BIT, 0);
assert_ne!(c.exact_in & DECIMAL_BIT, 0);
}
#[test]
fn literal_named_constant() {
let c = classify_literal_string("pi");
assert_ne!(c.exact_in & BINARY_BIT, 0);
assert_eq!(c.exact_in & DECIMAL_BIT, 0);
}
#[test]
fn literal_repeating() {
let c = classify_literal_string("0.333...");
assert_eq!(c, OperandClass::SYMBOLIC_ONLY);
}
#[test]
fn tree_walker_literal_add() {
let expr = LazyExpr::Add(
Box::new(LazyExpr::Literal("0.1")),
Box::new(LazyExpr::Literal("255")),
);
let class = route_expression(&expr);
assert_ne!(class.exact_in & DECIMAL_BIT, 0, "should be decimal-exact");
assert_eq!(class.exact_in & BINARY_BIT, 0, "should NOT be binary-exact");
}
#[test]
fn tree_walker_integer_add() {
let expr = LazyExpr::Add(
Box::new(LazyExpr::Literal("3")),
Box::new(LazyExpr::Literal("6")),
);
let class = route_expression(&expr);
assert_eq!(class, OperandClass::ALL);
}
#[test]
fn tree_walker_transcendental() {
let expr = LazyExpr::Exp(Box::new(LazyExpr::Literal("0.1")));
let class = route_expression(&expr);
assert_ne!(class.exact_in & DECIMAL_BIT, 0);
assert_eq!(class.exact_in & BINARY_BIT, 0);
}
#[test]
fn tree_walker_mixed_chain() {
let inner = LazyExpr::Add(
Box::new(LazyExpr::Literal("0.1")),
Box::new(LazyExpr::Literal("255")),
);
let expr = LazyExpr::Exp(Box::new(inner));
let class = route_expression(&expr);
assert_ne!(class.exact_in & DECIMAL_BIT, 0);
}
}