Maury Markowitz
Maury Markowitz

Reputation: 9283

"sign" function in Swift

Is there a function that returns +1 for positive numbers and -1 for negatives in Swift?

I looked through the enormous file that appears if you right-click->definitions on a typical function, but if it's in there I don't know it's name.

I did this:

(num < 0 ? -1 : 1)

But I'd rather use a built-in function if there is any--for self-documenting reasons at the minimum.

Upvotes: 23

Views: 13217

Answers (9)

Denis Kreshikhin
Denis Kreshikhin

Reputation: 9420

If both return and argument types should be same then this generic functions solve it:

func sign<T: FloatingPoint>(_ num: T) -> T {
    num < 0 ? T(-1) : T(1)
}

func sign<T: BinaryInteger>(_ num: T) -> T {
    num < 0 ? T(-1) : T(1)
}

Both can be defined together because Swift compiler able to select and inline the right function in compile time.

Examples:

print(sign(-1.23)) // -1.0
print(sign(0)) // 0
print(sign(1 as UInt8)) // 1

print(type(of: sign(-1.23))) // Double
print(type(of: sign(0))) // Int
print(type(of: sign(1 as UInt8))) // UInt8

Upvotes: 0

Fattie
Fattie

Reputation: 12363

The real-world, working codebase, seen-everywhere, approach.

For what it's worth ...

Every real-world app codebase I have ever seen, has something like this in the utility files.

The comment block I have added fully explains the thinking.

extension CGFloat {
    
    ///Precisely as it says, return a CGFloat "one, signed". Return the sign of
    ///the number, expressed as either +1 or -1. As everyone knows, never use
    ///`.sign` in a working codebase, ever, for any reason, as it's broken by
    ///being stupidly-named. As everyone knows, never use `.signum` in a working
    ///codebase, ever, for any reason, as it's broken by being stupidly-named.
    var oneSigned: CGFloat {
        return (self < 0.0) ? -1.0 : 1.0
    }
}

Don't forget that code which is not self-documenting: is broken and unusable.

Every variable name in all code, not to mention call names, has to be absolutely non-misunderstanable.

There should never be a case, where even one programmer on the team, for a split-second has to think "what does this do" and this is incredibly true when you're dealing with a common source of unity errors in algorithms.

Indeed as mentioned in a comment somewhere, I've often seen that looking like this:

extension CGFloat {
        var oneSignedCGFloat: CGFloat {
            return (self < 0.0) ? -1.0 : 1.0
        }
    }
extension Float {
        var oneSignedFloat: Float {
            return (self < 0.0) ? -1.0 : 1.0
        }
    }

... so that there is absolutely no possibility of a mistake in terms of type.

In the extremely rare case where you need a ternary in an algorithm (which anyway basically means your algorithm is poorly-thought out), I would name it EXTREMELY specifically, such as

extension CGFloat {
        var minusOneZeroOrOneBeAwareThisIsTernary: CGFloat {
            return ... -1, 0 or 1
        }
    }

FWIW.

Upvotes: -2

&#214;mer Ulusal
&#214;mer Ulusal

Reputation: 149

You can use signum() if your value is an integer.

https://developer.apple.com/documentation/swift/int/2886673-signum

Here is a code snippet to make it clear;

let negative: Int = -10
let zero: Int = 0
let positive: Int = 10

print(negative.signum()) // prints "-1"
print(zero.signum()) // prints "0"
print(positive.signum()) // prints "1"

Upvotes: 8

Wil Shipley
Wil Shipley

Reputation: 9543

In Swift-4 floats have a new property:

public var sign: FloatingPointSign { get }

(However this only checks the sign bit, so it’ll fail for some cases like -0 — see that accepted answer above.)

Upvotes: 6

dfrib
dfrib

Reputation: 73206

Swift 4

As has already been pointed out in @Wil Shipley's Swift 4 answer, there is now a sign property in the FloatingPoint protocol:

FloatingPointSign

The sign of a floating-point value.

Enumeration Cases

  • case minus The sign for a negative value.
  • case plus The sign for a positive value.

However, the comments to the sign blueprint in the source code of FloatingPoint contains important information that is (yet?) not present in the generated docs:

