Source code for algolib.numerics.constants

# src/algolib/numerics/constants.py
"""
Centralized numerical constants for algolib.

- No imports from `math` or third-party libs: *pure Python floats only*.
- Values are given either as exact decimal, or with comments showing hex-float
  for traceability.
- Includes Cody–Waite style splits for stable range-reduction in exp/sin/cos.
"""

from algolib.exceptions import NumericOverflowError

# -----------------------------
# IEEE-754 double "machine" info
# -----------------------------
# Python float is IEEE-754 binary64 on CPython.
DBL_MANT_DIG      = 53                    # significand bits
DBL_EPS           = 2.0 ** -52            # ~= 2.220446049250313e-16 (unit roundoff)
DBL_MIN_EXP       = -1022                 # min exponent for normal numbers
DBL_MAX_EXP       = 1024                  # max exponent (exclusive bound for 2^e)
DBL_MIN           = 2.0 ** -1022          # ~= 2.2250738585072014e-308 (min normal)
DBL_DENORM_MIN    = 2.0 ** -1074          # ~= 5e-324 (min subnormal)
DBL_MAX           = (2.0 - 2.0 ** -52) * (2.0 ** 1023)  # ~= 1.7976931348623157e+308

# --------------------------------
# Fundamental mathematical constants
# --------------------------------
PI   = 3.1415926535897932384626433832795028841971693993751058209749445923078164062862090
PI_2 = 1.5707963267948966192313216916397514420985846996875529104874722961539082031431045   # π/2
PI_4 = 0.78539816339744830961566084581987572104929234984377645524373614807695410157155225   # π/4
TAU  = 6.2831853071795864769252867665590057683943387987502116419498891846156328125724180   # 2π
INV_PI     = 3.1830988618379067153776752674502872406891929148091289749533468811779359526845307e-1       # 1/π
INV_PI_2   = 6.3661977236758134307553505349005744813783858296182579499066937623558719053690614e-1       # 2/π
E          = 2.7182818284590452353602874713526624977572470936999595749669676277240766303535476
LN2        = 6.9314718055994530941723212145817656807550013436025525412068000949339362196969472e-1
INV_LN2    = 1.4426950408889634073599246810018921374266459541529859341354494069311092191811851       # 1/ln 2
LOG2E      = INV_LN2                                                     # alias
LN10       = 2.3025850929940456840179914546843642076011014886287729760333279009675726096773525
INV_LN10   = 4.3429448190325182765112891891660508229439700580366656611445378316586464920887077e-1
SQRT2      = 1.4142135623730950488016887242096980785696718753769480731766797379907324784621070
INV_SQRT2  = 7.0710678118654752440084436210484903928483593768847403658833986899536623923105352e-1

# ----------------------------------------------------
# Cody–Waite splits for stable range reduction / exp()
# ----------------------------------------------------
# ln(2) = LN2_HI + LN2_LO (sum exactly equals LN2 in double)
LN2_HI = 0.6931471803691238
LN2_LO = 1.9082149292705877e-10

# π/2 triple split for high-accuracy range reduction
# Values compatible with fdlibm-style splits
PI2_HI  = 1.5707963267948966
PI2_MID = 1.9231321691639753e-17       # 0x3c91a62633145c07
PI2_LO  = -1.5579014153003124e-33      # 0x3b2a19c036e2eb4f

# 2/π double split for accurate k = round(x*(2/π))
# High part truncated to ~28 bits precision; low part is the residual.
INV_PI_2_HI = 0.6366197723675814          # high part of 2/π
INV_PI_2_LO = -5.692446494650995e-17

# Optional: π split (rarely needed if you reduce by π/2)
PI_HI = 3.141592653589793
PI_LO = 1.2246467991473532e-16

# -----------------------------------
# Default, *general* numerical tolerances
# -----------------------------------
# These are library-wide suggestions; tests may override.
REL_EPS_DEFAULT = 2e-12
ABS_EPS_DEFAULT = 1e-12

# ------------------------------------------------
# Small helpers (no math.*; keep them dependency-free)
# ------------------------------------------------
[docs] def pow2_int(k: int) -> float: """Compute 2**k using only multiplies (supports negative k) with IEEE754-style bounds. Behavior: - k > 1023 -> raise NumericOverflowError (subclass of OverflowError) - k < -1074 -> 0.0 (underflow to zero) - -1074 <= k <= -1023 -> exact subnormal via halving from DBL_MIN - otherwise -> exponentiation-by-squaring (<= 1023 以及 >= -1022) """ if k == 0: return 1.0 if k > 1023: # mirror builtin overflow, 但用库内异常(是 OverflowError 的子类) raise NumericOverflowError("Result too large") if k < -1074: # underflow to zero return 0.0 if k >= DBL_MIN_EXP: # 正规区间(包含 -1022) if k > 0: y = 1.0 base = 2.0 n = k while n: if (n & 1) != 0: y *= base base *= base n >>= 1 return y else: # -1022 <= k <= -1:用 0.5 的平方幂,稳定且不会一路掉成 0 y = 1.0 base = 0.5 # 2**-1 n = -k while n: if (n & 1) != 0: y *= base base *= base n >>= 1 return y # 次正规:-1074 <= k <= -1023 # 思路:从最小正规 DBL_MIN = 2**-1022 开始,做 (-(k+1022)) 次“乘 0.5”。 # 这里最多 52 次,能得到精确的 subnormal。 steps = -(k + 1022) # 介于 1..52 y = DBL_MIN for _ in range(steps): y *= 0.5 # k = -1074 时 y == DBL_DENORM_MIN(5e-324) return y
[docs] def copysign1(x: float, y: float) -> float: r"""Return :math:\\abs{x} with the sign of y. No math module, handles ±0.0 and NaN.""" # y 是 NaN:按约定返回 x 本身(测试也是这么期望的) if y != y: # NaN 自不等 return x # 取 |x| ax = x if x >= 0.0 else -x # y == 0.0 时,用十六进制表示判断符号位 if y == 0.0: neg = float.hex(y).startswith("-") return -ax if neg else ax # 其余情况:比较即可 return ax if y > 0.0 else -ax