7

Using Swift 3.

I am finding a lot of strange solutions online for checking if a Decimal object is a whole number. Everything feels far more complicated then it needs to be.

Here is my solution:

extension Decimal {
    var isWholeNumber: Bool {
        return self.exponent == 1
    }
}

In my tests this works. My question is am I missing something obvious?

robmathers
  • 3,028
  • 1
  • 26
  • 29
mgChristopher
  • 375
  • 1
  • 3
  • 12
  • 3
    You actually want `return self.exponent >= 0` but other than that, I can't think of any reason why it wouldn't work. – John Montgomery May 17 '17 at 20:05
  • @JohnMontgomery true, as opposed to type variable, its instance variable – Sandip Bantawa May 17 '17 at 20:22
  • Thanks @JohnMontgomery more testing shows I needed to add a few more checks. Here is my solution: `return self.isZero || (self.isNormal && self.exponent >= 0)` – mgChristopher May 17 '17 at 20:26
  • 1
    @mgChristopher note that when creating an instance computed property with a getter but without a setter you can omit the `get` keyword and its brackets `var isWholeNumber: Bool { return isZero || (isNormal && exponent >= 0) }` – Leo Dabus May 17 '17 at 20:58
  • Which *"strange solutions"* did you find online? – Just to avoid that someone posts an alternative solution only to get the response *"that's what I found already"* :) – Martin R May 17 '17 at 21:10
  • @MartinR I bet one of these "strange solutions" would be: *convert* to a string and look for the *decimal point* :-) – Paulo Mattos May 17 '17 at 21:17
  • 2
    I did see that @PauloMattos :D. Here are some SO post I found [1](http://stackoverflow.com/q/12298755/1535822), [2](http://stackoverflow.com/q/25552648/1535822), [3](http://stackoverflow.com/q/28447732/1535822). I was tempted to do one of the solution mention in #3, but still felt like more work then needed. – mgChristopher May 17 '17 at 21:20
  • 1
    @mgChristopher: [#1](http://stackoverflow.com/a/12305042/1187415) can be translated to Swift without problems. It is more code, but has the advantage that it uses only *documented* functions – as far as I can see the exact representation of Decimal is nowhere guaranteed, and the [Objective-C variant](https://developer.apple.com/reference/foundation/nsdecimal?language=objc) even states that "The fields of NSDecimal are private." – Martin R May 17 '17 at 21:25
  • @MartinR If you are working in ObjC, then yes you should use NSDecimalNumber. I would only use NSDecimal struct in ObjC if I needed extra performance. In Swift [NSDecimalNumber](https://developer.apple.com/reference/foundation/nsdecimalnumber) is bridged to Decimal. I would also say that Decimal is documented; NSDecimal is not. Also when in doubt we can view the source [code](https://github.com/apple/swift-corelibs-foundation/blob/master/Foundation/NSDecimal.swift) – mgChristopher May 17 '17 at 22:02

4 Answers4

8

Thanks for the comments! Here is what I am using now.

extension Decimal {
    var isWholeNumber: Bool { 
        return self.isZero || (self.isNormal && self.exponent >= 0) 
    }
}
mgChristopher
  • 375
  • 1
  • 3
  • 12
  • 2
    This gives a false negative for integer-valued, non-normalized `Decimal`s – Alexander Sep 20 '17 at 20:34
  • I think for most cases this works, but for like `Decimal(1.00000000000000009).isWholeNumber` returns `true`, 17 digits this, so the key should be don't convert from double to Decimal. – William Hu Sep 06 '21 at 04:12
5

Here is a translation of the Objective-C solution in Check if NSDecimalNumber is whole number to Swift:

extension Decimal {
    var isWholeNumber: Bool {
        if isZero { return true }
        if !isNormal { return false }
        var myself = self
        var rounded = Decimal()
        NSDecimalRound(&rounded, &myself, 0, .plain)
        return self == rounded
    }
}

print(Decimal(string: "1234.0")!.isWholeNumber) // true
print(Decimal(string: "1234.5")!.isWholeNumber) // false

This works even if the mantissa is not minimal (or the exponent not maximal), such as 100 * 10-1. Example:

let z = Decimal(_exponent: -1, _length: 1, _isNegative: 0, _isCompact: 1, _reserved: 0,
                _mantissa: (100, 0, 0, 0, 0, 0, 0, 0))

print(z) // 10.0
print(z.exponent) // -1
print(z.isWholeNumber) // true
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • IMHO guard statements have a more natural syntax here: `guard !isZero`, `guard isNormal` – Alexander Sep 20 '17 at 20:42
  • 1
    @Alexander:  No, I do not find the "double negation" in `guard !isZero else { return true }` more natural. Zero has to be treated separately, but it is not an "exceptional case". – Martin R Sep 20 '17 at 20:46
  • @MartinR: FWIW guard is not for “exceptional cases.” It’s for cases that should force exiting the current scope. Anyway usage of if vs. guard is usually up to the discretion of the implementer (I’d have used guard here but YMMV). Thanks for the answer. – Frizlab Jun 01 '18 at 01:24
  • 3
    @Frizlab: Sure, guard was introduced to solve the "if let pyramid of doom", but can be used with any boolean expression. – Perhaps I expressed myself badly, I am just opposed to using `guard` for each and every "early return" situation. In this case, `if isZero { return true }` is easier to understand than `guard !isZero else { return true }` *in my opinion.* – Martin R Jun 01 '18 at 06:59
0

I'm pretty new to Swift but I'm comparing the input to a rounded input:

let input = 2.9

round(input) == input
        3        2.9
Human Friend
  • 135
  • 1
  • 4
  • 11
-2

I'm not sure can works in all cases but that maybe a more coincise option

extension Decimal {

    static var decimalSeparator: String { return NumberFormatter().decimalSeparator }

    var isFraction: Bool {
        return self.description.contains(Decimal.decimalSeparator)
    }

    var isWhole: Bool {
        return !isFraction
    }

}
  • 1
    Converting number to string, and then looking for a symbol in the string is one of the slowest and resource consuming ways to solve the problem possible. – user28434'mstep Mar 24 '22 at 13:34