Home > Mobile >  Making a custom static value like CGFloat.infinity in Swift
Making a custom static value like CGFloat.infinity in Swift

Time:07-11

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:

  1. CGFloat.description defers to Float/Double.description to produce a result (depending on the platform)
  2. Double.description handles known Double values specifically:

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:

  1. 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;
  2. Even if you could, CGFloat.cool has the exact same representation as 0, and it wouldn't be possible to tell them apart — print(0) and print(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
  • Related