code wiki / _hdl_build / _gamma_dd_probe.nx
_gamma_dd_probe.nx source
↩ module page · 127 lines · 5991 B
1// _gamma_dd_probe.nx -- the DD-precision gamma probe (ME2-GAMMA-DD). The plain-f64
2// probe found 13 ulp; the named requirement was double-double lnGamma. This composes
3// the authored kernels' dd primitives (_sh_expdd/_sh_mul2/_sh_2sum/_sh_scale from the
4// sinhcosh module + nx_f64_log) into a dd lnGamma, and re-tests vs bf_gamma_f64. If
5// max_ulp <= 4 the GAMMA_STIRLING template is safe to author at dd; else the gap is
6// refined further. No emitter built on an unvalidated approach (design-intelligence).
7// ln_dd(w) : hi = nx_f64_log(w); lo = (w - exp_dd(hi))/w (Newton correction)
8// lnGamma_dd : (w-0.5)*ln_dd(w) [dd product] - w + 0.5ln2pi + series, kept dd
9// Gamma : exp(lnGamma_hi)*(1+lnGamma_lo) / pull-up-product
10// license_tier: ORIGINAL
11import "nx_syscalls.nx"
12import "nx_f64.nx"
13import "nx_f64_div.nx"
14import "nx_f64_cvt.nx"
15import "nx_bigfloat120.nx"
16import "nx_bigfloat120_div.nx"
17import "nx_bigfloat120_gamma.nx"
18import "_pm_gamma_spec.nx"
19import "_pe_f64log.nx"
20import "_pe_f64sinhcosh.nx"
21func gd_p(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
22func gd_f(fd: i64, s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(fd,s,n); return 0 }
23func gd_n(fd: i64, v: i64) -> i64 { let bb: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m; sys_write(fd,"-" as *u8,1)}; let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=48 as u8;k=1}; while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1}; var i: i64=0; while i<k{bb[i]=t[k-1-i];i=i+1}; sys_write(fd,bb,k); return 0 }
24// exp(v) via the PROVEN authored path (cosh+sinh) -- the plain-f64 probe confirmed this
25// gives correct values; the _sh_expdd reconstruction was the structural bug the first
26// dd run caught (484e9 ulp). Root-cause fix, not a patch: use the verified exp.
27func gd_expf(v: i64) -> i64 { return nx_f64_add(nx_f64_cosh(v), nx_f64_sinh(v)) }
28// ln_dd(w) -> out[0]=hi out[1]=lo (Newton: lo = (w - exp(hi))/w)
29func gd_lndd(w: i64, out: *i64) -> i64 {
30 let hi: i64 = nx_f64_log(w)
31 let eh: i64 = gd_expf(hi)
32 let resid: i64 = nx_f64_sub(w, eh)
33 out[0] = hi
34 out[1] = nx_f64_div(resid, w)
35 return 0
36}
37// Gamma(x) for x>0 via dd Stirling lnGamma + pull-up to w>=8
38func gd_gamma(x: i64, c: *i64) -> i64 {
39 let eight: i64 = c[3]
40 let one: i64 = c[1]
41 let half: i64 = c[1]
42 var w: i64 = x
43 var prod: i64 = c[0]
44 var guard: i64 = 0
45 while guard < 64 {
46 if nx_f64_sub(w, eight) >> 63 == 0 { guard = 64 } else {
47 prod = nx_f64_mul(prod, w)
48 w = nx_f64_add(w, c[0])
49 guard = guard + 1
50 }
51 }
52 // ln_dd(w)
53 let ln: *i64 = sys_mmap(32) as *i64
54 gd_lndd(w, ln)
55 // A = (w-0.5) * ln_dd(w), dd: _sh_mul2(w-0.5, ln_hi) + (w-0.5)*ln_lo into lo
56 let wm: i64 = nx_f64_sub(w, c[1])
57 let mp: *i64 = sys_mmap(32) as *i64
58 _sh_mul2(wm, ln[0], mp) // mp[0]=hi mp[1]=lo of (w-0.5)*ln_hi
59 var a_hi: i64 = mp[0]
60 var a_lo: i64 = nx_f64_add(mp[1], nx_f64_mul(wm, ln[1]))
61 // lnGamma = A - w + 0.5ln2pi + series, accumulate dd via _sh_2sum on the hi chain
62 let acc: *i64 = sys_mmap(32) as *i64
63 _sh_2sum(a_hi, nx_f64_neg(w), acc) // A + (-w)
64 var g_hi: i64 = acc[0]
65 var g_lo: i64 = nx_f64_add(acc[1], a_lo)
66 _sh_2sum(g_hi, c[2], acc) // + 0.5*ln(2pi)
67 g_hi = acc[0]; g_lo = nx_f64_add(g_lo, acc[1])
68 // series sum c_k / w^(2k-1), k=1..8 (c[12]=k1 .. c[5]=k8 hi-first), small -> into lo
69 let winv: i64 = nx_f64_div(c[0], w)
70 let w2inv: i64 = nx_f64_mul(winv, winv)
71 var term: i64 = winv
72 var ser: i64 = nx_f64_mul(c[12], term)
73 var kk: i64 = 2
74 while kk <= 8 {
75 term = nx_f64_mul(term, w2inv)
76 ser = nx_f64_add(ser, nx_f64_mul(c[12 - (kk - 1)], term))
77 kk = kk + 1
78 }
79 // the Stirling series (~0.01 for w=8) is a SIGNIFICANT lnGamma term, NOT a tiny dd
80 // correction -- it belongs in g_hi (the exponent), not g_lo. Folding it into g_lo
81 // and linearizing exp*(1+g_lo) was the ~0.5-abs bug (g_lo must stay ~2^-52).
82 _sh_2sum(g_hi, ser, acc)
83 g_hi = acc[0]; g_lo = nx_f64_add(g_lo, acc[1])
84 // Gamma = exp(g_hi) * (1 + g_lo) / prod (g_lo now genuinely tiny -> linearization ok)
85 let eg: i64 = gd_expf(g_hi)
86 let gw: i64 = nx_f64_add(eg, nx_f64_mul(eg, g_lo))
87 return nx_f64_div(gw, prod)
88}
89func gd_ulp(a: i64, b: i64) -> i64 { if a >= b { return a - b } return b - a }
90func main() -> i64 {
91 gd_p("=== GAMMA DD PROBE: double-double lnGamma vs bf oracle (the named requirement) ===\n" as *u8)
92 let c: *i64 = sys_mmap(8 * 40) as *i64
93 pm_gamma_spec_fill(c)
94 let xs: *i64 = sys_mmap(8 * 16) as *i64
95 xs[0] = 0x4000000000000000
96 xs[1] = 0x4004000000000000
97 xs[2] = 0x4008000000000000
98 xs[3] = 0x4010000000000000
99 xs[4] = 0x4014000000000000
100 xs[5] = 0x401C000000000000
101 xs[6] = 0x4024000000000000
102 var npts: i64 = 7
103 var maxulp: i64 = 0
104 var worst: i64 = 0
105 var i: i64 = 0
106 while i < npts {
107 let want: i64 = bf_gamma_f64(xs[i])
108 let got: i64 = gd_gamma(xs[i], c)
109 let u: i64 = gd_ulp(got, want)
110 if u > maxulp { maxulp = u; worst = xs[i] }
111 i = i + 1
112 }
113 let lfd: i64 = sys_openat_append("knowledge/status/math_engine.log" as *u8, 0x1a4)
114 if lfd >= 0 {
115 gd_f(lfd, "GAMMADDPROBE epoch=" as *u8); gd_n(lfd, sys_now_realtime_sec())
116 gd_f(lfd, " points=" as *u8); gd_n(lfd, npts)
117 gd_f(lfd, " max_ulp=" as *u8); gd_n(lfd, maxulp)
118 gd_f(lfd, " worst_x=" as *u8); gd_n(lfd, worst)
119 if maxulp <= 4 { gd_f(lfd, " verdict=DD-VALIDATED\n" as *u8) } else { gd_f(lfd, " verdict=DD-GAP\n" as *u8) }
120 sys_close(lfd)
121 }
122 gd_p(" max_ulp=" as *u8); gd_n(1, maxulp)
123 if maxulp <= 4 { gd_p(" -- GAMMA DD PROBE: VALIDATED (author GAMMA_STIRLING template at dd)\n" as *u8); sys_exit(0); return 0 }
124 gd_p(" -- GAMMA DD PROBE: still gap (refine dd-ln correction or series)\n" as *u8)
125 sys_exit(1)
126 return 1
127}