talc/talc-std/src/num.rs

684 lines
18 KiB
Rust

use std::cmp::Ordering;
use lazy_static::lazy_static;
use talc_lang::{parse_int, symbol::{Symbol, SYM_TYPE_ERROR}, throw, value::{Complex64, Value}, Vm, exception::Result};
use talc_macros::native_func;
use crate::{unpack_args, unpack_varargs};
lazy_static! {
static ref SYM_NAN: Symbol = Symbol::get("nan");
static ref SYM_INFINITE: Symbol = Symbol::get("infinite");
static ref SYM_ZERO: Symbol = Symbol::get("zero");
static ref SYM_SUBNORMAL: Symbol = Symbol::get("subnormal");
static ref SYM_NORMAL: Symbol = Symbol::get("normal");
}
#[inline]
fn to_floaty(v: Value) -> Value {
match v {
Value::Int(v) => Value::Float(v as f64),
Value::Ratio(v) => Value::Float(*v.numer() as f64 / *v.denom() as f64),
Value::Float(v) => Value::Float(v),
Value::Complex(v) => Value::Complex(v),
v => v,
}
}
#[inline]
fn to_complex(v: Value) -> Value {
match v {
Value::Int(v) => Value::Complex((v as f64).into()),
Value::Ratio(v) => Value::Complex((*v.numer() as f64 / *v.denom() as f64).into()),
Value::Float(v) => Value::Complex(v.into()),
Value::Complex(v) => Value::Complex(v),
v => v,
}
}
//
// load
//
pub fn load(vm: &mut Vm) {
vm.set_global_name("e", std::f64::consts::E.into());
vm.set_global_name("tau", std::f64::consts::TAU.into());
vm.set_global_name("pi", std::f64::consts::PI.into());
vm.set_global_name("egamma", 0.5772156649015329.into());
vm.set_global_name("phi", 1.618033988749895.into());
vm.set_global_name("inf", (f64::INFINITY).into());
vm.set_global_name("NaN", (f64::NAN).into());
vm.set_global_name("infi", Complex64::new(0.0,f64::INFINITY).into());
vm.set_global_name("NaNi", Complex64::new(0.0,f64::NAN).into());
vm.set_global_name("bin", bin().into());
vm.set_global_name("sex", sex().into());
vm.set_global_name("oct", oct().into());
vm.set_global_name("hex", hex().into());
vm.set_global_name("to_radix", to_radix().into());
vm.set_global_name("from_radix", from_radix().into());
vm.set_global_name("gcd", gcd().into());
vm.set_global_name("lcm", lcm().into());
vm.set_global_name("isqrt", isqrt().into());
vm.set_global_name("isprime", isprime().into());
vm.set_global_name("factors", factors().into());
vm.set_global_name("min", min().into());
vm.set_global_name("max", max().into());
vm.set_global_name("floor", floor().into());
vm.set_global_name("ceil", ceil().into());
vm.set_global_name("round", round().into());
vm.set_global_name("trunc", trunc().into());
vm.set_global_name("fract", fract().into());
vm.set_global_name("sign", sign().into());
vm.set_global_name("signum", signum().into());
vm.set_global_name("classify", classify().into());
vm.set_global_name("isnan", isnan().into());
vm.set_global_name("isfinite", isfinite().into());
vm.set_global_name("isinfinite", isinfinite().into());
vm.set_global_name("numer", numer().into());
vm.set_global_name("denom", denom().into());
vm.set_global_name("re", re().into());
vm.set_global_name("im", im().into());
vm.set_global_name("arg", arg().into());
vm.set_global_name("abs", abs().into());
vm.set_global_name("abs_sq", abs_sq().into());
vm.set_global_name("sqrt", sqrt().into());
vm.set_global_name("cbrt", cbrt().into());
vm.set_global_name("ln", cbrt().into());
vm.set_global_name("log2", cbrt().into());
vm.set_global_name("exp", cbrt().into());
vm.set_global_name("exp2", cbrt().into());
vm.set_global_name("sin", sin().into());
vm.set_global_name("cos", cos().into());
vm.set_global_name("tan", tan().into());
vm.set_global_name("asin", asin().into());
vm.set_global_name("acos", acos().into());
vm.set_global_name("atan", atan().into());
vm.set_global_name("sinh", sinh().into());
vm.set_global_name("cosh", cosh().into());
vm.set_global_name("tanh", tanh().into());
vm.set_global_name("asinh", asinh().into());
vm.set_global_name("acosh", acosh().into());
vm.set_global_name("atanh", atanh().into());
vm.set_global_name("atanh", atanh().into());
vm.set_global_name("atan2", atan2().into());
}
//
// base conversions
//
fn to_radix_inner(n: i64, radix: u32) -> String {
let mut result = vec![];
let mut begin = 0;
let mut x;
if n < 0 {
result.push('-' as u32 as u8);
begin = 1;
x = (-n) as u64;
} else {
x = n as u64;
}
loop {
let m = x % (radix as u64);
x /= radix as u64;
result.push(char::from_digit(m as u32, radix).unwrap() as u8);
if x == 0 {
break;
}
}
result[begin..].reverse();
String::from_utf8(result).expect("string was not valid utf8")
}
#[native_func(1)]
pub fn bin(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, x] = unpack_args!(args);
let Value::Int(x) = x else {
throw!(*SYM_TYPE_ERROR, "bin expected integer argument, got {x:#}")
};
Ok(to_radix_inner(x, 2).into())
}
#[native_func(1)]
pub fn sex(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, x] = unpack_args!(args);
let Value::Int(x) = x else {
throw!(*SYM_TYPE_ERROR, "sex expected integer argument, got {x:#}")
};
Ok(to_radix_inner(x, 6).into())
}
#[native_func(1)]
pub fn oct(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, x] = unpack_args!(args);
let Value::Int(x) = x else {
throw!(*SYM_TYPE_ERROR, "oct expected integer argument, got {x:#}")
};
Ok(to_radix_inner(x, 8).into())
}
#[native_func(1)]
pub fn hex(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, x] = unpack_args!(args);
let Value::Int(x) = x else {
throw!(*SYM_TYPE_ERROR, "hex expected integer argument, got {x:#}")
};
Ok(to_radix_inner(x, 16).into())
}
#[native_func(2)]
pub fn to_radix(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, x, radix] = unpack_args!(args);
let (Value::Int(x), Value::Int(radix)) = (&x, &radix) else {
throw!(*SYM_TYPE_ERROR, "to_radix expected integer arguments, got {x:#} and {radix:#}")
};
if *radix < 2 || *radix > 36 {
throw!(*SYM_TYPE_ERROR, "to_radix expected radix in range 0..=36")
}
Ok(to_radix_inner(*x, *radix as u32).into())
}
#[native_func(2)]
pub fn from_radix(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, s, radix] = unpack_args!(args);
let (Value::String(s), Value::Int(radix)) = (&s, &radix) else {
throw!(*SYM_TYPE_ERROR, "from_radix expected string and integer arguments, got {s:#} and {radix:#}")
};
if *radix < 2 || *radix > 36 {
throw!(*SYM_TYPE_ERROR, "from_radix expected radix in range 0..=36")
}
match parse_int(s, *radix as u32) {
Ok(v) => Ok(v.into()),
Err(_) => throw!(*SYM_TYPE_ERROR, "string was not a valid integer in given radix"),
}
}
//
// integer operations
//
fn isqrt_inner(mut n: i64) -> i64 {
assert!(n >= 0, "isqrt input should be nonnegative");
if n < 2 { return n }
let mut c = 0;
let mut d = 1 << 62;
while d > n {
d >>= 2;
}
while d != 0 {
if n >= c + d {
n -= c + d;
c = (c >> 1) + d;
}
else {
c >>= 1;
}
d >>= 2;
}
c
}
pub fn gcd_inner(a: i64, b: i64) -> i64 {
let (mut a, mut b) = (a.abs(), b.abs());
if a == 0 {
return b
}
if b == 0 {
return a
}
let az = a.trailing_zeros();
a >>= az;
let bz = b.trailing_zeros();
b >>= bz;
let z = az.min(bz);
loop {
if a > b {
std::mem::swap(&mut a, &mut b);
}
b -= a;
if b == 0 {
return a << z;
}
b >>= b.trailing_zeros();
}
}
#[native_func(1)]
pub fn isqrt(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, x] = unpack_args!(args);
let Value::Int(x) = x else {
throw!(*SYM_TYPE_ERROR, "isqrt expected integer argument, got {x:#}")
};
if x < 0 {
throw!(*SYM_TYPE_ERROR, "isqrt: argument must be positive")
}
Ok(isqrt_inner(x).into())
}
#[native_func(1)]
pub fn isprime(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, x] = unpack_args!(args);
let Value::Int(x) = x else {
throw!(*SYM_TYPE_ERROR, "isprime expected integer argument, got {x:#}")
};
if x < 2 {
return Ok(false.into())
}
for p in [2, 3, 5, 7] {
if x % p == 0 {
return Ok((x == p).into())
}
}
if x < 11 {
return Ok(false.into())
}
let lim = isqrt_inner(x);
let mut i = 12;
while i <= lim+1 {
if x % (i - 1) == 0 { return Ok(false.into()) }
if x % (i + 1) == 0 { return Ok(false.into()) }
i += 6;
}
Ok(true.into())
}
#[native_func(2..)]
pub fn gcd(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let ([_, x, y], rest) = unpack_varargs!(args);
let Value::Int(x) = x else {
throw!(*SYM_TYPE_ERROR, "gcd expected integer argument, got {x:#}")
};
let Value::Int(y) = y else {
throw!(*SYM_TYPE_ERROR, "gcd expected integer argument, got {x:#}")
};
let mut g = gcd_inner(x, y);
for a in rest {
let Value::Int(a) = a else {
throw!(*SYM_TYPE_ERROR, "gcd expected integer argument, got {a:#}")
};
g = gcd_inner(g, a);
}
Ok(g.into())
}
#[native_func(2..)]
pub fn lcm(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let ([_, x, y], rest) = unpack_varargs!(args);
let Value::Int(x) = x else {
throw!(*SYM_TYPE_ERROR, "lcm expected integer argument, got {x:#}")
};
let Value::Int(y) = y else {
throw!(*SYM_TYPE_ERROR, "lcm expected integer argument, got {x:#}")
};
let mut g = gcd_inner(x, y);
let mut prod = y;
for a in rest {
let Value::Int(a) = a else {
throw!(*SYM_TYPE_ERROR, "lcm expected integer argument, got {a:#}")
};
prod *= a;
g = gcd_inner(g, a);
}
Ok((x/g * prod).into())
}
#[native_func(1)]
pub fn factors(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, x] = unpack_args!(args);
let Value::Int(mut x) = x else {
throw!(*SYM_TYPE_ERROR, "factords expected integer argument, got {x:#}")
};
let mut factors = Vec::new();
if x <= 1 {
return Ok(factors.into())
}
while x & 1 == 0 {
x >>= 1;
factors.push(Value::Int(2));
}
while x % 3 == 0 {
x /= 3;
factors.push(Value::Int(3));
}
let mut i = 5;
while x >= i*i {
while x % i == 0 {
x /= i;
factors.push(Value::Int(i));
}
i += 2;
while x % i == 0 {
x /= i;
factors.push(Value::Int(i));
}
i += 4;
}
if x > 1 {
factors.push(Value::Int(x));
}
Ok(factors.into())
}
//
// numeric operations
//
#[native_func(1..)]
pub fn min(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let ([_, mut x], rest) = unpack_varargs!(args);
for val in rest {
if val < x {
x = val;
}
}
Ok(x)
}
#[native_func(1..)]
pub fn max(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let ([_, mut x], rest) = unpack_varargs!(args);
for val in rest {
if val > x {
x = val;
}
}
Ok(x)
}
#[native_func(1)]
pub fn floor(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, x] = unpack_args!(args);
match x {
Value::Int(x) => Ok(Value::Int(x)),
Value::Float(x) => Ok(Value::Float(x.floor())),
Value::Ratio(x) => Ok(Value::Ratio(x.floor())),
x => throw!(*SYM_TYPE_ERROR, "floor expected real argument, got {x:#}"),
}
}
#[native_func(1)]
pub fn ceil(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, x] = unpack_args!(args);
match x {
Value::Int(x) => Ok(Value::Int(x)),
Value::Float(x) => Ok(Value::Float(x.ceil())),
Value::Ratio(x) => Ok(Value::Ratio(x.ceil())),
x => throw!(*SYM_TYPE_ERROR, "ceil expected real argument, got {x:#}"),
}
}
#[native_func(1)]
pub fn round(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, x] = unpack_args!(args);
match x {
Value::Int(x) => Ok(Value::Int(x)),
Value::Float(x) => Ok(Value::Float(x.round())),
Value::Ratio(x) => Ok(Value::Ratio(x.round())),
x => throw!(*SYM_TYPE_ERROR, "round expected real argument, got {x:#}"),
}
}
#[native_func(1)]
pub fn trunc(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, x] = unpack_args!(args);
match x {
Value::Int(x) => Ok(Value::Int(x)),
Value::Float(x) => Ok(Value::Float(x.trunc())),
Value::Ratio(x) => Ok(Value::Ratio(x.trunc())),
x => throw!(*SYM_TYPE_ERROR, "trunc expected real argument, got {x:#}"),
}
}
#[native_func(1)]
pub fn fract(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, x] = unpack_args!(args);
match x {
Value::Int(_) => Ok(Value::Int(0)),
Value::Float(x) => Ok(Value::Float(x.fract())),
Value::Ratio(x) => Ok(Value::Ratio(x.fract())),
x => throw!(*SYM_TYPE_ERROR, "fract expected real argument, got {x:#}"),
}
}
#[native_func(1)]
pub fn sign(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, x] = unpack_args!(args);
match x {
Value::Int(x) => Ok(Value::Int(x.signum())),
Value::Float(x) => Ok(Value::Float(if x == 0.0 { 0.0 } else { x.signum() })),
Value::Ratio(x) => match x.cmp(&0.into()) {
Ordering::Greater => Ok(Value::Ratio(1.into())),
Ordering::Less => Ok(Value::Ratio((-1).into())),
Ordering::Equal => Ok(Value::Ratio(0.into())),
}
x => throw!(*SYM_TYPE_ERROR, "sign expected real argument, got {x:#}"),
}
}
//
// floating-point operations
//
#[native_func(1)]
pub fn signum(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, x] = unpack_args!(args);
match to_floaty(x) {
Value::Float(x) => Ok(Value::Float(x.signum())),
x => throw!(*SYM_TYPE_ERROR, "signum expected real argument, got {x:#}"),
}
}
#[native_func(1)]
pub fn classify(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, x] = unpack_args!(args);
let x = to_floaty(x);
let Value::Float(x) = x else {
throw!(*SYM_TYPE_ERROR, "classify expected real argument, got {x:#}")
};
Ok(match x.classify() {
std::num::FpCategory::Nan => *SYM_NAN,
std::num::FpCategory::Infinite => *SYM_INFINITE,
std::num::FpCategory::Zero => *SYM_ZERO,
std::num::FpCategory::Subnormal => *SYM_SUBNORMAL,
std::num::FpCategory::Normal => *SYM_NORMAL,
}.into())
}
#[native_func(1)]
pub fn isnan(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, v] = unpack_args!(args);
match v {
Value::Int(_) | Value::Ratio(_) => Ok(false.into()),
Value::Float(x) => Ok(x.is_nan().into()),
Value::Complex(z) => Ok(z.is_nan().into()),
v => throw!(*SYM_TYPE_ERROR, "isnan expected numeric argument, got {v:#}"),
}
}
#[native_func(1)]
pub fn isfinite(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, v] = unpack_args!(args);
match v {
Value::Int(_) | Value::Ratio(_) => Ok(true.into()),
Value::Float(x) => Ok(x.is_finite().into()),
Value::Complex(z) => Ok(z.is_finite().into()),
v => throw!(*SYM_TYPE_ERROR, "isfinite expected numeric argument, got {v:#}"),
}
}
#[native_func(1)]
pub fn isinfinite(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, v] = unpack_args!(args);
match v {
Value::Int(_) | Value::Ratio(_) => Ok(false.into()),
Value::Float(x) => Ok(x.is_infinite().into()),
Value::Complex(z) => Ok(z.is_infinite().into()),
v => throw!(*SYM_TYPE_ERROR, "isinfinite expected numeric argument, got {v:#}"),
}
}
//
// rational operations
//
#[native_func(1)]
pub fn numer(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, v] = unpack_args!(args);
match v {
Value::Int(x) => Ok(Value::Int(x)),
Value::Ratio(x) => Ok(Value::Int(*x.numer())),
v => throw!(*SYM_TYPE_ERROR, "numer expected rational argument, got {v:#}"),
}
}
#[native_func(1)]
pub fn denom(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, v] = unpack_args!(args);
match v {
Value::Int(_) => Ok(Value::Int(1)),
Value::Ratio(x) => Ok(Value::Int(*x.denom())),
v => throw!(*SYM_TYPE_ERROR, "denom expected rational argument, got {v:#}"),
}
}
//
// complex operations
//
#[native_func(1)]
pub fn re(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, v] = unpack_args!(args);
match v {
Value::Int(x) => Ok(Value::Int(x)),
Value::Ratio(x) => Ok(Value::Ratio(x)),
Value::Float(x) => Ok(Value::Float(x)),
Value::Complex(z) => Ok(Value::Float(z.re)),
v => throw!(*SYM_TYPE_ERROR, "re expected numeric argument, got {v:#}"),
}
}
#[native_func(1)]
pub fn im(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, v] = unpack_args!(args);
match to_complex(v) {
Value::Int(_) => Ok(Value::Int(0)),
Value::Ratio(_) => Ok(Value::Ratio(0.into())),
Value::Float(_) => Ok(Value::Float(0.0)),
Value::Complex(z) => Ok(Value::Float(z.im)),
v => throw!(*SYM_TYPE_ERROR, "im expected numeric argument, got {v:#}"),
}
}
#[native_func(1)]
pub fn arg(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, v] = unpack_args!(args);
match to_complex(v) {
Value::Complex(z) => Ok(Value::Float(z.arg())),
v => throw!(*SYM_TYPE_ERROR, "im expected numeric argument, got {v:#}"),
}
}
#[native_func(1)]
pub fn abs(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, x] = unpack_args!(args);
match x {
Value::Int(x) => Ok(Value::Int(x.abs())),
Value::Ratio(x) => Ok(Value::Ratio(if x < 0.into() { -x } else { x })),
Value::Float(x) => Ok(Value::Float(x.abs())),
Value::Complex(x) => Ok(Value::Float(x.norm())),
x => throw!(*SYM_TYPE_ERROR, "abs expected numeric argument, got {x:#}"),
}
}
#[native_func(1)]
pub fn abs_sq(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, x] = unpack_args!(args);
match x {
Value::Int(x) => Ok(Value::Int(x * x)),
Value::Ratio(x) => Ok(Value::Ratio(x * x)),
Value::Float(x) => Ok(Value::Float(x * x)),
Value::Complex(x) => Ok(Value::Float(x.norm_sqr())),
x => throw!(*SYM_TYPE_ERROR, "abs_sq expected numeric argument, got {x:#}"),
}
}
//
// continuous operations
//
macro_rules! float_func {
($name:ident) => {
#[native_func(1)]
pub fn $name(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, v] = unpack_args!(args);
match to_floaty(v) {
Value::Float(x) => Ok(Value::Float(x.$name())),
Value::Complex(z) => Ok(Value::Complex(z.$name())),
v => throw!(*SYM_TYPE_ERROR,
"{} expected numeric argument, got {v:#}", stringify!($name)),
}
}
};
}
float_func!(sqrt);
float_func!(cbrt);
float_func!(ln);
float_func!(log2);
float_func!(exp);
float_func!(exp2);
float_func!(sin);
float_func!(cos);
float_func!(tan);
float_func!(asin);
float_func!(acos);
float_func!(atan);
float_func!(sinh);
float_func!(cosh);
float_func!(tanh);
float_func!(asinh);
float_func!(acosh);
float_func!(atanh);
#[native_func(2)]
pub fn atan2(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, y, x] = unpack_args!(args);
match (to_floaty(y), to_floaty(x)) {
(Value::Float(y), Value::Float(x))
=> Ok(Value::Float(x.atan2(y))),
(y,x) => throw!(*SYM_TYPE_ERROR,
"atan2 expected real arguments, got {y:#} and {x:#}"),
}
}