use super::base::{leading_part, trailing_part};
use cubecl_core as cubecl;
use cubecl_core::prelude::*;
const LN2_HI: f32 = leading_part(core::f64::consts::LN_2);
const LN2_LO: f32 = trailing_part(core::f64::consts::LN_2);
const SQRT_2: f32 = core::f32::consts::SQRT_2;
const SUBNORMAL_SCALE: f32 = (1u32 << 24) as f32;
const SUBNORMAL_SHIFT: i32 = 24;
const LOG_0: f32 = 0.3333333;
const LOG_1: f32 = -0.25000304;
const LOG_2: f32 = 0.20001201;
const LOG_3: f32 = -0.16641581;
const LOG_4: f32 = 0.14209945;
const LOG_5: f32 = -0.12989277;
const LOG_6: f32 = 0.12655699;
const LOG_7: f32 = -0.079742186;
#[cube]
pub fn ln<F: Float, N: Size>(x: Vector<F, N>) -> Vector<F, N> {
let x = Vector::<f32, N>::cast_from(x);
let subnormal = x.less_than(&Vector::new(f32::MIN_POSITIVE));
let scaled = select_many(subnormal, x * Vector::new(SUBNORMAL_SCALE), x);
let bits = Vector::<u32, N>::reinterpret(scaled);
let exponent = Vector::<i32, N>::cast_from(bits >> Vector::new(23u32)) - Vector::new(127i32);
let mantissa = Vector::<f32, N>::reinterpret(
(bits & Vector::new(0x007f_ffffu32)) | Vector::new(0x3f80_0000u32),
);
let exponent = select_many(subnormal, exponent - Vector::new(SUBNORMAL_SHIFT), exponent);
let halved = mantissa.greater_than(&Vector::new(SQRT_2));
let mantissa = select_many(halved, mantissa * Vector::new(0.5f32), mantissa);
let exponent = select_many(halved, exponent + Vector::new(1i32), exponent);
let f = mantissa - Vector::new(1.0f32);
let square = f * f;
let quartic = square * square;
let terms_01 = fma(Vector::new(LOG_1), f, Vector::new(LOG_0));
let terms_23 = fma(Vector::new(LOG_3), f, Vector::new(LOG_2));
let terms_45 = fma(Vector::new(LOG_5), f, Vector::new(LOG_4));
let terms_67 = fma(Vector::new(LOG_7), f, Vector::new(LOG_6));
let low = fma(terms_23, square, terms_01);
let high = fma(terms_67, square, terms_45);
let tail = fma(high, quartic, low);
let mantissa_log = fma(square * f, tail, fma(square, Vector::new(-0.5f32), f));
let exponent = Vector::<f32, N>::cast_from(exponent);
let series = fma(
exponent,
Vector::new(LN2_HI),
fma(exponent, Vector::new(LN2_LO), mantissa_log),
);
let zero = Vector::<f32, N>::new(0.0);
let series = select_many(x.greater_than(&zero), series, Vector::new(f32::NAN));
let series = select_many(x.equal(&zero), Vector::new(f32::NEG_INFINITY), series);
let series = select_many(
x.equal(&Vector::new(f32::INFINITY)),
Vector::new(f32::INFINITY),
series,
);
Vector::<F, N>::cast_from(series)
}
#[cfg(test)]
mod tests {
use super::super::base::{evaluate, worst_relative_error};
use super::*;
#[test]
fn the_series_fits_the_logarithm_over_the_mantissa_window() {
let from = (0.5f64).sqrt() - 1.0;
let to = (2.0f64).sqrt() - 1.0;
let worst = worst_relative_error(from, to, f64::ln_1p, |f| {
let tail = evaluate(&[LOG_0, LOG_1, LOG_2, LOG_3, LOG_4, LOG_5, LOG_6, LOG_7], f);
f - f * f / 2.0 + f * f * f * tail
});
assert!(worst < 3e-8, "worst relative error {worst}");
}
}