Skip to content

Instantly share code, notes, and snippets.

@colbyn
Created June 23, 2025 16:19
Show Gist options
  • Save colbyn/de2e52d93ddf67fcf4111bdf20b58b4c to your computer and use it in GitHub Desktop.
Save colbyn/de2e52d93ddf67fcf4111bdf20b58b4c to your computer and use it in GitHub Desktop.
Turn Angles
import Foundation
/// A type-safe representation of an angle in full turns.
/// One full turn equals 360 degrees or 2π radians.
struct Turn: Hashable, Codable {
/// The raw value in full turns. 1.0 = 360° = 2π radians.
let value: Double
/// Creates a `Turn` from a value in full turns.
/// - Parameter value: The angle in full turns.
init(_ value: Double) {
self.value = value
}
/// Creates a `Turn` from radians.
/// - Parameter radians: Angle in radians.
init(radians: Double) {
self.value = radians / (2 * .pi)
}
/// Creates a `Turn` from degrees.
/// - Parameter degrees: Angle in degrees.
init(degrees: Double) {
self.value = degrees / 360.0
}
/// Returns the angle in radians.
var radians: Double {
value * 2 * .pi
}
/// Returns the angle in degrees.
var degrees: Double {
value * 360.0
}
/// Returns the angle wrapped into the range `[0, 1)`.
var wrapped: Turn {
let v = value.truncatingRemainder(dividingBy: 1.0)
return Turn(v >= 0 ? v : v + 1.0)
}
/// A constant representing 0 turns (0°).
static var zero: Turn { .init(0) }
/// A constant representing 1/4 turn (90°).
static var quarter: Turn { .init(0.25) }
/// A constant representing 1/2 turn (180°).
static var half: Turn { .init(0.5) }
/// A constant representing 1 full turn (360°).
static var full: Turn { .init(1.0) }
}
//static func ≈ (lhs: Turn, rhs: Turn) -> Bool {
// abs(lhs.wrapped.value - rhs.wrapped.value) < .ulpOfOne
//}
// MARK: - Arithmetic
extension Turn {
/// Adds two `Turn` values.
static func + (lhs: Turn, rhs: Turn) -> Turn {
Turn(lhs.value + rhs.value)
}
/// Subtracts one `Turn` from another.
static func - (lhs: Turn, rhs: Turn) -> Turn {
Turn(lhs.value - rhs.value)
}
/// Multiplies a `Turn` by a scalar.
static func * (lhs: Turn, rhs: Double) -> Turn {
Turn(lhs.value * rhs)
}
/// Multiplies a scalar by a `Turn`.
static func * (lhs: Double, rhs: Turn) -> Turn {
Turn(lhs * rhs.value)
}
/// Divides a `Turn` by a scalar.
static func / (lhs: Turn, rhs: Double) -> Turn {
Turn(lhs.value / rhs)
}
/// Negates a `Turn`.
static prefix func - (angle: Turn) -> Turn {
Turn(-angle.value)
}
}
// MARK: - Trigonometry
extension Turn {
/// Sine of the angle.
var sin: Double { Foundation.sin(radians) }
/// Cosine of the angle.
var cos: Double { Foundation.cos(radians) }
/// Tangent of the angle.
var tan: Double { Foundation.tan(radians) }
/// Inverse sine: returns an angle in the range `[0, 0.5]` turns.
/// - Parameter value: Must be in `[-1, 1]`.
static func arcsin(_ value: Double) -> Turn {
Turn(radians: Foundation.asin(value)).wrapped
}
/// Inverse cosine: returns an angle in the range `[0, 0.5]` turns.
/// - Parameter value: Must be in `[-1, 1]`.
static func arccos(_ value: Double) -> Turn {
Turn(radians: Foundation.acos(value)).wrapped
}
/// Inverse tangent: returns an angle in the range `[0, 1)` turns.
/// - Parameter value: Any real number.
static func arctan(_ value: Double) -> Turn {
Turn(radians: Foundation.atan(value)).wrapped
}
/// Inverse tangent with two arguments, returning angle in full-turn range `[0, 1)`.
/// - Parameters:
/// - y: Y-coordinate (opposite).
/// - x: X-coordinate (adjacent).
static func arctan2(y: Double, x: Double) -> Turn {
Turn(radians: Foundation.atan2(y, x)).wrapped
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment