Even and Odd Integers


(Saagar Jha) #4

Luckily, LLVM is good enough to catch this for you: https://godbolt.org/g/4UF5mo


#5

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.


(Saagar Jha) #6

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 }
}

#7

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.


(^) #8

learn something new every day

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


(Robert MacEachern) #9

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?.


'Double modulo' operator
(Pierpaolo Frasa) #11

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


(Cal Stephens) #12

+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:


(Robert MacEachern) #13

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.


(Jordan Rose) #14

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.)


(Steve Canon) #15

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.


#16

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.


'Double modulo' operator
(Pierpaolo Frasa) #17

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?).


(Ben Cohen) #18

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().


#19

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.


(Robert MacEachern) #20

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:


(Iggy Drougge) #21

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.


(Jacob Williams) #22

I know that some languages (not sure about swift), compile certain modulo expressions to the same assembly instructions as the binary logic and so there are no “performance optimizations” that could be made. Of course, this may not be the case for swift.


(Letanyan Arumugam) #23

It is. As can be seen on line 39 in the link. test bl, 1 is emitted, but only with -O flag.


(Jacob Williams) #24

So then I guess the binary version would be the one used in the potential design since we’d want the most optimized version no matter the compilation flags.