Even and Odd Integers

Even And Odd Integers

DISCLAIMER: This is not a proposal, just an idea

Several times recently I have been forced to identify even elements for the purposes of analyzing an array or other, similar groupings of data. After the second time I simply extended Int and added an isEven computed property, along with an isOdd one to complement it (since they are inherently complimentary ideas).

After it came up several a few times I began to wonder if this is the sort of thing that should be included in the Standard Library (although I admittedly have no idea how much this problem affects other Swift users)

However, simply extending Int wouldn't be appropriate for inclusion to the Standard Library; not just are there are a plethora of integer types, but it is also possible for users to define their own. Therefore, I recreated my extension for BinaryInteger, a type inherited by all integers in the Standard Library (and presumably all custom integers as well).

Outline

The extension of BinaryInteger (again, this protocol is inherited by all integers), would consist of two new computed variables, isOdd and isEven, both of which would be provided with a default implementation.

This default implementation is possible because BinaryInteger already incorporates remainder operations, which are essential for determining whether a number is odd or even.

While only one property is technically needed, it is syntactically correct to provide both properties, as it makes more sense and feels more "swifty"

Implementation
implementation modified from feedback from @saagarjha

extension BinaryInteger {
    ///Returns true whenever the integer is even, otherwise it will return false
    var isEven: Bool { return self % 2 == 0 }

    ///Returns true whenever the integer is odd, otherwise it will return false
    var isOdd: Bool { return self % 2 != 0 }
}

Reasoning

While this solution is relatively simple and there are plenty of existing workarounds for this issue, this solution provides a syntactically nice way of querying an integer to see whether it is odd or even (eg. if 2.isOdd { /*Do Something */ } ) as well as one that is automatically implemented in all existing integer types as well as any custom ones a user implements.

Additionally, simplicity in Swift is important: the remainder (%) operator, while one of the better known operators overall, is not one that will be immediately recognizable to those learning programming for their first time. Adding isOdd and isEven getters eliminates a common need for the operator, meaning that it could increase the amount of time before new developers would have to spend valuable time learning about it (in my own experience, which I realize will likely be different from others, the % operator is of little importance most of the time).

This section was added based on feedback from @Letan
There are alternatives to this implementation that could have VERY bad performance, although these would almost certainly be implemented only by someone ignorant of the remainder operator. For example, it is feasible to attempt a switch to get the same sort of operation (using the description property of an Int and the final Character of the resulting String).
Other mistakes could be feasibly made by someone more experienced: for example creating an isEven getter and having the isOdd getter reference it (eg. var isOdd: Bool { return !isEven} )

ABI Stability

This is a purely additive change, so it would have zero affect on ABI stability.

Because isOdd and isEven both receive default implementations, there would be no effort on the part of anyone to modify their custom integers (or for the Standard Library to change anything other than BinaryInteger). Additionally, adopting BinaryInteger would not increase in complexity.

5 Likes

any concrete binary integer type can be tested way faster with & 1 instead of the division

9 Likes

This isn't in any guidelines for including something in the Standard Library, but I think it would be helpful.

I think you got 1 and 2. Maybe 5, but a compiler should probably handle that case.

Luckily, LLVM is good enough to catch this for you: Compiler Explorer

5 Likes

