As may most of you know we have a value like CGFloat.infinity
which if we print it, it prints inf
which is a little wired! because CGFloat has no alphabet or string but numbers!
However I want to build my own custom value like CGFloat.infinity
called CGFloat.cool
I want get print result of cool
like we can get inf
for CGFloat.infinity
, so how can i define a static value for CGFloat which in prints it prints cool
but in the same time it is just a CGFloat and not a String.
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)
}
results:
inf
0.0
CodePudding user response:
tl;dr: Unfortunately, on a type you don't own (like CGFloat
), you can't (and in this specific case, even if you could, you couldn't distinguish between 0
and CGFloat.cool
).
Types in Swift use the CustomStringConvertible
protocol to define a conversion from a value to a String
, and when a type conforms to this protocol, print
and similar debug tools defer to the implementation of that protocol to get a string value to display.
In this case:
CGFloat.description
defers toFloat
/Double.description
to produce a result (depending on the platform)Double.description
handles knownDouble
values specifically:- For
.nan
, it produces the result"nan"
directly; for all other values, it defers to a function called_float64ToString
_float64ToString
defers to a function calledswift_float64ToString
swift_float64ToString
defers to a function calledswift_dtoa_optimal_double
, which itself defers toswift_dtoa_optimal_binary64_p
- Now in the meat and potatoes,
swift_dtoa_optional_binary64_p
checks for, and handles, infinity specifically, and this is what returns"inf"
- For
The key to this, however, is that it's Double.description
that determines what you see when you print
a value, and its implementation of the protocol eventually converts the bit pattern of Double.infinity
into "inf"
. There are two difficulties with Double.cool
:
Double.description
has an implementation that you can't inject behavior into. It is in charge of controlling its string representation, and you can't meaningfully change it;- Even if you could,
CGFloat.cool
has the exact same representation as0
, and it wouldn't be possible to tell them apart —print(0)
andprint(Double.cool)
are exactly the same on the calling side, so you can't override the values separately
So in all, there isn't anything you can do in this case specifically. For your own types, you can implement CustomStringConvertible
yourself for print
to use (and decide on whatever behavior you want there), but in this case, you'll need to find an alternative solution.
CodePudding user response:
CGFloat.infinity
is not a String. It is one of the values that CGFloat can hold.
print(CGFloat.infinity.bitPattern)
// 9218868437227405312
In binary, it is the following bit pattern: 0111111111110000000000000000000000000000000000000000000000000000
This is in the IEEE 754 format, with one sign bit, 11 exponent bits, and 52 mantissa bits.
You'll note that this is not actually "infinity" in any mathematical way. According to the normal interpretation of floating point bit patterns, this would be 2^1024 (or approximately 1.79769e 308, as @jjquirckart correctly noted). But, according to the IEEE format, this bit pattern is special and interpreted as "infinity."
There are many other special bit patterns in IEEE floating point. Most useful for your question would be the NaN (not-a-number) values, which are all bit patterns that have all ones in the exponent (like infinity), but have at least one set bit in the mantissa. For example, the bit pattern:
0111111111110000000000000000000000000000000000000000000000000001
^
print(CGFloat(bitPattern: 9218868437227405313))
// nan
print(CGFloat(bitPattern: 9218868437227405313).floatingPointClass)
// signalingNaN
To your question, what you've specifically asked for is impossible as Itai Ferber described. You cannot change CGFloat's description
(which is what is used by print
).
You can create a non-signaling NaN to hold a special "not a number" value if you like:
extension CGFloat {
static var cool: CGFloat { CGFloat(bitPattern: 0x7FF80000636F6F6C) }
}
But note that NaN does not behave like numbers (they are, after all, not a number). For example, a NaN is neither less than, greater than, or equal to any other value, including another NaN (even if their bit patterns are the same).
print(CGFloat.cool == CGFloat.cool)
// false
The only way you can check that it's "your" value is to check the bit pattern:
print(CGFloat.cool.bitPattern == CGFloat.cool.bitPattern)
// true
You can use this to transmit "special" values through the system, however, and this is supported. You can use the 51 low bits of the mantissa to encode whatever you like (as long as you don't use all zeros). The high bit of the mantissa encodes whether it's a "quiet" (non-signaling) NaN.
If you wanted to create some new "number-like" thing that had all the values of CGFloat and also another "cool" value, and printed the way you wanted it too, you'd have to make your own number type. It couldn't be CGFloat.
(This is slapped together and not well tested, and it assumes this is a 64-bit platform, and it's a ton of code to do it, but this is how you would.)
// 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
}
}
And this is all the boilerplate required for ExtendedNumber to conform to 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) }
}
But if you do all of that, then, as requested:
let value1 = ExtendedNumber.infinity
let value2 = ExtendedNumber.cool
print(value1) // inf
print(value2) // cool