Files
ArcGis_Py/tools/core/utils/math_utils.py
2026-04-22 12:27:49 +08:00

147 lines
5.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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))