最近在项目中遇到一个被nan坑惨的问题,记录一下
最开始的代码如下
/**
* 辛普森积分法
* @param start 积分区间起点
* @param end 积分区间终点
* @param St t时刻的正股股价
* @param K 行权价strike
* @param R 无风险利率
* @param T time to maturity
* @param V σ,正股年度波动率
*/
static func SimpsonIntegrator(start: Double, end: Double, St: Double, R: Double, V: Double, T: Double) -> Double {
let s = max(start, 0.000000001)
let params = (St: St, R: R, V: V, T: T)
if s >= end {
return calculProb(K: end, P: params)
}
let ans = simpson(L: s, R: end, P: params)
let mid = (s + end) / 2.0
let left = simpson(L: s, R: mid, P: params)
let right = simpson(L: mid, R: end, P: params)
if fabs(ans - ( left + right)) < EPS {
return ans
}
else {
let p1 = SimpsonIntegrator(start: s, end: mid, St: St, R: R, V: V, T: T, step: theStep)
let p2 = SimpsonIntegrator(start: mid, end: end, St: St, R: R, V: V, T: T, step: theStep)
return p1 + p2
}
}
是一段很普通的辛普森积分法的函数。从c语言直接翻译过来的。是通过二分法递归调用求积分的近似值。我们测试过后结果正常以后就上线了。但是上线以后部分用户反馈手机发热严重。极少数用户甚至出现了网络返回全无的情况。我们开始排查。最后发现问题出现在了这个方法。
这个方法是在子线程执行的。但是线上有一些不正常的数据会出现0/0或者开负数根号的情况。会在运算过程中出现Double.Nan。当出现nan的参数时
if fabs(ans - ( left + right)) < EPS
这个逃逸条件的判断始终为false。整个递归进入死循环。会始终占据一个子线程而不释放。当CPU除主线程的其他所有物理线程都被这个死循环占满后,网络请求回调的子线程都不会被执行。这是相当严重的问题了。
实际上当出现nan结果时。所有和Nan相关的判断都会始终未false
let num = Double.NaN
if num == num {
print("Num is \(num)")
} else {
print("NaN")
}
// 输出:
// NaN
随后我们修改了这个积分函数
/**
* 辛普森积分法
* @param start 积分区间起点
* @param end 积分区间终点
* @param St t时刻的正股股价
* @param K 行权价strike
* @param R 无风险利率
* @param T time to maturity
* @param V σ,正股年度波动率
* step 默认最多递归2^16层
*/
static func SimpsonIntegrator(start: Double, end: Double, St: Double, R: Double, V: Double, T: Double, step: Int = 16) -> Double {
let s = max(start, 0.000000001)
let params = (St: St, R: R, V: V, T: T)
let theStep = step - 1
if s >= end || theStep <= 0 {
return calculProb(K: end, P: params)
}
let ans = simpson(L: s, R: end, P: params)
let mid = (s + end) / 2.0
let left = simpson(L: s, R: mid, P: params)
let right = simpson(L: mid, R: end, P: params)
if ans.isNaN || left.isNaN || right.isNaN {
return 0
}
if fabs(ans - ( left + right)) < EPS {
return ans
}
else {
let p1 = SimpsonIntegrator(start: s, end: mid, St: St, R: R, V: V, T: T, step: theStep)
let p2 = SimpsonIntegrator(start: mid, end: end, St: St, R: R, V: V, T: T, step: theStep)
return p1 + p2
}
}
把Nan的情况排除掉,同时在递归增加一个层数限制,默认最多递归2^16层。
在进行复制且不熟悉的复杂运算时。一定要把Nan和 infinity的情况计算在内。同时递归算法应该要上一个保险的递归限制。就算计算有问题也不能出现死循环占满线程的情况。不能把错误扩大。
网友评论