Search code examples
uikitswift5scientific-notation

Is there any built-in way to get the mantissa and exponent (the "scientific notation" or "E-notation" values) of a CGFloat in Swift?


I was stunned to learn there is no? immediate way to get the mantissa/exponent of a CGFloat in Swift.

(Note that I want the two values, NOT a string representation in scientific notation format.)

These hopelessly undocumented properties Double.exponent, Double.significand are on Double but that's quite different from wanting the "usual, human" mantissa/exponent. (Unless I'm drastically missing something.)

I typed out a function to do it, but doing so is crap for at least three major reasons that come to mind.

Is there a solution to this conundrum?

(*) Surprised there are not 100 questions about this issue on here!

extension CGFloat { // (ignored negatives for clarity)
    var decomp: (CGFloat, CGFloat) {
        var exponent: CGFloat = 0.0
        var mantissa: CGFloat = self
        while mantissa < 1.0 {
            mantissa = mantissa * 10.0
            exponent -= 1
        }
        while mantissa >= 10.0 {
            mantissa = mantissa / 10.0
            exponent += 1
        }
        print("check ... ", self, "\(mantissa)E\(exponent)")
        return (mantissa, exponent)
    }
}

hence ..

var x: CGFloat = 0.00123
print( "yo" , x.decomp)
x = 12
print( "yo" , x.decomp)
x = 1000
print( "yo" , x.decomp)
check ...  0.00123 1.23E-3.0
yo (1.23, -3.0)
check ...  12.0 1.2E1.0
yo (1.2, 1.0)
check ...  1000.0 1.0E3.0
yo (1.0, 3.0)

(†) Minor - I return the exp as a float since that seemed more consistent for the typical ways you'd then use such a thing, but IDK.


Solution

  • The exponent and significand properties of Double gives you the binary significand and exponents. After all, Double is a binary floating point type.

    It seems like you want to get the base-10 exponent and significand. To do that, you should use a Decimal, which represents a base-10 number.

    extension CGFloat {
        // this does not consider "special" values like infinities and NaNs
        var base10ExponentAndSignificand: (CGFloat, CGFloat) {
            let decimal = Decimal(self)
            
            return (
                CGFloat(truncating: decimal.significand as NSNumber) * (decimal.isSignMinus ? -1 : 1),
                CGFloat(decimal.exponent)
            )
        }
    }
    

    This will produce integers n, m where the original number is equal to n * pow(10, m).

    Scientific notation is usually written as nEm where n is a number between 1 and 10. If you want n to be between 1 and 10, I would just use log10 to calculate the exponent, instead of the looping approach shown in your question:

    extension CGFloat {
        // this does not consider "special" values like infinities and NaNs
        var base10ExponentAndSignificand: (CGFloat, CGFloat) {
            guard self != 0 else { return (0, 0) }
            
            let exp = log10(self.magnitude).rounded(.down)
            let significand = self / pow(10, exp)
            
            return (significand, exp)
        }
    }