在Swift中生成类似CGFloat.infinity的自定义静态值



正如你们大多数人所知,我们有一个像CGFloat.infinity这样的值,如果我们打印它,它就会打印有点有线的inf!因为CGFloat没有字母表或字符串,只有数字!

然而,我想构建我自己的自定义值,如CGFloat.infinity,称为CGFloat.cool。我想获得cool的打印结果,就像我们可以为CGFloat.infinity获得inf一样,那么我如何为CGFloat定义一个静态值,它在打印中打印cool,但同时它只是一个CGFloate,而不是字符串。

extension CGFloat {
static var cool: CGFloat {
get {
return 0
}
}
}

func test() {

let value1: CGFloat = CGFloat.infinity
let value2: CGFloat = CGFloat.cool

print(value1)
print(value2)
}

结果:

inf

0.0

tl;dr:不幸的是,对于您不拥有的类型(如CGFloat),您不能(在这种特定情况下,即使可以,也无法区分0CGFloat.cool)。

Swift中的类型使用CustomStringConvertible协议来定义从值到String的转换,当类型符合该协议时,print和类似的调试工具会根据该协议的实现来获得要显示的字符串值。

在这种情况下:

  1. CGFloat.description按照Float/Double.description生成结果(取决于平台)
  2. Double.description专门处理已知的Double值:
    • 对于.nan,它直接产生结果"nan";对于所有其他值,它遵循一个名为_float64ToString的函数
    • _float64ToString遵从一个名为swift_float64ToString的函数
    • swift_float64ToString服从一个名为swift_dtoa_optimal_double的函数,该函数本身服从swift_dtoa_optimal_binary64_p
    • 现在,在肉和土豆中,swift_dtoa_optional_binary64_p专门检查并处理无穷大,这就是返回"inf"的内容

然而,关键是Double.description决定了当print为值时所看到的内容,它的协议实现最终将Double.infinity的位模式转换为"inf"Double.cool有两个困难:

  1. Double.description有一个无法将行为注入其中的实现。它负责控制它的字符串表示,而你不能有意义地改变它
  2. 即使可以,CGFloat.cool也具有与0完全相同的表示,并且不可能将它们区分开来——print(0)print(Double.cool)在调用端完全相同,因此不能分别覆盖这些值

所以总的来说,在这种情况下你什么都做不了。对于您自己的类型,您可以自己实现CustomStringConvertible以供print使用(并决定您想要的任何行为),但在这种情况下,您需要找到替代解决方案。

CGFloat.infinity不是字符串。它是CGFloat可以保持的值之一。

print(CGFloat.infinity.bitPattern)
// 9218868437227405312

在二进制中,它是以下位模式:01111111111100000000000000000000000000000000000000000000atr

这是IEEE 754格式,具有一个符号位、11个指数位和52个尾数位。

你会注意到,这实际上并不是";无穷大";以任何数学方式。根据浮点位模式的正常解释,这将是2^1024(或大约1.79769e+308,正如@jjquirckart正确指出的那样)。但是,根据IEEE格式,该比特模式是特殊的,并且被解释为";无穷大">

在IEEE浮点中还有许多其他特殊的比特模式。对你的问题最有用的是NaN(非数字)值,它是所有的位模式,所有的1都在指数中(像无穷大),但尾数中至少有一个设置位。例如,位模式:

0111111111110000000000000000000000000000000000000000000000000001
^
print(CGFloat(bitPattern: 9218868437227405313))
// nan
print(CGFloat(bitPattern: 9218868437227405313).floatingPointClass)
// signalingNaN

对于你的问题,正如Itai Ferber所描述的,你特别要求的是不可能的。您不能更改CGFloat的description(print使用的内容)。

您可以创建一个非信号NaN来保存一个特殊的";不是一个数字";如果你喜欢的话:

extension CGFloat {
static var cool: CGFloat { CGFloat(bitPattern: 0x7FF80000636F6F6C) }
}

但请注意,NaN的行为与数字不同(毕竟,不是数字)。例如,NaN既不小于、也不大于或等于任何其他值,包括另一个NaN(即使它们的位模式相同)。

print(CGFloat.cool == CGFloat.cool)
// false

你唯一能检查它是";您的";值是检查位模式:

print(CGFloat.cool.bitPattern == CGFloat.cool.bitPattern)
// true

您可以使用它来传输";特别的";然而,这是支持的。你可以使用尾数的51个低位来编码你喜欢的任何东西(只要你不使用全零)。尾数的高位编码它是否是"尾数";安静";(非信号)NaN。

如果你想创造一些新的";类似数字的";具有CGFloat的所有值;"酷";值,并按照您想要的方式打印,您必须制作自己的数字类型。不可能是CGFloat。

(这是拼凑在一起的,没有经过很好的测试,它假设这是一个64位平台,需要大量的代码才能完成,但就是这样。)

// A CGFloat that has one special NaN called .cool
struct ExtendedNumber {
// This is a bit pattern for a non-signaling NaN value.
// It is not meant to be interpreted as an integer.
// The low order bits of the mantissa are the UTF-8 encoding of "cool"
private static var coolBitPattern: UInt = 0x7FF8_0000_636F6F6C
static var cool: ExtendedNumber { ExtendedNumber(CGFloat(bitPattern: coolBitPattern)) }
var value: CGFloat
init(_ value: CGFloat) { self.value = value }
var bitPattern: UInt { value.bitPattern }
// This allows `.cool == .cool` to be true
func isEqual(to other: ExtendedNumber) -> Bool {
(self.bitPattern, other.bitPattern) == (Self.coolBitPattern, Self.coolBitPattern)
|| value.isEqual(to: other.value)
}
}
// This allows `print(ExtendedNumber.cool)` to print "cool"
extension ExtendedNumber: CustomStringConvertible {
var description: String {
self == .cool ? "cool" : value.description
}
}