Thanks for the feedback! I remembered having read something like that (although I didn't remember where) when creating the post. I'll try to address the other three points.

I'm not quite sold that this clears the high bar for inclusion in the standard library.

I disagree with your example here. % is far more versatile than a set of isEven/isOdd methods, and personally I don't feel that it's a very difficult concept to learn. Adding a property for purely pedagogic reasons, that will end up becoming essentially useless once new programmers learn about the % operator, doesn't feel like a particularly nice solution. You're free to define this in your own projects, but I feel that this just adds bloat for everyone else who doesn't fit into this category. I also don't quite agree with your goal here: yes, simplicity in Swift is important, but that doesn't mean we should just stop using operators.

P.S. Your implementation could be shorter, since your ternary just returns true and false:

extension BinaryInteger {
    ///Returns true whenever the integer is even, otherwise it will return false
    var isEven: Bool { return self % 2 == 0 }

    ///Returns true whenever the integer is odd, otherwise it will return false
    var isOdd: Bool { return self % 2 != 0 }
}

You make very excellent points.
I'm not disagreeing that % is useful, as are all operators. All operators are important, and nothing will ever replace their flexibility and power.
However, isOdd and isEven both provide provide syntactically nicer ways of writing if valueX % 2 == 0 {, which (in my personal opinion) is not as immediately clear in the intent. While there are plenty of perfectly valid remainder (and other operator) operations that shouldn't be accessed with getters, checking to see if a value is odd or even seems important enough and frequent enough to warrant the creation of such getters.
I cannot imagine that people should feel (or would be) forced to use isOdd and isEven; they would still be free to manually write out what was already available to them with any integer type.

1 Like

learn something new every day

(me: still writes >> 2 instead of / 4)

I think this would be a useful addition to the standard library. In fact, I started sketching out a pitch before I found this post! :slight_smile:

I've included my pitch below which includes more details about how it satisfies the criteria mentioned in the toggle proposal.

Let me know what you think @SiliconUnicorn and others. Maybe we can team up and see if we can get some momentum behind this.

Introduction

I propose adding var isEven: Bool and var isOdd: Bool to BinaryInteger. These are convenience properties for querying the parity of the integer.

Motivation

It is sometimes useful to know the evenness or oddness (parity) of an integer and switch the behaviour based on the result. The most typical way to do this is using value % 2 == 0 to determine if value is even, or value % 2 != 0 to determine if value is odd.

// Gray background for even rows, white for odd.
view.backgroundColor = indexPath.row % 2 == 0 ? .gray : .white

// Enable button if we have odd number of photos
buttonSave.isEnabled = photos.count % 2 != 0

It is also possible to use the bitwise AND operator (value & 1 == 0) which will inevitably lead to discussions about which one is faster and attempts at profiling them, etc, etc.

There are a few more specific motivations for this proposal:

Commonality

The need to determine the parity of an integer isn’t restricted to a limited problem domain.

Readability

This proposal significantly improves readability. There is no need to understand operator precedence rules (% has higher precedence than ==) which are non-obvious.

The properties are also fewer characters wide than the modulus approach (maximum 7 characters for .isEven vs 9 for % 2 == 0) which saves horizontal space while being clearer in intent.

view.backgroundColor = indexPath.row % 2 == 0 ? .gray : .white
view.backgroundColor = indexPath.row.isEven ? .gray : .white

buttonSave.isEnabled = photos.count % 2 != 0
buttonSave.isEnabled = photos.count.isOdd

Discoverability

Determining whether a value is even or odd is a common question across programming languages, at least based on these Stack Overflow questions:
c - How do I check if an integer is even or odd? 300,000+ views
java - Check whether number is even or odd 350,000+ views
Check if a number is odd or even in python 140,000+ views

IDEs will be able to suggest .isEven and .isOdd as part of autocomplete which will aid discoverability.

Consistency

It would be relatively easy to reproduce the properties in user code but there would be benefits to having a standard implementation. It may not be obvious to some users exactly which protocol these properties belong on (Int?, SignedInteger?, FixedWidthInteger?, BinaryInteger?). This inconsistency can be seen in a popular Swift utility library which defines these properties on SignedInteger which results in the properties being inaccessible for unsigned integers.

These properties will also eliminate the need to use modulus 2 and bitwise AND 1 to determine parity.

Adding isEven and isOdd is also consistent with the .isEmpty utility property, which is a convenience for .count == 0.

if array.count == 0 { ... }
if array.isEmpty { ... }

if value % 2 == 0 { ... }
if value.isEven { ... }

Correctness

There is a minor correctness risk in misinterpreting something like value % 2 == 0, particularly when used in a more complex statement, when compared to value.isEven.

Performance

This proposal likely won’t have a major positive impact on performance but it should not introduce any additional overhead thanks to @inlineable.

Proposed solution

Add two computed properties, isEven and isOdd, to BinaryInteger

extension BinaryInteger {
    @inlinable
    /// A Boolean value indicating whether this value is even.
    ///
    /// An integer is even if it is evenly divisible by two.
    public var isEven: Bool {
        return self % 2 == 0
    }

    @inlinable
    /// A Boolean value indicating whether this value is odd.
    ///
    /// An integer is odd if it is not evenly divisible by two.
    public var isOdd: Bool {
        return self % 2 != 0
    }
}

Similar functionality exists on Ruby’s Integer type: even? and odd?.

11 Likes

While we're there, how about isDivisible(by n: Int)?

3 Likes

+1, I think these would be a good addition.

It seems people write these little helpers quite frequently in their own projects — over 4,000 results for var isEven: Bool on Github:

8 Likes

over 4,000 results for var isEven: Bool on Github

Good point! At first glance, most seem to be extensions on Int which reinforces the point in the Consistency section regarding choosing the appropriate protocol to extend.

3 Likes

I'll note that this isn't quite the same as isEmpty because some Collections can compute isEmpty much faster than count. However, the same could be true for certain BinaryIntegers: it's possible that (self.words.first ?? 0) % 2 is faster than self % 2. (I'd want to be careful that that doesn't make the normal Int/UInt case compile to something worse, though.)

3 Likes

It's definitely the case that a isDivisible(by:) can be more efficient than checking that remainder == 0 in general, so I can see a reasonable argument for adding that API. isEven seems overly specific to me.

1 Like

For me, one calculation I encounter with moderate regularity is to find the euclidean remainder of two integers—that is, the smallest non-negative integer to which the first number is congruent modulo the second. This generally comes up in situations that involve placing numbers in equivalence classes.

Neither the % operator nor the quotientAndRemainder(dividingBy:) do what I need in these cases, because they can produce negative remainders. So if I were to suggest something with respect to the ergonomics of integer remainders, it would be to add euclidean division and remainder operations.

5 Likes

That would be nice, I don't know any language that does this properly though... not even Haskell. Not sure why this is the case. (I'm curious though, what kind of applications do you have that routinely deal with equivalence classes and modular arithmetic?).

There may well be lots of good uses out there that can be enumerated in a proposal, just saying I'd be wary of GitHub counts. My (unscientifically conducted) review of a sample of these 4,000 results suggests they are almost entirely:

  • unused extensions (i.e. lots of copies of the definition of isEven in the same IntExtensions.swift file, but hardly any calls to it); or
  • sample code using isEven as an example of a function being passed to a higher-order function like filter. Makes sense – it's my go-to example function to use for these purposes too! (But defining it as a property would be unsatisfactory for these purposes since I usually want to use it point-free :)

Several of the handful of real uses I could spot were ones that have better equivalents: for example, arc4random().isEven for a version of Bool.random().

6 Likes

I think this will be solved more generally by something like key paths converting to functions, so you don't have to litter free functions at the top level.

I totally agree with you about taking the Github counts with a grain of salt.

It's funny you should mention that. I was recently working through some tutorials in a book and the examples repeatedly used even/odd filtering to demonstrate the various concepts. Eventually I got fed up with looking at % 2 == 0 over and over and thought, "This would be clearer as .isEven..." so started writing this pitch :)

Interestingly, the top google result for "swift filter" (for me) also uses even/odd as the example:

let digits = [1,4,10,15]
let even = digits.filter { $0 % 2 == 0 }

It turns out that 3 of the top 6 search results I see for "swift filter" use % 2 == 0 in their demos.

It also pops up a lot in my copy of Advanced Swift, too :slight_smile:

2 Likes

As someone who has written an isEven() at some point in time, I think this is in line with Swift philosophy (as far as I've interpreted it) of declaring intentionality instead of method.

Back in Swift 2, C-style for loops were deprecated. It's not that the C construct is totally useless, but it has so many possible side effects. That means that each time you encounter a for loop, you had to actually scrutinise the entire declaration only to discover that in 99 % of cases, all it did was iterate linearly through a set of indices. That hampers readability of code since it doesn't really express its intent in the way as for i in 4...12 does.

The same goes for isEmpty, which declares the intention a lot more clearly than comparing the count of a collection, as well as opening the door to better-optimised code.

isOdd tells you what you need to know without actively analysing the intricacies of the modulo operation — something that LLVM must now do instead because so many even/odd solutions use division instead of binary logic.

6 Likes