The Elements of Programming Style, 2nd Edition

Author: Brian W. Kernighan, P. J. Plauger
All Stack Overflow 13
This Year Stack Overflow 2
This Month Stack Overflow 2


by anonymous   2017-08-20

In the example, the value 1.5F has an exact representation in IEEE 754 (and pretty much any other conceivable binary or decimal floating point representation), so the answer is almost certainly going to be yes. However, there is no guarantee, and there could be compilers which do not manage to achieve the result.

If you change the value to one without an exact binary representation, such as 5.1F, the result is far from guaranteed.

Way, way, way back in their excellent classic book "The Elements of Programming Style", Kernighan & Plauger said:

A wise programmer once said, "Floating point numbers are like sand piles; every time you move one, you lose a little sand and you pick up a little dirt". And after a few computations, things can get pretty dirty.

(It's one of two phrases in the book that I highlighted many years ago1.)

They also observe:

  • 10.0 times 0.1 is hardly ever 1.0.
  • Don't compare floating point numbers just for equality

Those observations were made in 1978 (for the second edition), but are still fundamentally valid today.

If the question is viewed at its most extremely restricted scope, you may be OK. If the question is varied very much, you are more likely to be bitten than not, and you'll probably be bitten sooner rather later.

1 The other highlighted phrase is (minus bullets):

  • the subroutine call permits us to summarize the irregularities in the argument list [...]
  • [t]he subroutine itself summarizes the regularities of the code [...]
by anonymous   2017-08-20

Basically, because the decimal number 0.01 does not have an exact representation in binary floating point, so over time, adding the best approximation to 0.01 deviates from the answer you'd like.

This is basic property of (binary) floating point arithmetic and not peculiar to Perl. What Every Computer Scientist Should Know About Floating-Point Arithmetic is the standard reference, and you can find it very easily with a Google search.

See also: C compiler bug (floating point arithmetic) and no doubt a myriad other questions.

Kernighan & Plauger say, in their old but classic book "The Elements of Programming Style", that:

  • A wise old programmer once said "floating point numbers are like little piles of sand; every time you move one, you lose a little sand and gain a little dirt".

They also say:

  • 10 * 0.1 is hardly ever 1.0

Both sayings point out that floating point arithmetic is not precise.

Note that some modern CPUs (IBM PowerPC) have IEEE 754:2008 decimal floating point arithmetic built-in. If Perl used the correct types (it probably doesn't), then your calculation would be exact.

by anonymous   2017-08-20

Sometimes if you can't come up with a good function name it's an indication that the function doesn't have a nice, crisp focus and needs to be refactored. If it's a class method, perhaps the class needs refactoring too.

But it's well worth the trouble finding the best possible names, since it makes your code so much more understandable and usable.

Update: Many software engineering authors have talked about the importance of naming. Henry F. Ledgard's Programming Proverbs (1975) and Brian Kernighan and P.J. Plaugher's Elements of Programming Style (1978) were early ones and are still worth reading. Steve McConnell's wonderful Code Complete (2nd Edition, 2005) is a more recent example, devoting an entire chapter to the topic.

Elements of Programming Style was in part patterned on Strunk and White's Elements of Style, which actually has a surprising relevance. Their stress on making prose clear and extremely concise applies to our technical writing and comments (and naming), but I've always seen it as analogous to what we do when we refactor and improve our code.

by anonymous   2017-08-20

Your code seems to prompt for the patron name on each iteration of the loop, which is an unusual way of organizing things. It should probably prompt for the names outside the loop. You should also error check the calls to scanf():

if (scanf("%.31s", f_name) != 1)
    ...break...or otherwise handle I/O problem...

We might need to see how you create the list of patrons within the library. It could be that you are storing the names in the same space each time, or something similar.

Your function always returns 0; there is no point that. Either it should not return any value (void) so you don't have to check what it returns, or you should make it return a struct Patron * for the found patron, or NULL (0) if there is no matching patron.

Your structures seem to me unexpectedly deeply nested. This fragment of code compiles, but I've not spent the effort to populate the list. However, providing 5 structures is 2 or 3 levels deeper than I'd expect. It definitely complicates your life.

#include <stdio.h>
#include <string.h>

struct Name
    char first[32];
    char last[32];

struct Patron
    struct Name name;

struct Patron_node
    struct Patron *patron;
    struct Patron_node *next;

struct Patron_list
    struct Patron_node *node;

struct Library
    struct Patron_list patrons;

int findpatron(struct Library* lib1, struct Patron **p_ptr)
    char f_name[32], l_name[32];
    struct Patron_node *currNode = lib1->patrons.node;
    printf("Enter the first name of patron: \n");
    if (scanf("%.31s", f_name) != 1)
        return -1;
    printf("Enter the last name of patron: \n");
    if (scanf("%.31s", l_name) != 1)
        return -1;
    while (currNode != NULL)
        if (strcmp(currNode->patron->name.first, f_name) == 0 &&
            strcmp(currNode->patron->name.last,  l_name) == 0)
            *p_ptr = currNode->patron;
        currNode = currNode->next;
    return 0;

I note I had to invent two structure names since they were not shown in your source code. At some point, you should look up the Law of Demeter and work out how to avoid violating it quite so flagrantly in your code.

For example:

int name_cmp_by_first(const struct Name *n1, const struct Name *n2)
    int rc = strcmp(n1->first, n2->first);
    if (rc == 0)
        rc = strcmp(n1->last, n2->last);
    return rc;

For the purpose at hand, it doesn't matter whether you compare first names first or last names first. In general, if you want to sort the data (for example), you would need to know which way to order them. I'm using the case-sensitive search you used; again, you might want to think about using a case-insensitive search instead. You can do that more easily when your comparisons are nicely separated and isolated as shown here.

int prompt_for_name(const char *prompt, char *name)
    printf("%s", prompt);
    if (scanf("%.31s", name) == 1)
        return 0;
    return -1;

int name_read(struct Name *name)
    if (prompt_for_name("first", name->first) == 0 &&
        prompt_for_name("last",  name->last)  == 0)
        return 0;
    return -1;

Notice that this avoids repetition in your code. Kernighan and Plauger summarized it neatly in their book 'The Elements of Programming Style':

  • The subroutine call permits us to summarize the irregularities in the argument list, where we can see quickly what is going on.
  • The subroutine itself summarizes the regularities of the code, so repeated patterns need not be used.

Then in your 'find_patron()` function:

struct Name to_be_found;
if (name_read(&to_be_found) != 0)
    return -1;  // Error return from function

while ...

    if (name_cmp_by_first(&name_to_be_found, currNode->patron->name) == 0)
        ...found the patron...

This is better; not fully clean, but better. A more realistic function would take in the name of the patron to be found and would search for that name in the list; you don't mix I/O operations such as reading the patrons name with searching operations. That is mixing up two very different operations.