初始化
This commit is contained in:
147
tools/core/utils/math_utils.py
Normal file
147
tools/core/utils/math_utils.py
Normal file
@@ -0,0 +1,147 @@
|
||||
from typing import List, Union
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
# 解决百分比相加不为100%
|
||||
def fix_percentages(values: List[float], total: float) -> List[float]:
|
||||
"""
|
||||
修正百分比相加不为100%的问题。
|
||||
|
||||
Args:
|
||||
values (list[float]): 百分比列表,元素个数与总和相同
|
||||
total (float): 总和
|
||||
|
||||
Returns:
|
||||
list[float]: 修正后的百分比列表
|
||||
|
||||
Examples:
|
||||
>>> values = [0.2, 0.3, 0.5]
|
||||
>>> total = 1
|
||||
>>> fix_percentages(values, total)
|
||||
[20.0, 30.0, 50.0]
|
||||
|
||||
>>> values = [0.2, 0.3, 0.5]
|
||||
>>> total = 0.8
|
||||
>>> fix_percentages(values, total)
|
||||
[25.0, 37.5, 62.5]
|
||||
"""
|
||||
exact = [v / total * 100 for v in values]
|
||||
floor = [np.floor(p * 100) / 100 for p in exact] # 向下取整到2位小数
|
||||
remainders = [exact[i] - floor[i] for i in range(len(exact))]
|
||||
|
||||
# 需要分配的百分点数(以0.01%为单位)
|
||||
to_distribute = int(round(10000 - sum(floor) * 100))
|
||||
|
||||
# 按余数大小分配
|
||||
indices = sorted(range(len(remainders)), key=lambda i: remainders[i], reverse=True)
|
||||
|
||||
fixed = floor.copy()
|
||||
for i in range(to_distribute):
|
||||
fixed[indices[i]] += 0.01
|
||||
|
||||
return [round(p, 2) for p in fixed]
|
||||
|
||||
|
||||
# === 误差矫正 ===
|
||||
def correct_rounding_error(target_total:Union[int,float], adjusted_areas:List[float], original_areas:List[float]) -> List[int]:
|
||||
"""
|
||||
健壮的数值舍入误差矫正函数:将浮点型面积值四舍五入后,调整至目标总和。
|
||||
核心逻辑:基于原始数值的小数部分优先级,逐次增减1来抵消舍入误差,确保最终总和匹配目标值,
|
||||
同时避免调整后数值出现负数,防止无限循环。
|
||||
|
||||
Args:
|
||||
target_total (int/float): 目标总和(最终舍入后数值的合计值),函数内部会转为整型
|
||||
adjusted_areas (list[float]): 经过比例调整后的浮点型面积列表(待舍入的原始数据)
|
||||
original_areas (list[float]): 调整前的原始浮点型面积列表(用于计算小数部分优先级)
|
||||
|
||||
Returns:
|
||||
list[int]: 矫正后的整型面积列表,总和尽可能接近/等于target_total;
|
||||
若无法完全矫正,返回尽可能接近的结果并打印警告
|
||||
|
||||
Raises:
|
||||
无显式抛出异常,所有异常会被捕获并打印错误信息,返回保底的四舍五入结果
|
||||
|
||||
Notes:
|
||||
1. 误差矫正规则:
|
||||
- 误差>0(当前总和 < 目标总和):优先给小数部分大的数值+1
|
||||
- 误差<0(当前总和 > 目标总和):优先给小数部分小的数值-1
|
||||
2. 边界限制:调整时确保数值≥0(避免出现负数面积)
|
||||
3. 防无限循环:最大迭代次数为 len(adjusted_areas) * 10,超出则终止并提示剩余误差
|
||||
|
||||
Examples:
|
||||
>>> target = 10
|
||||
>>> adjusted = [3.2, 2.8, 4.1] # 四舍五入后总和=3+3+4=10,无误差
|
||||
>>> original = [3.2, 2.8, 4.1]
|
||||
>>> correct_rounding_error(target, adjusted, original)
|
||||
[3, 3, 4]
|
||||
|
||||
>>> target = 10
|
||||
>>> adjusted = [3.1, 2.1, 4.1] # 四舍五入后总和=3+2+4=9,误差+1
|
||||
>>> original = [3.1, 2.1, 4.1]
|
||||
>>> correct_rounding_error(target, adjusted, original)
|
||||
[3, 2, 5] # 优先给小数部分最大的4.1+1
|
||||
|
||||
>>> target = 8
|
||||
>>> adjusted = [3.9, 2.9, 1.9] # 四舍五入后总和=4+3+2=9,误差-1
|
||||
>>> original = [3.9, 2.9, 1.9]
|
||||
>>> correct_rounding_error(target, adjusted, original)
|
||||
[3, 3, 2] # 优先给小数部分最小的1.9-1(实际小数1.9>2.9>3.9,故调整3.9)
|
||||
"""
|
||||
try:
|
||||
target_total = int(target_total)
|
||||
rounded_areas = [int(round(area)) for area in adjusted_areas]
|
||||
current_total = sum(rounded_areas)
|
||||
error = target_total - current_total
|
||||
|
||||
if error == 0 or len(adjusted_areas) == 0:
|
||||
return rounded_areas
|
||||
|
||||
# 使用循环分配直到误差为0或无法再分配
|
||||
remaining_error = error
|
||||
max_iterations = len(adjusted_areas) * 10 # 防止无限循环
|
||||
|
||||
for _ in range(max_iterations):
|
||||
if remaining_error == 0:
|
||||
break
|
||||
|
||||
# 每次迭代重新计算小数部分和排序
|
||||
decimal_parts = [float(area - int(area)) for area in original_areas]
|
||||
indices = list(range(len(adjusted_areas)))
|
||||
|
||||
if remaining_error > 0:
|
||||
indices.sort(key=lambda i: decimal_parts[i], reverse=True)
|
||||
adjustment = 1
|
||||
else:
|
||||
indices.sort(key=lambda i: decimal_parts[i])
|
||||
adjustment = -1
|
||||
|
||||
# 尝试分配一次调整
|
||||
adjusted = False
|
||||
for idx in indices:
|
||||
if (adjustment == 1 and rounded_areas[idx] >= 0) or (adjustment == -1 and rounded_areas[idx] > 0):
|
||||
rounded_areas[idx] += adjustment
|
||||
remaining_error -= adjustment
|
||||
adjusted = True
|
||||
break
|
||||
|
||||
if not adjusted: # 无法再调整
|
||||
break
|
||||
|
||||
if remaining_error != 0:
|
||||
print(f"警告:无法完全矫正误差,剩余: {remaining_error}")
|
||||
|
||||
return rounded_areas
|
||||
|
||||
except Exception as e:
|
||||
print(f"误差矫正出错: {e}")
|
||||
# 返回原始四舍五入结果作为保底
|
||||
return [int(round(area)) for area in adjusted_areas]
|
||||
|
||||
if __name__ == '__main__':
|
||||
target = 10
|
||||
adjusted = [3.3, 3.9, 4.2] # 四舍五入后总和=3+3+4=10,无误差
|
||||
original = [3.25, 2.85, 4.15]
|
||||
print(correct_rounding_error(target, adjusted, original))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user