Power Query M中List.Generate条件在4-16区间失效问题排查
问题描述
现有一组数据:v值从3到24以0.5为步长(即v = List.Numbers(3, 43, 0.5)),对应p值列表为{89, 178, 328, 522, 758, 1040, 1376, 1771, 2230, 2758, 3351, 3988, 4617, 5166, 5584, 5862, 6028, 6117, 6161, 6183, 6192, 6192, 6192, 6192, 6192, 6192, 6192, 6192, 6192, 6192, 6192, 6192, 6192, 6192, 6192, 6192, 5956, 5832, 5708, 5584, 5460, 5336, 5212}。为实现保留一位小数的插值,编写了Power Query M代码,但运行后出现异常:
- v≤4时插值正常;
- 4≤v<16时,
List.Generate函数生成6个元素(预期为5个),导致出现重复值(例如v=4.5时p值重复,v=4.6时仍为该值); - v≥16后插值恢复正常。
测试调整前置行或p值后问题仍存在,请问该问题的原因是什么?是否与数据类型有关?
附原代码:
let Source = Excel.CurrentWorkbook(){[Name="LK"]}[Content], //Add two columns which are the v and p columns offset by one //It is faster to subtract this way than by adding an Index column offset= Table.FromColumns( Table.ToColumns(Source) & {List.RemoveFirstN(Source[v]) & {null}} & {(List.RemoveFirstN(Source[p])) & {null}}, type table[v=Decimal.Type, p=Decimal.Type, sv=Decimal.Type, sp=Decimal.Type]), // Add a column with a list of the interest rates for each data interpolated between the segments #"p_interpol" = Table.AddColumn(offset, "p_interpol", each let sv_val=[sv], p_increment = ([sp]-[p])/(([sv]-[v])*10), Lists= List.Generate( ()=>[v_val=[v],p_val=[p]], each [v_val]< sv_val, each [v_val=[v_val]+0.1, p_val = [p_val]+p_increment], each [p_val]) in Lists), // add another column with a list of days corresponding to the interest rates #"v_interpol" = Table.AddColumn(#"p_interpol", "v_interpol", each List.Numbers([v], ([sv]-[v])/0.1, 0.1)), //remove the last row as it will have an error remErrRow = Table.RemoveLastN(#"v_interpol",1), //create the new table which has the rates for every duration LK_interpol = Table.FromColumns( {List.Combine(remErrRow[v_interpol]),List.Combine(remErrRow[p_interpol])}, type table[v=Decimal.Type, p=Decimal.Type]) in LK_interpol
异常示例:v=4.5时p值522重复出现,v=4.6时仍显示522。
问题原因与解决方案
原因分析
问题核心是浮点数精度误差,和Decimal数据类型的二进制存储特性直接相关:
- 0.1无法用二进制浮点数精确表示,多次累加后会产生微小误差(例如4.0累加5次0.1后,实际结果可能是
4.499999999999999而非精确的4.5); - 当
v≥4且<16时,sv_val是精确的0.5步长值(比如4.5),而[v_val]因累加误差略小于sv_val,导致List.Generate的终止条件[v_val]<sv_val判定为真,多执行一次生成操作,最终得到6个元素(预期5个); - v≥16后,p值进入平台期(连续多个6192),此时
p_increment=0,即使多生成一个元素,p值也不会变化,所以看起来插值正常,但生成逻辑的问题依然存在。
解决方案
修改List.Generate的终止条件,避免依赖浮点数的精确比较,改为基于预期生成的元素数量控制,或者对数值取整后再比较。以下是两种可行方案:
方案一:基于元素数量控制生成次数
通过计数来限制生成的元素数量,彻底规避浮点数比较误差:
Lists= List.Generate( ()=>[v_val=[v],p_val=[p], count=1], each [count] <= ([sv]-[v])/0.1, each [v_val=[v_val]+0.1, p_val = [p_val]+p_increment, count=[count]+1], each [p_val])
方案二:对数值取整后比较
将[v_val]和sv_val都保留一位小数后再比较,消除精度误差:
Lists= List.Generate( ()=>[v_val=[v],p_val=[p]], each Number.Round([v_val],1) < Number.Round(sv_val,1), each [v_val=[v_val]+0.1, p_val = [p_val]+p_increment], each [p_val])
修改后的完整代码(以方案一为例)
let Source = Excel.CurrentWorkbook(){[Name="LK"]}[Content], offset= Table.FromColumns( Table.ToColumns(Source) & {List.RemoveFirstN(Source[v]) & {null}} & {(List.RemoveFirstN(Source[p])) & {null}}, type table[v=Decimal.Type, p=Decimal.Type, sv=Decimal.Type, sp=Decimal.Type]), #"p_interpol" = Table.AddColumn(offset, "p_interpol", each let sv_val=[sv], p_increment = ([sp]-[p])/(([sv]-[v])*10), expectedCount = ([sv]-[v])/0.1, Lists= List.Generate( ()=>[v_val=[v],p_val=[p], count=1], each [count] <= expectedCount, each [v_val=[v_val]+0.1, p_val = [p_val]+p_increment, count=[count]+1], each [p_val]) in Lists), #"v_interpol" = Table.AddColumn(#"p_interpol", "v_interpol", each List.Numbers([v], ([sv]-[v])/0.1, 0.1)), remErrRow = Table.RemoveLastN(#"v_interpol",1), LK_interpol = Table.FromColumns( {List.Combine(remErrRow[v_interpol]),List.Combine(remErrRow[p_interpol])}, type table[v=Decimal.Type, p=Decimal.Type]) in LK_interpol
内容的提问来源于stack exchange,提问作者Katrin
相关产品推荐
相关产品推荐

