确定角度是否接近零 +/- 2?

Determine if angle is close to zero by +/- 2?

在我的应用中,我试图提供一个指示,表明用户的航向在从零开始的合理范围内。如果超过 360 度,角度将重置为零。这就是我正在做的:

let angle1 = Angle(degree: 359)
let angle2 = Angle(degree: 2)

angle1.degrees > 358 || angle1.degrees < 2
angle2.degrees > 358 || angle2.degrees < 2

是否有内置方法或更好的方法来对此进行测试?另外,会不会有CoreLocation航向大于360或负数的情况?

您可以尝试以下代码来实现您的要求。

    var angle = Angle(degrees: 359.0) // Angle(degrees: 2.0)
    // between 358 and 360 inclusive or between 0 and +\- 2 inclusive
    if angle.degrees >= 358 && angle.degrees <= 360 ||
        angle.degrees >= 0 && angle.degrees <= 2 ||
        angle.degrees >= -2 && angle.degrees <= 0 {
        print("Yes, user is heading within a reasonable range from zero")
    }

关于 CoreLocation course (标题),根据文档, https://developer.apple.com/documentation/corelocation/cllocation/1423832-course

"...负值表示课程信息无效..."

could there be a scenario where the CoreLocation heading is larger

我不确定,但是很多 angle-related 数学运算可能会使您的值超出 0..<360 范围。要解决这个问题,您可以添加如下内容:

extension Angle {
    // Return a normalized copy of this angle that's guaranteed to be within +0 ..< +360
    var normalized: Angle {
        let potentiallyNegativeAngle = degrees.truncatingRemainder(dividingBy: 360.0)
        let positiveInRangeAngle = (potentiallyNegativeAngle + 360).truncatingRemainder(dividingBy: 360.0)
        return Angle(degrees: positiveInRangeAngle) }
}

Is there a built in method or better way to test for this?

不,但是自己编写会很有趣。以下是我的做法:

// It astounds me that these basic operators aren't already built-in
extension Angle {
    static func + (minuend: Angle, subtrahend: Angle) -> Angle {
        Angle(radians: minuend.radians + subtrahend.radians)
    }
    
    static func - (minuend: Angle, subtrahend: Angle) -> Angle {
        Angle(radians: minuend.radians - subtrahend.radians)
    }
}

extension Angle {
    // There's probably some clever way to do this without branching,
    // and purely with modular arithmetic, but I couldn't figure it out
    func isWithin(_ delta: Angle, of target: Angle) -> Bool {
        return self.normalized > (target - delta).normalized ||
            self.normalized < (target + delta).normalized
    }
    
    func isCloseToZero(delta: Angle = Angle(degrees: 2.0)) -> Bool {
        isWithin(delta, of: Angle(degrees: 0))
    }
}

以下是一些测试用例:

print(" -340.0: ", Angle(degrees: -340.0).isCloseToZero()) // False
print(" -358.1: ", Angle(degrees: -358.1).isCloseToZero())
print("   -5.0: ", Angle(degrees:   -5.0).isCloseToZero()) // False
print("   -1.9: ", Angle(degrees:   -1.9).isCloseToZero())
print("    0.0: ", Angle(degrees:    0.0).isCloseToZero())
print("   +1.9: ", Angle(degrees:   +1.9).isCloseToZero())
print("   +5.0: ", Angle(degrees:   +5.0).isCloseToZero()) // False
print(" +358.1: ", Angle(degrees: +358.1).isCloseToZero())
print(" +360.0: ", Angle(degrees: +360.0).isCloseToZero())
print(" +365.0: ", Angle(degrees: +365.0).isCloseToZero()) // False

这是一个更高级的变体,它完全 branch-free 使用了一些巧妙的模块化算法:

extension Angle {
    // Returns the distance between `self` and `target`, in the range `-180..<180` degrees
    func distance(to target: Angle) -> Angle {
        let rawDistance = (self - target).radians
        let normalizedDistance = .pi - abs(abs(rawDistance) - .pi)
        return Angle(radians: normalizedDistance)
    }
    
    func isWithin(_ delta: Angle, of target: Angle) -> Bool {
        let normalizedDelta = delta.normalized
        precondition(normalizedDelta.radians <= .pi,
            """
            `isWithin(_:of:)` always find the shortest distance between the two angles,
            so the delta has to be congruent to an angle between 0 and 180 degrees!
            It was \(delta), which normalized to: \(normalizedDelta)
            """
        )
        return abs(self.distance(to: target).radians) <= normalizedDelta.radians
    }
    
    func isCloseToZero(delta: Angle = Angle(degrees: 2.0)) -> Bool {
        isWithin(delta, of: Angle(degrees: 0))
    }
}

您所做的工作有效,但有一种方法可以在代码中执行此操作,我认为它是逻辑的更 direct/accurate 代码表示。那将是检查一系列值是否包含您的值。在这种情况下,我们正在检查范围 0 到 2 或 358 到 360 是否包含相关的当前角度(以度为单位)。

附带说明一下,我喜欢将这些类型的计算作为计算 属性 包装到我的模型中。它可能看起来像...

struct Angle {
    var degrees: Double

    var isCloseToZero: Bool {
        let upperRange = 358.0...360.0
        let lowerRange = 0.0...2.0
    
        return lowerRange.contains(degrees) || upperRange.contains(degrees)
    }
}

可以这样访问...

let angle = Angle(degrees: 359)
let isClsoeToZero = angle.isCloseToZero