/// The sign of the floating-point value.
///
/// The `sign` property is `.minus` if the value's signbit is set, and
/// `.plus` otherwise. For example:
///
///     let x = -33.375
///     // x.sign == .minus
///
/// Do not use this property to check whether a floating point value is
/// negative. For a value `x`, the comparison `x.sign == .minus` is not
/// necessarily the same as `x < 0`. In particular, `x.sign == .minus` if
/// `x` is -0, and while `x < 0` is always `false` if `x` is NaN, `x.sign`
/// could be either `.plus` or `.minus`.

emphasizing, "... .minus if the value's signbit is set" and "Do not use this property to check whether a floating point value is negative".

Summa summarum: use the new sign property of the FloatingPoint protocol to actually check whether the value's signbit is set or not, but make sure to use some care if attempting to use this property to tell whether a number is negative or not.

var f: Float = 0.0

if case .minus = (-f).sign { print("A. f is negative!") }

f = -Float.nan

if f < 0 { print("B. f is negative!") }
if case .minus = f.sign { print("C. f is negative!") }

// A. f is negative!
// C. f is negative!

Swift 3

W.r.t. built-in functions, I think the closest you'll get is the Foundation method copysign(_: Double, _: Double) -> Double

let foo = -15.2
let sign = copysign(1.0, foo) // -1.0 (Double)

Naturally needing some type conversion in case you're not operating on a number of type Double.

However, I see no reason why not to create your own extension fit to your needs, especially for such a simple function as sign as they needn't get bloated, e.g.

extension IntegerType {
    func sign() -> Int {
        return (self < 0 ? -1 : 1)
    }
    /* or, use signature: func sign() -> Self */
}

extension FloatingPointType {
    func sign() -> Int {
        return (self < Self(0) ? -1 : 1)
    }
}

(here yielding 1 also for 0, as in the example in your question).


(Edit with regard to your comment below)

An alternative solution to the above would be to define your own protocol with a default implementation of sign(), so that all types conforming to this protocol would have access to that sign() method.

protocol Signable {
    init()
    func <(lhs:Self, rhs:Self) -> Bool
}

extension Signable {
    func sign() -> Int {
        return (self < Self() ? -1 : 1)
    }
}

/* extend signed integer types to Signable */
extension Int: Signable { }    // already have < and init() functions, OK
extension Int8 : Signable { }  // ...
extension Int16 : Signable { }
extension Int32 : Signable { }
extension Int64 : Signable { }

/* extend floating point types to Signable */
extension Double : Signable { }
extension Float : Signable { }
extension CGFloat : Signable { }

/* example usage */
let foo = -4.2
let bar = 42

foo.sign() // -1 (Int)
bar.sign() // 1  (Int)

Upvotes: 16

neave
neave

Reputation: 2531

The simd library has a sign method:

import simd

sign(-100.0) // returns -1
sign(100.0) // returns 1
sign(0.0) // returns 0

You get simd for free if you import SpriteKit.

Upvotes: 8

JMI
JMI

Reputation: 2628

Since copysign cannot be used over Integer I'm using this extension:

extension Comparable where Self: SignedNumber {

    var sign: Int {
        guard self != -self else {
            return 0
        }
        return self > -self ? 1 : -1
    } 
}

Upvotes: -1

Kevin
Kevin

Reputation: 17576

FloatingPointType has a built-in computed variable but it returns a boolean. If you only need this operation on floats you can use an extension like this:

extension FloatingPointType {
    var signValue: Int {
        return isSignMinus ? -1 : 1
    }
}

However, I believe the best approach would be to extend the SignedNumberType protocol.

extension SignedNumberType {
    var signValue: Int {
        return (self >= -self) ? 1 : -1
    }
}

If you want 0 to return -1 then just change >= to >.

Test cases:

print(3.0.signValue)
print(0.signValue)
print(-3.0.signValue)

Upvotes: 1

Masih
Masih

Reputation: 1710

Use:

let result = signbit(number)

This will return 1 for negative numbers and 0 for positives.

let number = -1.0
print("\(signbit(number))")

1

let number = 1.0
print("\(signbit(number))")

0

Upvotes: 4

Related Questions