这是ExtendedNumber符合BinaryFloatingPoint:所需的所有样板

extension ExtendedNumber: BinaryFloatingPoint {
mutating func round(_ rule: FloatingPointRoundingRule) { value.round(rule) }
static func /= (lhs: inout ExtendedNumber, rhs: ExtendedNumber) { lhs.value /= rhs.value }
init(sign: FloatingPointSign, exponentBitPattern: CGFloat.RawExponent, significandBitPattern: CGFloat.RawSignificand) {
value = CGFloat(sign: sign, exponentBitPattern: exponentBitPattern, significandBitPattern: significandBitPattern)
}
var exponentBitPattern: CGFloat.RawExponent { value.exponentBitPattern }
var significandBitPattern: CGFloat.RawSignificand { value.significandBitPattern }
init(sign: FloatingPointSign, exponent: CGFloat.Exponent, significand: ExtendedNumber) {
value = CGFloat(sign: sign, exponent: exponent, significand: significand.value)
}
var exponent: CGFloat.Exponent { value.exponent }
func distance(to other: ExtendedNumber) -> CGFloat.Stride { value.distance(to: other.value) }
func advanced(by n: CGFloat.Stride) -> ExtendedNumber { Self(value.advanced(by: n)) }
init(integerLiteral value: CGFloat.IntegerLiteralType) { self.value = CGFloat(integerLiteral: value) }
init(floatLiteral value: CGFloat.FloatLiteralType) { self.value = CGFloat(floatLiteral: value) }
typealias RawSignificand = CGFloat.RawSignificand
typealias RawExponent = CGFloat.RawExponent
typealias FloatLiteralType = CGFloat.FloatLiteralType
typealias Exponent = CGFloat.Exponent
typealias Stride = CGFloat.Stride
static func *= (lhs: inout ExtendedNumber, rhs: ExtendedNumber) { lhs.value *= rhs.value }
static func - (lhs: ExtendedNumber, rhs: ExtendedNumber) -> ExtendedNumber { Self(lhs.value - rhs.value) }
typealias IntegerLiteralType = CGFloat.IntegerLiteralType
static var exponentBitCount: Int { CGFloat.exponentBitCount }
static var significandBitCount: Int { CGFloat.significandBitCount }
var binade: ExtendedNumber { Self(value.binade) }
var significandWidth: Int { value.significandWidth }
static var nan: ExtendedNumber { Self(CGFloat.nan) }
static var signalingNaN: ExtendedNumber { Self(CGFloat.signalingNaN) }
static var infinity: ExtendedNumber { Self(CGFloat.infinity) }
static var greatestFiniteMagnitude: ExtendedNumber { Self(CGFloat.greatestFiniteMagnitude) }
static var pi: ExtendedNumber { Self(CGFloat.pi) }
var ulp: ExtendedNumber { Self(value.ulp) }
static var leastNormalMagnitude: ExtendedNumber { Self(CGFloat.leastNormalMagnitude) }
static var leastNonzeroMagnitude: ExtendedNumber { Self(CGFloat.leastNonzeroMagnitude) }
var sign: FloatingPointSign { value.sign }
var significand: ExtendedNumber { Self(value.significand) }
static func * (lhs: ExtendedNumber, rhs: ExtendedNumber) -> ExtendedNumber { Self(lhs.value * rhs.value) }
static func / (lhs: ExtendedNumber, rhs: ExtendedNumber) -> ExtendedNumber { Self(lhs.value / rhs.value) }
mutating func formRemainder(dividingBy other: ExtendedNumber) { value.formRemainder(dividingBy: other.value) }
mutating func formTruncatingRemainder(dividingBy other: ExtendedNumber) { value.formTruncatingRemainder(dividingBy: other.value) }
mutating func formSquareRoot() { value.formSquareRoot() }
mutating func addProduct(_ lhs: ExtendedNumber, _ rhs: ExtendedNumber) { value.addProduct(lhs.value, rhs.value) }
var nextUp: ExtendedNumber { Self(value.nextUp) }
func isLess(than other: ExtendedNumber) -> Bool { value.isLess(than: other.value) }
func isLessThanOrEqualTo(_ other: ExtendedNumber) -> Bool { value.isLessThanOrEqualTo(other.value) }
var isNormal: Bool { value.isNormal }
var isFinite: Bool { value.isFinite }
var isZero: Bool { value.isZero }
var isSubnormal: Bool { value.isSubnormal }
var isInfinite: Bool { value.isInfinite }
var isNaN: Bool { value.isNaN }
var isSignalingNaN: Bool { value.isSignalingNaN }
var isCanonical: Bool { value.isCanonical }
init?<T>(exactly source: T) where T : BinaryInteger {
guard let value = CGFloat(exactly: source) else { return nil }
self.value = value
}
var magnitude: ExtendedNumber { Self(value.magnitude) }
static func + (lhs: ExtendedNumber, rhs: ExtendedNumber) -> ExtendedNumber { Self(lhs.value + rhs.value) }
}

但如果你做到了所有这些,那么,按照要求:

let value1 = ExtendedNumber.infinity
let value2 = ExtendedNumber.cool
print(value1)  // inf
print(value2)  // cool

最新更新