提取工具函数

This commit is contained in:
SOR
2026-06-21 11:05:42 +08:00
parent c59105ef43
commit 1c48f91be5
13 changed files with 259 additions and 444 deletions
+16
View File
@@ -0,0 +1,16 @@
from utils.formatting import (
get_decimal_places,
scientific_round,
parse_angle_to_minutes,
format_minutes_as_angle
)
from utils.stats import (
calculate_stats,
linear_regression
)
from utils.io import (
input_float,
input_decimal,
input_int,
input_data_group
)
+76
View File
@@ -0,0 +1,76 @@
import math
from decimal import Decimal, ROUND_HALF_EVEN
def get_decimal_places(data_strings):
"""获取输入数据中小数点后的位数,用于自适应展示"""
max_places = 0
for s in data_strings:
if '.' in s:
places = len(s.split('.')[1])
max_places = max(max_places, places)
return max_places
def scientific_round(value, uncertainty):
"""
按照“四舍六入五凑偶”修约:
1. 不确定度 u 保留一位有效数字
2. 平均值/结果末位与 u 对齐
"""
if uncertainty <= 0:
return str(value), "0"
d_val = Decimal(str(value))
d_unc = Decimal(str(uncertainty))
u_float = float(d_unc)
first_digit_pos = math.floor(math.log10(u_float))
prec = Decimal('1e' + str(first_digit_pos))
u_final = d_unc.quantize(prec, rounding=ROUND_HALF_EVEN)
val_final = d_val.quantize(prec, rounding=ROUND_HALF_EVEN)
return val_final, u_final
def parse_angle_to_minutes(s):
"""
将输入解析为内部统一单位:分钟(Decimal)。
支持格式:
1. 'D.M' -> D度M分 (例如 '120.15' -> 120*60 + 15 = 7215分)
2. 'D' (纯数字无点) -> D度 (例如 '120' -> 120*60 = 7200分)
3. 'D.M.S' -> D度M分S秒 (例如 '120.15.30' -> 7215.5分)
"""
parts = s.split('.')
if len(parts) == 1:
return Decimal(parts[0]) * 60
elif len(parts) == 2:
return Decimal(parts[0]) * 60 + Decimal(parts[1])
else:
res = Decimal(parts[0]) * 60 + Decimal(parts[1])
if len(parts) >= 3:
res += Decimal(parts[2]) / 60
return res
def format_minutes_as_angle(minutes, prec=None):
"""
将总分钟数转换为 D° M' 格式,支持保持对齐精度。
"""
neg = minutes < 0
minutes = abs(minutes)
d = int(minutes // 60)
m = minutes % 60
if m >= 60:
d += 1
m -= 60
if prec is not None and prec < 1:
places = abs(prec.as_tuple().exponent)
m_str = format(m, f'.{places}f')
else:
if m == int(m):
m_str = str(int(m))
else:
m_str = format(m, 'f').rstrip('0').rstrip('.')
return f"{'-' if neg else ''}{d}°{m_str}'"
+59
View File
@@ -0,0 +1,59 @@
from decimal import Decimal
def input_float(prompt, default=None):
"""输入一个浮点数值,支持默认值"""
while True:
try:
suffix = f" (默认 {default})" if default is not None else ""
val = input(f"{prompt}{suffix}: ").strip()
if not val and default is not None:
return float(default)
if not val:
continue
return float(val)
except Exception:
print(" 输入无效,请输入数字。")
def input_decimal(prompt, default=None):
"""输入一个 Decimal 值,支持默认值"""
while True:
try:
suffix = f" (默认 {default})" if default is not None else ""
val = input(f"{prompt}{suffix}: ").strip()
if not val and default is not None:
return Decimal(str(default))
if not val:
continue
return Decimal(val)
except Exception:
print(" 输入无效,请输入数字。")
def input_int(prompt, default=None):
"""输入一个整数,支持默认值"""
while True:
try:
suffix = f" (默认 {default})" if default is not None else ""
val = input(f"{prompt}{suffix}: ").strip()
if not val and default is not None:
return default
if not val:
continue
return int(val)
except Exception:
print(" 输入无效,请输入整数。")
def input_data_group(name, count, unit=""):
"""输入一组重复测量的数值"""
prompt_unit = f" (单位: {unit})" if unit else ""
print(f"\n请输入 {name} 的 {count} 次测量值{prompt_unit}:")
data = []
for i in range(count):
while True:
try:
val = input(f"第 {i+1} 次 = ").strip()
if not val: continue
data.append(Decimal(val))
break
except Exception:
print("输入无效,请输入数字。")
return data
+59
View File
@@ -0,0 +1,59 @@
import math
from decimal import Decimal
def calculate_stats(data_list, delta_inst):
"""
计算一组数据的平均值、A类、B类及合成不确定度。
支持输入 list of Decimal 或 list of float/str/int。
返回: (mean, u_combined, u_a, u_b, s)
"""
n = len(data_list)
decimal_data = [Decimal(str(x)) for x in data_list]
mean = sum(decimal_data) / Decimal(n)
if n > 1:
variance = sum((x - mean)**2 for x in decimal_data) / Decimal(n - 1)
s = Decimal(str(math.sqrt(float(variance))))
u_a = s / Decimal(str(math.sqrt(n)))
else:
s = Decimal("0")
u_a = Decimal("0")
u_b = Decimal(str(delta_inst)) / Decimal(str(math.sqrt(3)))
u_combined = Decimal(str(math.sqrt(float(u_a**2 + u_b**2))))
return mean, u_combined, u_a, u_b, s
def linear_regression(x_list, y_list):
"""
最小二乘法线性回归: y = k·x + b
返回: (k, b, r, u_k)
u_k = |k| · √[(1/r² − 1) / (n − 2)]
支持 input numbers as Decimal or float.
"""
n = len(x_list)
x_dec = [Decimal(str(x)) for x in x_list]
y_dec = [Decimal(str(y)) for y in y_list]
x_mean = sum(x_dec) / Decimal(n)
y_mean = sum(y_dec) / Decimal(n)
L_xx = sum((x - x_mean)**2 for x in x_dec)
L_yy = sum((y - y_mean)**2 for y in y_dec)
L_xy = sum((x_dec[i] - x_mean) * (y_dec[i] - y_mean) for i in range(n))
k = L_xy / L_xx if L_xx != 0 else Decimal("0")
b = y_mean - k * x_mean
denom = Decimal(str(math.sqrt(float(L_xx * L_yy)))) if L_xx * L_yy > 0 else Decimal("1")
r = L_xy / denom
r_float = float(r)
if abs(r_float) < 1.0 and n > 2:
u_k_float = float(abs(k)) * math.sqrt((1.0 / (r_float**2) - 1.0) / (n - 2))
else:
u_k_float = 0.0
u_k = Decimal(str(u_k_float))
return k, b, r, u_k