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))