mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
After speaking with the current owner of Measurement, the most sensible path is to at runtime verifty the conformance of Units to a specific dimension since we cannot directly check at compile time. If at a future point in time a more specific comparitor can be added that can restrict the comparison to measurements in a specific dimension without causing equatable failures we may want to revisit this. However as it stands this preserves the most reasonable implementation of comparison of disperate measurement unit types while preserving the expected logic of conversions within that dimension.
255 lines
12 KiB
Swift
255 lines
12 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See http://swift.org/LICENSE.txt for license information
|
|
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
@_exported import Foundation // Clang module
|
|
|
|
/// A `Measurement` is a model type that holds a `Double` value associated with a `Unit`.
|
|
///
|
|
/// Measurements support a large set of operators, including `+`, `-`, `*`, `/`, and a full set of comparison operators.
|
|
@available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
|
|
public struct Measurement<UnitType : Unit> : ReferenceConvertible, Comparable, Equatable {
|
|
public typealias ReferenceType = NSMeasurement
|
|
|
|
/// The unit component of the `Measurement`.
|
|
public let unit: UnitType
|
|
|
|
/// The value component of the `Measurement`.
|
|
public var value: Double
|
|
|
|
/// Create a `Measurement` given a specified value and unit.
|
|
public init(value: Double, unit: UnitType) {
|
|
self.value = value
|
|
self.unit = unit
|
|
}
|
|
|
|
public var hashValue: Int {
|
|
return Int(bitPattern: __CFHashDouble(value))
|
|
}
|
|
}
|
|
|
|
@available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
|
|
extension Measurement : CustomStringConvertible, CustomDebugStringConvertible, CustomReflectable {
|
|
public var description: String {
|
|
return "\(value) \(unit.symbol)"
|
|
}
|
|
|
|
public var debugDescription: String {
|
|
return "\(value) \(unit.symbol)"
|
|
}
|
|
|
|
public var customMirror: Mirror {
|
|
var c: [(label: String?, value: Any)] = []
|
|
c.append((label: "value", value: value))
|
|
c.append((label: "unit", value: unit.symbol))
|
|
return Mirror(self, children: c, displayStyle: Mirror.DisplayStyle.struct)
|
|
}
|
|
}
|
|
|
|
|
|
/// When a `Measurement` contains a `Dimension` unit, it gains the ability to convert between the kinds of units in that dimension.
|
|
@available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
|
|
extension Measurement where UnitType : Dimension {
|
|
/// Returns a new measurement created by converting to the specified unit.
|
|
///
|
|
/// - parameter otherUnit: A unit of the same `Dimension`.
|
|
/// - returns: A converted measurement.
|
|
public func converted(to otherUnit: UnitType) -> Measurement<UnitType> {
|
|
if unit.isEqual(otherUnit) {
|
|
return Measurement(value: value, unit: otherUnit)
|
|
} else {
|
|
let valueInTermsOfBase = unit.converter.baseUnitValue(fromValue: value)
|
|
if otherUnit.isEqual(type(of: unit).baseUnit()) {
|
|
return Measurement(value: valueInTermsOfBase, unit: otherUnit)
|
|
} else {
|
|
let otherValueFromTermsOfBase = otherUnit.converter.value(fromBaseUnitValue: valueInTermsOfBase)
|
|
return Measurement(value: otherValueFromTermsOfBase, unit: otherUnit)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Converts the measurement to the specified unit.
|
|
///
|
|
/// - parameter otherUnit: A unit of the same `Dimension`.
|
|
public mutating func convert(to otherUnit: UnitType) {
|
|
self = converted(to: otherUnit)
|
|
}
|
|
|
|
/// Add two measurements of the same Dimension.
|
|
///
|
|
/// If the `unit` of the `lhs` and `rhs` are `isEqual`, then this returns the result of adding the `value` of each `Measurement`. If they are not equal, then this will convert both to the base unit of the `Dimension` and return the result as a `Measurement` of that base unit.
|
|
/// - returns: The result of adding the two measurements.
|
|
public static func +(lhs: Measurement<UnitType>, rhs: Measurement<UnitType>) -> Measurement<UnitType> {
|
|
if lhs.unit.isEqual(rhs.unit) {
|
|
return Measurement(value: lhs.value + rhs.value, unit: lhs.unit)
|
|
} else {
|
|
let lhsValueInTermsOfBase = lhs.unit.converter.baseUnitValue(fromValue: lhs.value)
|
|
let rhsValueInTermsOfBase = rhs.unit.converter.baseUnitValue(fromValue: rhs.value)
|
|
return Measurement(value: lhsValueInTermsOfBase + rhsValueInTermsOfBase, unit: type(of: lhs.unit).baseUnit())
|
|
}
|
|
}
|
|
|
|
/// Subtract two measurements of the same Dimension.
|
|
///
|
|
/// If the `unit` of the `lhs` and `rhs` are `==`, then this returns the result of subtracting the `value` of each `Measurement`. If they are not equal, then this will convert both to the base unit of the `Dimension` and return the result as a `Measurement` of that base unit.
|
|
/// - returns: The result of adding the two measurements.
|
|
public static func -(lhs: Measurement<UnitType>, rhs: Measurement<UnitType>) -> Measurement<UnitType> {
|
|
if lhs.unit == rhs.unit {
|
|
return Measurement(value: lhs.value - rhs.value, unit: lhs.unit)
|
|
} else {
|
|
let lhsValueInTermsOfBase = lhs.unit.converter.baseUnitValue(fromValue: lhs.value)
|
|
let rhsValueInTermsOfBase = rhs.unit.converter.baseUnitValue(fromValue: rhs.value)
|
|
return Measurement(value: lhsValueInTermsOfBase - rhsValueInTermsOfBase, unit: type(of: lhs.unit).baseUnit())
|
|
}
|
|
}
|
|
}
|
|
|
|
@available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
|
|
extension Measurement {
|
|
/// Add two measurements of the same Unit.
|
|
/// - precondition: The `unit` of `lhs` and `rhs` must be `isEqual`.
|
|
/// - returns: A measurement of value `lhs.value + rhs.value` and unit `lhs.unit`.
|
|
public static func +(lhs: Measurement<UnitType>, rhs: Measurement<UnitType>) -> Measurement<UnitType> {
|
|
if lhs.unit.isEqual(rhs.unit) {
|
|
return Measurement(value: lhs.value + rhs.value, unit: lhs.unit)
|
|
} else {
|
|
fatalError("Attempt to add measurements with non-equal units")
|
|
}
|
|
}
|
|
|
|
/// Subtract two measurements of the same Unit.
|
|
/// - precondition: The `unit` of `lhs` and `rhs` must be `isEqual`.
|
|
/// - returns: A measurement of value `lhs.value - rhs.value` and unit `lhs.unit`.
|
|
public static func -(lhs: Measurement<UnitType>, rhs: Measurement<UnitType>) -> Measurement<UnitType> {
|
|
if lhs.unit.isEqual(rhs.unit) {
|
|
return Measurement(value: lhs.value - rhs.value, unit: lhs.unit)
|
|
} else {
|
|
fatalError("Attempt to subtract measurements with non-equal units")
|
|
}
|
|
}
|
|
|
|
/// Multiply a measurement by a scalar value.
|
|
/// - returns: A measurement of value `lhs.value * rhs` with the same unit as `lhs`.
|
|
public static func *(lhs: Measurement<UnitType>, rhs: Double) -> Measurement<UnitType> {
|
|
return Measurement(value: lhs.value * rhs, unit: lhs.unit)
|
|
}
|
|
|
|
/// Multiply a scalar value by a measurement.
|
|
/// - returns: A measurement of value `lhs * rhs.value` with the same unit as `rhs`.
|
|
public static func *(lhs: Double, rhs: Measurement<UnitType>) -> Measurement<UnitType> {
|
|
return Measurement(value: lhs * rhs.value, unit: rhs.unit)
|
|
}
|
|
|
|
/// Divide a measurement by a scalar value.
|
|
/// - returns: A measurement of value `lhs.value / rhs` with the same unit as `lhs`.
|
|
public static func /(lhs: Measurement<UnitType>, rhs: Double) -> Measurement<UnitType> {
|
|
return Measurement(value: lhs.value / rhs, unit: lhs.unit)
|
|
}
|
|
|
|
/// Divide a scalar value by a measurement.
|
|
/// - returns: A measurement of value `lhs / rhs.value` with the same unit as `rhs`.
|
|
public static func /(lhs: Double, rhs: Measurement<UnitType>) -> Measurement<UnitType> {
|
|
return Measurement(value: lhs / rhs.value, unit: rhs.unit)
|
|
}
|
|
|
|
/// Compare two measurements of the same `Dimension`.
|
|
///
|
|
/// If `lhs.unit == rhs.unit`, returns `lhs.value == rhs.value`. Otherwise, converts `rhs` to the same unit as `lhs` and then compares the resulting values.
|
|
/// - returns: `true` if the measurements are equal.
|
|
public static func ==<LeftHandSideType : Unit, RightHandSideType : Unit>(_ lhs: Measurement<LeftHandSideType>, _ rhs: Measurement<RightHandSideType>) -> Bool {
|
|
if lhs.unit == rhs.unit {
|
|
return lhs.value == rhs.value
|
|
} else {
|
|
if let lhsDimensionalUnit = lhs.unit as? Dimension,
|
|
let rhsDimensionalUnit = rhs.unit as? Dimension {
|
|
if type(of: lhsDimensionalUnit).baseUnit() == type(of: rhsDimensionalUnit).baseUnit() {
|
|
let lhsValueInTermsOfBase = lhsDimensionalUnit.converter.baseUnitValue(fromValue: lhs.value)
|
|
let rhsValueInTermsOfBase = rhsDimensionalUnit.converter.baseUnitValue(fromValue: rhs.value)
|
|
return lhsValueInTermsOfBase == rhsValueInTermsOfBase
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
|
|
/// Compare two measurements of the same `Unit`.
|
|
/// - returns: `true` if the measurements can be compared and the `lhs` is less than the `rhs` converted value.
|
|
public static func <<LeftHandSideType : Unit, RightHandSideType : Unit>(lhs: Measurement<LeftHandSideType>, rhs: Measurement<RightHandSideType>) -> Bool {
|
|
if lhs.unit == rhs.unit {
|
|
return lhs.value < rhs.value
|
|
} else {
|
|
if let lhsDimensionalUnit = lhs.unit as? Dimension,
|
|
let rhsDimensionalUnit = rhs.unit as? Dimension {
|
|
if type(of: lhsDimensionalUnit).baseUnit() == type(of: rhsDimensionalUnit).baseUnit() {
|
|
let lhsValueInTermsOfBase = lhsDimensionalUnit.converter.baseUnitValue(fromValue: lhs.value)
|
|
let rhsValueInTermsOfBase = rhsDimensionalUnit.converter.baseUnitValue(fromValue: rhs.value)
|
|
return lhsValueInTermsOfBase < rhsValueInTermsOfBase
|
|
}
|
|
}
|
|
fatalError("Attempt to compare measurements with non-equal dimensions")
|
|
}
|
|
}
|
|
}
|
|
|
|
// Implementation note: similar to NSArray, NSDictionary, etc., NSMeasurement's import as an ObjC generic type is suppressed by the importer. Eventually we will need a more general purpose mechanism to correctly import generic types.
|
|
|
|
@available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
|
|
extension Measurement : _ObjectiveCBridgeable {
|
|
@_semantics("convertToObjectiveC")
|
|
public func _bridgeToObjectiveC() -> NSMeasurement {
|
|
return NSMeasurement(doubleValue: value, unit: unit)
|
|
}
|
|
|
|
public static func _forceBridgeFromObjectiveC(_ source: NSMeasurement, result: inout Measurement?) {
|
|
result = Measurement(value: source.doubleValue, unit: source.unit as! UnitType)
|
|
}
|
|
|
|
public static func _conditionallyBridgeFromObjectiveC(_ source: NSMeasurement, result: inout Measurement?) -> Bool {
|
|
if let u = source.unit as? UnitType {
|
|
result = Measurement(value: source.doubleValue, unit: u)
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
|
|
public static func _unconditionallyBridgeFromObjectiveC(_ source: NSMeasurement?) -> Measurement {
|
|
let u = source!.unit as! UnitType
|
|
return Measurement(value: source!.doubleValue, unit: u)
|
|
}
|
|
}
|
|
|
|
/*
|
|
FIXME(id-as-any): can't write this code because of:
|
|
<rdar://problem/27539951> "unhandled generic bridged type" when bridging NSMeasurement
|
|
|
|
@available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
|
|
extension NSMeasurement : _HasCustomAnyHashableRepresentation {
|
|
// Must be @nonobjc to avoid infinite recursion during bridging.
|
|
@nonobjc
|
|
public func _toCustomAnyHashable() -> AnyHashable? {
|
|
return AnyHashable(self as Measurement)
|
|
}
|
|
}
|
|
*/
|
|
|
|
// This workaround is required for the time being, because Swift doesn't support covariance for Measurement (26607639)
|
|
@available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
|
|
extension MeasurementFormatter {
|
|
public func string<UnitType: Unit>(from measurement: Measurement<UnitType>) -> String {
|
|
if let result = string(for: measurement) {
|
|
return result
|
|
} else {
|
|
return ""
|
|
}
|
|
}
|
|
}
|