use core::{marker::PhantomData, ops::Neg};
use ufmt::derive::uDebug;
extern "C" {
fn kernal_poly1(poly: *const u8);
fn kernal_poly2(poly: *const u8);
fn kernal_cos();
fn kernal_sin();
fn kernal_tan();
fn kernal_atn();
fn kernal_chrout(c: u8);
fn kernal_settim(time: u32);
fn kernal_rdtim() -> u32;
fn kernal_screen() -> u16;
fn kernal_plot_get() -> u16;
fn kernal_plot_set(row: u8, column: u8);
}
pub const TRIG_CONSTANTS: *const TrigConstants = 0xe2e0 as *const _;
#[repr(C, packed)]
pub struct TrigConstants {
pub pi_over_2: F40,
pub pi_times_2: F40,
pub zero_point_2_5: F40,
pub sin_2_pi_x: Polynomial<OddTerms, 6>,
_atn_fn: [u8; 0x30],
pub atn: Polynomial<OddTerms, 12>,
}
#[derive(Clone, Copy, uDebug)]
#[repr(C, packed)]
pub struct F40 {
exp: u8,
sign_mantissa: [u8; 4],
}
impl F40 {
pub const fn new(mantissa: i32, exponent: i8) -> Option<Self> {
let (sign, mut mantissa) = (mantissa.is_negative(), mantissa.unsigned_abs() as u32);
let exp = if mantissa == 0 {
Some(0)
} else {
let exp_offset = mantissa.leading_zeros() as i8;
mantissa = (mantissa << exp_offset) & (u32::MAX >> 1);
let exp_offset = exp_offset - 32;
match exponent.checked_sub(exp_offset) {
Some(exp) if exp != i8::MIN => Some((exp as u8).wrapping_add(128)),
_ => None,
}
};
if let Some(exp) = exp {
Some(Self {
exp,
sign_mantissa: ((if sign { 1 << 31 } else { 0 }) | mantissa).to_be_bytes(),
})
} else {
None
}
}
pub const fn zero() -> Self {
Self {
exp: 0,
sign_mantissa: [0; 4],
}
}
pub const fn one() -> Self {
Self {
exp: 0x81,
sign_mantissa: [0; 4],
}
}
}
impl Neg for F40 {
type Output = Self;
fn neg(mut self) -> Self::Output {
self.sign_mantissa[0] ^= 0b1000_0000;
self
}
}
#[derive(Clone, Copy)]
#[repr(C, packed)]
pub struct Polynomial<Terms, const N: usize> {
degree: u8,
coefficients: [F40; N],
_terms: PhantomData<Terms>,
}
pub struct AllTerms;
pub struct OddTerms;
impl<const N: usize> Polynomial<AllTerms, N> {
pub const fn new(coefficients: [F40; N]) -> Option<Self> {
match N.checked_sub(1) {
Some(degree @ 0..=255) => Some(Self {
degree: degree as u8,
coefficients,
_terms: PhantomData,
}),
_ => None,
}
}
#[inline]
pub unsafe fn poly(&self) {
unsafe { kernal_poly2(self as *const _ as _) }
}
}
impl<const N: usize> Polynomial<OddTerms, N> {
pub const fn with_odd_terms(coefficients: [F40; N]) -> Option<Self> {
match N.checked_sub(1) {
Some(degree @ 0..=255) => Some(Self {
degree: degree as u8,
coefficients,
_terms: PhantomData,
}),
_ => None,
}
}
#[inline]
pub unsafe fn poly(&self) {
unsafe { kernal_poly1(self as *const _ as _) }
}
}
#[inline]
pub unsafe fn cos() {
unsafe { kernal_cos() }
}
#[inline]
pub unsafe fn sin() {
unsafe { kernal_sin() }
}
#[inline]
pub unsafe fn tan() {
unsafe { kernal_tan() }
}
#[inline]
pub unsafe fn atn() {
unsafe { kernal_atn() }
}
#[inline]
pub unsafe fn chrout(c: u8) {
unsafe { kernal_chrout(c) }
}
#[inline]
pub unsafe fn settim(jiffies: u32) {
unsafe { kernal_settim(jiffies) }
}
#[inline]
pub unsafe fn rdtim() -> u32 {
unsafe { kernal_rdtim() }
}
#[inline]
pub unsafe fn screen() -> (u8, u8) {
let v = unsafe { kernal_screen() };
((v >> 8) as u8, (v & 0x00ff) as u8)
}
#[inline]
pub unsafe fn plot_get() -> (u8, u8) {
let v = unsafe { kernal_plot_get() };
((v >> 8) as u8, (v & 0x00ff) as u8)
}
#[inline]
pub unsafe fn plot_set(row: u8, col: u8) {
unsafe { kernal_plot_set(row, col) };
}
#[cfg(test)]
mod tests {
use super::F40;
fn check(mantissa: i32, exponent: i8, exp: u8, sm: [u8; 4]) {
let f = F40::new(mantissa, exponent).expect("expected representable value");
let got_exp = f.exp;
let got_sm = f.sign_mantissa;
assert_eq!(
(got_exp, got_sm),
(exp, sm),
"F40::new({mantissa}, {exponent}) encoding mismatch",
);
}
#[test]
fn zero_is_canonical_regardless_of_exponent() {
for exponent in [-128i8, -1, 0, 1, 127] {
let f = F40::new(0, exponent).expect("0 is always representable");
assert_eq!(
(f.exp, f.sign_mantissa),
(0, [0, 0, 0, 0]),
"0 with exponent {exponent}",
);
}
}
#[test]
fn one_matches_one_const() {
check(1, 0, 0x81, [0, 0, 0, 0]);
let one_via_new = F40::new(1, 0).unwrap();
let one_const = F40::one();
assert_eq!(
(one_via_new.exp, one_via_new.sign_mantissa),
(one_const.exp, one_const.sign_mantissa),
"F40::new(1, 0) must match F40::one()",
);
}
#[test]
fn negative_one_only_differs_in_sign_bit() {
check(-1, 0, 0x81, [0x80, 0, 0, 0]);
}
#[test]
fn powers_of_two_have_zero_mantissa() {
check(2, 0, 0x82, [0, 0, 0, 0]);
check(4, 0, 0x83, [0, 0, 0, 0]);
check(1024, 0, 0x8b, [0, 0, 0, 0]);
check(1, 1, 0x82, [0, 0, 0, 0]);
check(1, 10, 0x8b, [0, 0, 0, 0]);
}
#[test]
fn three_has_one_mantissa_bit_set() {
check(3, 0, 0x82, [0x40, 0, 0, 0]);
}
#[test]
fn negative_exponent_yields_fraction() {
check(1, -1, 0x80, [0, 0, 0, 0]);
check(-1, -1, 0x80, [0x80, 0, 0, 0]);
check(1, -2, 0x7f, [0, 0, 0, 0]);
}
#[test]
fn i32_min_does_not_overflow_abs() {
check(i32::MIN, 0, 0xa0, [0x80, 0, 0, 0]);
}
#[test]
fn i32_max_round_trips() {
check(i32::MAX, 0, 0x9f, [0x7f, 0xff, 0xff, 0xfe]);
}
#[test]
fn exponent_overflow_returns_none() {
assert!(F40::new(1, 127).is_none());
assert!(F40::new(2, 126).is_none());
}
}