Thinking about the Python equivalents of C's !! double negation

July 10, 2007

C's !! double negation idiom is a way of compressing all non-zero values of an expression down into a 1. There's a number of things that you can use to get the same effect in Python:

  • if you know that your result is always zero or positive, you can use cmp(). I'm ambivalent about whether this is obscure usage; you're at least making it clear what you're doing, although probably not why.

    (If your result can be negative, you can use abs(cmp(...)).)

  • the minimalist version is just bool(...), because (currently) True and False can be directly used in arithmetic expressions.
  • writing int(bool(...)) has the same effect but makes it clear to readers (and programs like pylint and pychecker) that you're doing this deliberately.

I'm not sure which one I like better. Consider an expression like:

nbytes = nbits // 8 + int(bool(nbits % 8))

My C heritage says that this version is the clearest (and indeed it's the first one I thought of). I'd have to pause to think about the cmp() version, partly because I've yet to memorize which way cmp()'s arguments run so I always have to think about what its result will be.

I suspect that none of these would be considered good Python style, and that the proper Pythonic way to do this is to just use an if, or to write the whole thing as math.ceil(nbits / 8.0) and trust that floating point precision is not going to bite you.

(For some reason I have a small addiction to little overly clever Python idioms like this.)


Comments on this page:

By nothings at 2007-07-10 22:28:15:

(nbits + 7) // 8

?

At least that's how I'd do it in C.

By cks at 2007-07-10 23:43:32:

Even for the nbytes case, I think I'd probably stick with my version in my own code; it's clearer to me what's going on. And in the general case there aren't always simplifications like that (although I'll have to remember this one; I'm rusty on that sort of thing).

From 65.172.155.230 at 2007-07-11 00:59:38:

As a long time C coder[1] ... if I had to do the same in python, and I didn't want to use the if inline, I'd almost certainly create a function:

  def ibool(x):
    """ Convert x into a 1 or a 0. """
    if x:
      return 1
    return 0

  nbytes = (nbits // 8 + ibool(nbits % 8))

...because it's just more convenient to create simple ones like that in python.

[1] Still more than python, in fact I had to stare at the nbytes expression a couple of seconds before I remembered that "//" is an operator and not a comment (I guess I should start using that more, before python 3000 forces me too :)

By nothings at 2007-07-11 03:16:07:

The thing is, (nbits+7)/8 generalizes in a mathematically clean way: (nbits+k)/8. You can generalize the boolean one: nbits/8 + (n%8 >= k'), but it obfuscates what's going on mathematically; it's much easier to integrate the former into mathematical reasoning than the latter.

I can see arguing for either one, but to me (nbits+7)/8 is basically HOW I think of floor and ceiling.

Let me illustrate both of these points with an example.

Most people know you can define round(x) as floor(x+0.5). So round(x_int / 8) is floor(x__int / 8 + 0.5) is floor((x_int + 4) / 8) is (x_int+4)//8. ceil() is floor(x + 1 - epsilon), so a similar logic can be used to generate my original solution.

I agree of course that your point about the general issue of how to get a number from a boolean is interesting and useful, and you're right, I was just taking exception to this example rather than addressing your point.

In general in C I've moved away from using !!x if I want to treat the result as an integer. (I might use !! to force something to be literally 0 or 1 just because some other API takes a boolean and I want to be on the safe side.) Instead if math is involved I tend to use a ternary operation; I'll write 3*(x ? 1 : 0) before I'll write 3*!!x -- and of course I'll tend to try to distribute out the ternary as much as possible. In this case, if I want either 0 or 3, I don't conceptualize it as 'get 0 or 1, and multiply by 3', I conceptualize it as (x ? 3 : 0). I find this tends to be easier to read and understand, since it's more of a 'say what you mean, don't try to be clever' ism. I vaguely recall you had a previous python post about how to achieve an equivalent operation, though, so if I'm remember right maybe that approach is less effective in python.

(I know I've complained before, but jesus DWikiText is the hardest-to-use plain-text-markup system I've ever used. [[_|]] for _ ?! )

From 87.79.236.202 at 2007-07-11 08:20:25:

I agree with the previous commenter: a ternary is the way to go if you want to be clear. The typecast is OK in the sense that it’s not obscure, but I still think it does not actually express intent directly.

For some reason I have a small addiction to little overly clever Python idioms like this.

See, it’s not just us Perl folk. :-) (I mostly grew out of that phase several years ago, but when I was new to Perl I revelled in such trickery just as much. No doubt I’ll soon be unreasonably enamoured with it in Haskell. It’s part of the journey.)

Aristotle Pagaltzis

By Dan.Astoorian at 2007-07-11 12:14:35:

cmp() would be, IMHO, poor form: it is documented only to return a negative, zero, or positive integer--not -1, 0 or 1 specifically, even though int.__cmp__() happens to do so.

One might (though I probably wouldn't) perhaps consider int(n.__nonzero__()) instead of int(bool(n)); the latter calls the former, but __nonzero__ expresses what you're really testing for and is marginally faster.

Even faster than that, perhaps surprisingly, seems to be 0**(0**abs(n)) (ugh), which avoids boolean types altogether. But an if...then...else would be faster still, and probably clearer.

--Dan

By cks at 2007-07-19 18:30:38:

I can see arguing for either one, but to me (nbits+7)/8 is basically HOW I think of floor and ceiling.

I'm not arguing for my version in general, for everyone; I am saying that my version is what I thought of, so clearly it is clearer for me, at least right now. (If I internalize your idiom strongly enough, that may change.)

On the markup: part of the problem is that DWiki's usage has drifted since I picked the markup characters. In this case, underscore does not come up very often when writing about Unix commands but comes up all the time when writing about identifiers in programming languages.

(I have toyed with ways of overriding underscore but have yet to be satisfied enough with what I've come up with to commit to it.)

Written on 10 July 2007.
« How many bits of information are in a password?
Why I wish Python had assignment in conditionals »

Page tools: View Source, View Normal, Add Comment.
Search:
Login: Password:
Atom Syndication: Recent Comments.

Last modified: Tue Jul 10 21:30:48 2007
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.