Coverage for src/algolib/numerics/constants.py: 100%
73 statements
« prev ^ index » next coverage.py v7.10.4, created at 2025-08-20 19:37 +0000
« prev ^ index » next coverage.py v7.10.4, created at 2025-08-20 19:37 +0000
1# src/algolib/numerics/constants.py
2"""
3Centralized numerical constants for algolib.
5- No imports from `math` or third-party libs: *pure Python floats only*.
6- Values are given either as exact decimal, or with comments showing hex-float
7 for traceability.
8- Includes Cody–Waite style splits for stable range-reduction in exp/sin/cos.
9"""
11from algolib.exceptions import NumericOverflowError
13# -----------------------------
14# IEEE-754 double "machine" info
15# -----------------------------
16# Python float is IEEE-754 binary64 on CPython.
17DBL_MANT_DIG = 53 # significand bits
18DBL_EPS = 2.0 ** -52 # ~= 2.220446049250313e-16 (unit roundoff)
19DBL_MIN_EXP = -1022 # min exponent for normal numbers
20DBL_MAX_EXP = 1024 # max exponent (exclusive bound for 2^e)
21DBL_MIN = 2.0 ** -1022 # ~= 2.2250738585072014e-308 (min normal)
22DBL_DENORM_MIN = 2.0 ** -1074 # ~= 5e-324 (min subnormal)
23DBL_MAX = (2.0 - 2.0 ** -52) * (2.0 ** 1023) # ~= 1.7976931348623157e+308
25# --------------------------------
26# Fundamental mathematical constants
27# --------------------------------
28PI = 3.1415926535897932384626433832795028841971693993751058209749445923078164062862090
29PI_2 = 1.5707963267948966192313216916397514420985846996875529104874722961539082031431045 # π/2
30PI_4 = 0.78539816339744830961566084581987572104929234984377645524373614807695410157155225 # π/4
31TAU = 6.2831853071795864769252867665590057683943387987502116419498891846156328125724180 # 2π
32INV_PI = 3.1830988618379067153776752674502872406891929148091289749533468811779359526845307e-1 # 1/π
33INV_PI_2 = 6.3661977236758134307553505349005744813783858296182579499066937623558719053690614e-1 # 2/π
34E = 2.7182818284590452353602874713526624977572470936999595749669676277240766303535476
35LN2 = 6.9314718055994530941723212145817656807550013436025525412068000949339362196969472e-1
36INV_LN2 = 1.4426950408889634073599246810018921374266459541529859341354494069311092191811851 # 1/ln 2
37LOG2E = INV_LN2 # alias
38LN10 = 2.3025850929940456840179914546843642076011014886287729760333279009675726096773525
39INV_LN10 = 4.3429448190325182765112891891660508229439700580366656611445378316586464920887077e-1
40SQRT2 = 1.4142135623730950488016887242096980785696718753769480731766797379907324784621070
41INV_SQRT2 = 7.0710678118654752440084436210484903928483593768847403658833986899536623923105352e-1
43# ----------------------------------------------------
44# Cody–Waite splits for stable range reduction / exp()
45# ----------------------------------------------------
46# ln(2) = LN2_HI + LN2_LO (sum exactly equals LN2 in double)
47LN2_HI = 0.6931471803691238
48LN2_LO = 1.9082149292705877e-10
50# π/2 triple split for high-accuracy range reduction
51# Values compatible with fdlibm-style splits
52PI2_HI = 1.5707963267948966
53PI2_MID = 1.9231321691639753e-17 # 0x3c91a62633145c07
54PI2_LO = -1.5579014153003124e-33 # 0x3b2a19c036e2eb4f
56# 2/π double split for accurate k = round(x*(2/π))
57# High part truncated to ~28 bits precision; low part is the residual.
58INV_PI_2_HI = 0.6366197723675814 # high part of 2/π
59INV_PI_2_LO = -5.692446494650995e-17
61# Optional: π split (rarely needed if you reduce by π/2)
62PI_HI = 3.141592653589793
63PI_LO = 1.2246467991473532e-16
65# -----------------------------------
66# Default, *general* numerical tolerances
67# -----------------------------------
68# These are library-wide suggestions; tests may override.
69REL_EPS_DEFAULT = 2e-12
70ABS_EPS_DEFAULT = 1e-12
72# ------------------------------------------------
73# Small helpers (no math.*; keep them dependency-free)
74# ------------------------------------------------
75def pow2_int(k: int) -> float:
76 """Compute 2**k using only multiplies (supports negative k) with IEEE754-style bounds.
78 Behavior:
79 - k > 1023 -> raise NumericOverflowError (subclass of OverflowError)
80 - k < -1074 -> 0.0 (underflow to zero)
81 - -1074 <= k <= -1023 -> exact subnormal via halving from DBL_MIN
82 - otherwise -> exponentiation-by-squaring (<= 1023 以及 >= -1022)
83 """
84 if k == 0:
85 return 1.0
86 if k > 1023:
87 # mirror builtin overflow, 但用库内异常(是 OverflowError 的子类)
88 raise NumericOverflowError("Result too large")
89 if k < -1074:
90 # underflow to zero
91 return 0.0
93 if k >= DBL_MIN_EXP: # 正规区间(包含 -1022)
94 if k > 0:
95 y = 1.0
96 base = 2.0
97 n = k
98 while n:
99 if (n & 1) != 0:
100 y *= base
101 base *= base
102 n >>= 1
103 return y
104 else:
105 # -1022 <= k <= -1:用 0.5 的平方幂,稳定且不会一路掉成 0
106 y = 1.0
107 base = 0.5 # 2**-1
108 n = -k
109 while n:
110 if (n & 1) != 0:
111 y *= base
112 base *= base
113 n >>= 1
114 return y
116 # 次正规:-1074 <= k <= -1023
117 # 思路:从最小正规 DBL_MIN = 2**-1022 开始,做 (-(k+1022)) 次“乘 0.5”。
118 # 这里最多 52 次,能得到精确的 subnormal。
119 steps = -(k + 1022) # 介于 1..52
120 y = DBL_MIN
121 for _ in range(steps):
122 y *= 0.5
123 # k = -1074 时 y == DBL_DENORM_MIN(5e-324)
124 return y
126def copysign1(x: float, y: float) -> float:
127 r"""Return :math:\\abs{x} with the sign of y. No math module, handles ±0.0 and NaN."""
128 # y 是 NaN:按约定返回 x 本身(测试也是这么期望的)
129 if y != y: # NaN 自不等
130 return x
132 # 取 |x|
133 ax = x if x >= 0.0 else -x
135 # y == 0.0 时,用十六进制表示判断符号位
136 if y == 0.0:
137 neg = float.hex(y).startswith("-")
138 return -ax if neg else ax
140 # 其余情况:比较即可
141 return ax if y > 0.0 else -ax