code wiki / (root) / nx_f32_log.nx

nx_f32_log.nx source

↩ module page · 110 lines · 4151 B

1// nx_f32_log.nx -- IEEE 754 binary32 natural-log log(x) bits-up. 2// 3// L6 of the bits-up numeric tower. Composes L4 mul/add/sub/div/cvt 4// to produce real-valued log(x) without libm. 5// 6// Algorithm (clean-room from cited refs): 7// 8// 1. Special cases per IEEE 754: 9// log(NaN) = NaN 10// log(+0) = -inf 11// log(-0) = -inf 12// log(neg) = NaN 13// log(+inf) = +inf 14// log(1.0) = +0 (exact) 15// 16// 2. Range reduction: x = 2^k * m, m in [1, 2). 17// k = f32_exp_field(x) - 127 18// m = same bits with exp field set to 127 (interpret as 1.frac) 19// Then log(x) = k * ln(2) + log(m). 20// 21// 3. Polynomial for log(m) on m in [1, 2): 22// u = (m - 1) / (m + 1), u in [0, 1/3] 23// log(m) = 2 * atanh(u) 24// ~= 2 * u * (1 + u^2/3 + u^4/5 + u^6/7 + u^8/9) 25// The atanh form converges rapidly on this range (much better 26// than direct Taylor of log(1+t) which is slow near t=1). 27// 28// 4. Combine: log(x) = k * ln(2) + 2*u*P(u^2) 29// where P(u^2) = 1 + u^2/3 + u^4/5 + u^6/7 + u^8/9 (Horner). 30// 31// References absorbed clean-room (no code borrowed): 32// Hart 1968, "Computer Approximations" 33// Cody+Waite 1980, "Software Manual for Elementary Functions" 34// Muller 2016, "Elementary Functions" 35// 36// v1 accuracy: ~50-500 ULPs on f32 (atanh polynomial truncation + 37// accumulator rounding). Adequate for ML loss + entropy math; 38// v2 Remez tightening for sub-100 ULPs. 39// 40// genealogy_id: standard_log_range_reduction + atanh_form_polynomial 41// lineage_id: substrate_f32_log_v1_atanh 42 43import "nx_syscalls.nx" 44import "nx_tier.nx" 45import "nx_f32.nx" 46import "nx_f32_div.nx" 47import "nx_f32_cvt.nx" 48 49// Constants as f32 bit patterns. 50const NX_F32_LOG_ONE: i64 = 0x3F800000 // 1.0 51const NX_F32_LOG_TWO: i64 = 0x40000000 // 2.0 52const NX_F32_LOG_LN2: i64 = 0x3F317218 // ln(2) = 0.6931472 53const NX_F32_LOG_NEG_INF: i64 = 0xFF800000 // -inf 54// Polynomial coefficients (rational 1/N for atanh series). 55const NX_F32_LOG_INV_3: i64 = 0x3EAAAAAB // 1/3 56const NX_F32_LOG_INV_5: i64 = 0x3E4CCCCD // 1/5 57const NX_F32_LOG_INV_7: i64 = 0x3E124925 // 1/7 ~= 0.14285715 58const NX_F32_LOG_INV_9: i64 = 0x3DE38E39 // 1/9 ~= 0.11111111 59 60func nx_f32_log(x: i64) -> i64 { 61 let cls: nx_int = nx_f32_classify(x) 62 63 if cls == NX_F32_CLS_NAN { return NX_F32_NAN_RAW } 64 if cls == NX_F32_CLS_ZERO { return NX_F32_LOG_NEG_INF } 65 66 // Negative finite or -inf -> NaN. 67 if nx_f32_sign(x) == 1 { return NX_F32_NAN_RAW } 68 69 // +inf 70 if cls == NX_F32_CLS_INF { return 0x7F800000 } 71 72 // Exact log(1.0) = 0 73 if x == NX_F32_LOG_ONE { return 0 } 74 75 // Subnormal v1: conservative, return -inf. 76 if cls == NX_F32_CLS_SUBNORMAL { return NX_F32_LOG_NEG_INF } 77 78 // Range reduction: x = 2^k * m, m in [1, 2). 79 let exp_field: i64 = nx_f32_exp_field(x) 80 let k_int: i64 = exp_field - NX_F32_EXP_BIAS 81 let mant: i64 = nx_f32_mant_field(x) 82 // m_raw: same mantissa, exp_field = 127 (= 1.frac form) 83 let m_raw: i64 = (mant) | (NX_F32_EXP_BIAS << NX_F32_EXP_SHIFT) 84 85 // u = (m - 1) / (m + 1). 86 let m_minus_1: i64 = nx_f32_sub(m_raw, NX_F32_LOG_ONE) 87 let m_plus_1: i64 = nx_f32_add(m_raw, NX_F32_LOG_ONE) 88 let u: i64 = nx_f32_div(m_minus_1, m_plus_1) 89 let u2: i64 = nx_f32_mul(u, u) 90 91 // P(u^2) = 1 + u^2/3 + u^4/5 + u^6/7 + u^8/9 via Horner: 92 // = 1 + u^2 * (1/3 + u^2 * (1/5 + u^2 * (1/7 + u^2 * 1/9))) 93 let t1: i64 = nx_f32_mul(u2, NX_F32_LOG_INV_9) 94 let t2: i64 = nx_f32_add(NX_F32_LOG_INV_7, t1) 95 let t3: i64 = nx_f32_mul(u2, t2) 96 let t4: i64 = nx_f32_add(NX_F32_LOG_INV_5, t3) 97 let t5: i64 = nx_f32_mul(u2, t4) 98 let t6: i64 = nx_f32_add(NX_F32_LOG_INV_3, t5) 99 let t7: i64 = nx_f32_mul(u2, t6) 100 let P: i64 = nx_f32_add(NX_F32_LOG_ONE, t7) 101 102 // log(m) = 2 * u * P 103 let two_u: i64 = nx_f32_mul(NX_F32_LOG_TWO, u) 104 let log_m: i64 = nx_f32_mul(two_u, P) 105 106 // log(x) = k * ln(2) + log(m) 107 let k_f32: i64 = nx_i32_to_f32(k_int) 108 let k_ln2: i64 = nx_f32_mul(k_f32, NX_F32_LOG_LN2) 109 return nx_f32_add(k_ln2, log_m) 110}