crc checksums

Thursday, 16 June 2011
Header ae_crc.h and C++ source file ae_crc.cc contain C++ types and methods shown on this page. This is the C++ version of cy_crc64 on wiki page CeeHtmlEscape, except 32-bit and 128-bit hashes appear as well. We use zlib for crc32().

Type Zcrc128 below is not really a crc—it's a misnomer. Instead it's just two 64-bit checksums, which is not the same thing. Assume it's a placeholder for a real 128-bit crc.

Note I like using CRC to hash symbols put into hashmaps, based on collision resistance. I recommend using Zcrc32 based on zlib's crc32() for things like compiler symbol tables. A 64-bit version might be better for huge populations in which you want rare hash collisions. (Read about the birthday paradox for an intro to probability related to hash collisions. We expect an average of one collision in 2^16 hashes with a good 32-bit hash, and we expect about one collision per 2^32 hashes with a good 64-bit hash. The general rule is one collision expected per half the number of bits.)

You can find web pages extolling the virtues of hash functions. But don't believe them without data. Here's how to collect data you want: write a closed/probing hashmap of large capacity, resolving collisions by probing. Find a big dictionary of things to hash—a 250K word dictionary, or better yet an actual population sample you intend to hash. Size your hashmap so it has either 50% more slots than what you add, or perhaps only 25% more slots.

If your hashmap has M slots and N keys added to it, where N is less than M, then no matter what hash function you use, the average number of slots used is going to be the same: N/M. So the interesting thing is distribution of unused slots, which I call holes. You want unused holes in a probing hashmap distributed as uniformly as possible, because it's finding a hole that terminates a hashmap lookup search. A great hash function distributes holes more uniformly than a poor hash function. Touching fewer cache lines is faster.

Other than speed of course, we can rank hash functions by how uniformly holes are distributed in a probing hashmap. How can we measure "uniformity" of hole distribution? By looking at standard deviation of distance between holes. The average distance is fixed, of course, when you add the same number of unique keys. But for different hash functions, standard deviation of distance between holes will differ. The smaller the standard deviation, the better.

In my tests comparing crc with other hash functions, crc won. It always had a smaller standard deviation of distance between holes, compared to other highly touted hash functions. As far as I'm concerned, this means crc is definitively a better hash function than others with a higher standard deviation in distance between probing hashmap holes.

A bakeoff between hash functions with sufficient speed is easy: standard deviation. Of course you need code to calculate that, which I'll post later.

tests and retraction

Monday, 20 June 2011
In recreating hash tests to see standard deviation in gaps between unused slot holes in probing hashmaps, I'm not getting the same results I recall. So I'm not seeing crc do consistently better than other hashes this time. Results are typically quite close, and sometimes another hash has a lower standard deviation for distance between holes—by a small amount anyway.

So I retract my "crc wins" comments above. So far I get variation depending on data set used, and how full I'm willing to fill a hashmap. I no longer have the same data set I used a while back. My current largest dict of natural language words is in German, which suits me fine. When you study the tests, or clone them to try this yourself, you'll get a lot of things to think about.

The zlib version of crc32() is typically fastest by a large margin, and sometimes it has a fractionally lower standard deviation in distance between holes. But when a hashmap is filled less, and when a data set is English words, sometimes oat and fnv have smaller standard deviations in distance between holes. So no, crc does not clearly dominate there too.

header file

Thursday, 16 June 2011
We begin with the interface:
#ifndef ae_crc_H
#define ae_crc_H 1

#include <string.h>
#include "zlib.h"

#ifndef ae_u8_H
#include "ae_u8.h"
#endif

#ifndef ae_iov_H
#include "ae_iov.h"
#endif
Note ae_crc_test_main() at the end of this page is a simple command line tool using all four hashes below, depending on bit size you choose with the -b command line option.
extern "C" {
int ae_crc_test_main(int argc, const char* const* argv);
}

namespace ae {
Type Zcrc32 is a thin wrapper around crc32() in zlib, which calculates a fairly good 32-bit cyclic redundancy check, and does so quickly because it uses multiple lookup tables and not just one. This does make thorn depend on zlib though.
class Zcrc32 { // crc32 based on zlib's crc32()
private:
    u32  z_len; // number of bytes added to crc
    u32  z_crc; // crc from applying crc32() to z_len bytes

    void _init() {
        z_len = 0;
        z_crc = (u32)crc32(0L, 0, 0);
    }
public:
    u32 zlen() const { return z_len; }
    u32 zcrc() const { return z_crc; }
    operator unsigned long() const { return z_crc; }

    void zadd(const char* s) {
        u32 n = ::strlen(s);
        z_crc = crc32(z_crc, (Bytef*) s, n);
        z_len += n;
    }
    void zadd(Iov const& src) {
        u32 n = src.iov_len;
        z_crc = crc32(z_crc, (Bytef*) src.iov_base, n);
        z_len += n;
    }
    void zadd(const void* p, u32 n) {
        z_crc = crc32(z_crc, (Bytef*) p, n);
        z_len += n;
    }
    Zcrc32() { _init(); }
    Zcrc32(Iov const& src) {
        _init();
        if (src.iov_len && src.iov_base) {
            this->zadd(src);
        }
    }
    Zcrc32(const void* p, u32 n) {
        _init();
        if (p && n) {
            zadd(p, n);
        }
    }
    Zcrc32& operator<<(const char* s) {
        if (s) { 
            zadd(s);
        }
        return *this;
    }
    Zcrc32& operator<<(Iov const& v) {
        if (v.iov_base && v.iov_len) {
            zadd(v);
        }
        return *this;
    }
    void zcite(Sink& o) const;
}; // Zcrc32
inline Sink& operator<<(Sink& o, DumpT<Zcrc32> const& x) {
    x.d_t.zcite(o);
    return o;
}
inline Sink& operator<<(Sink& o, CiteT<Zcrc32> const& x) {
    x.c_t.zcite(o);
    return o;
}
    
/* ----- Zcrc64 ----- */
Type Zcrc64 below aims to have the same api as Zcrc32 above, but calculates a good 64-bit crc instead of only 32 bits. Prefix Z makes far less sense when zlib is not used, true, but at least the naming scheme is consistent.

Speed of this 64-bit crc code is substantially slower than the 32-bit version for two reasons. First, it uses only one lookup table instead of four, and this does matter in practice. Second, code uses 64-bit arithmetic, and gcc notoriously does not generate as fast code as a modestly skilled developer might do by hand, when it comes to 64-bit arithmetic. So if you make a product using a 64-bit crc, you want to optimize it more then code you see below.
class Zcrc64 { // using ECMA-182
private:
    u64 z_crc;
    u32 z_len;
    void _zinit() { z_len = 0; z_crc = (u64) 0xFFFFFFFFFFFFFFFFULL; }
    static u64 _zadd64(u64 crc, const void* p, u32 n);
    static u64 _zsub64(u64 crc, const void* p, u32 n);
Enum constant kpoly is one version of a ECMA-182's 64-bit crc polynomial:
    static const u64 kpoly = 0x42f0e1eba9ea3693ULL; // ECMA-182
Enum constant kMirror is the bitwise reflection of kpoly above:
    // kMirror: bitwise reflection of kpoly
    static const u64 kMirror = 0xc96c5795d7870f42ULL; // ECMA-182
    static u64 _zmir64(u64 crc, const void* p, u32 n);
public:
    Zcrc64() { this->_zinit(); }
    Zcrc64(const void* p, u32 n) {
        this->_zinit();
        z_crc = this->_zadd64(z_crc, p, n);
    }
Method zclear() reconstructs this object again from scratch:
    void zclear() { // make same as just constructed
        this->_zinit();
    }
    static void zterms(); // print poly terms
    u32 zlen() const { return z_len; }
    u64 zcrc() const { return ~z_crc; }
    operator unsigned long long() const { return ~z_crc; }
    
    void zmir(Iov const& src) {
        u32 n = src.iov_len; // note: do NOT mix with zadd() or zcut()
        z_crc = _zmir64(z_crc, src.iov_base, n);
        z_len += n;
    }
    void zcut(Iov const& src) {
        u32 n = src.iov_len; // note: do NOT mix with zadd() calls
        z_crc = _zsub64(z_crc, src.iov_base, n);
        z_len += n;
    }
    void zadd(const void* p, u32 n) {
        z_crc = _zadd64(z_crc, p, n);
        z_len += n;
    }
    void zadd(Iov const& src) {
        u32 n = src.iov_len;
        z_crc = _zadd64(z_crc, src.iov_base, n);
        z_len += n;
    }
    void zadd(const char* s) {
        if (s) {
            u32 n = ::strlen(s);
            z_crc = _zadd64(z_crc, s, n);
            z_len += n;
        }
    }
    Zcrc64& operator<<(const char* s) {
        if (s) {
            u32 n = ::strlen(s);
            z_crc = _zadd64(z_crc, s, n);
            z_len += n;
        }
        return *this;
    }
    Zcrc64& operator<<(Iov const& v) {
        u32 n = v.iov_len;
        if (v.iov_base && n) {
            z_crc = _zadd64(z_crc, v.iov_base, n);
            z_len += n;
        }
        return *this;
    }
    void zcite(Sink& o) const;
}; // Zcrc64
inline Sink& operator<<(Sink& o, DumpT<Zcrc64> const& x) {
    x.d_t.zcite(o);
    return o;
}
inline Sink& operator<<(Sink& o, CiteT<Zcrc64> const& x) {
    x.c_t.zcite(o);
    return o;
}
    
/* ----- Zcrc128 ----- */
Type Zcrc218 below is not really a crc—it's actually two 64-bit crcs pasted together, for a total of 128-bits worth of good, fast, collision resistant hash. We expect collisions are very rare by chance alone, unless you craft hash collisions by hand—fairly easy for a non-cryptographic hash like crc. You just need to analyze whether there's an attack scenario.

If you test bloom filters with large data sets, you want a lot of good quality 32-bit hashes, and you can get four from Zcrc128. (I did a lot of bloom filter research, but I'm not allowed to tell you my results, or questions I asked. But I spent a lot of time studying what happens when you remove keys from bloom filters, since literature rarely discusses that, to see if a fruitful result ever occurs when you organize things differently. It's time consuming.)

I should not offer you advice on when to use a 64-bit hash vs a 128-bit hash. But you can't rule out using a 64-bit hash, if you're willing to cope with collisions. Cost of handling collisions can be less than space cost from doubling hash size.
class Zcrc128 { // (ACTUALLY just TWO appended hashes)
private:
    u128 z_crc; // two DIFFERENT Zcrc64 values
    u32  z_len;
    void _zinit() {
        z_len = 0;
        z_crc.u.m_u64[0] = 0xFFFFFFFFFFFFFFFFULL;
        z_crc.u.m_u64[1] = 0xFFFFFFFFFFFFFFFFULL;
    }
    // _zadd128() modifies botyh z_crc and z_len
    void _zadd128(const void* p, u32 n);
public:
    Zcrc128() { this->_zinit(); }
    Zcrc128(const char* s) {
        this->_zinit();
        if (s) {
            unsigned n = strlen(s);
            if (n) {
                this->_zadd128(s, n);
            }
        }
    }
    Zcrc128(const void* p, u32 n) {
        this->_zinit();
        if (p && n) {
            this->_zadd128(p, n);
        }
    }
    Zcrc128(Iov const& v) {
        this->_zinit();
        if (v.iov_base && v.iov_len) {
            this->_zadd128(v.iov_base, v.iov_len);
        }
    }
    u32 zlen() const { return z_len; }
    
    operator u128() const {
        u128 inverse; // one's complement of z_crc
        inverse.u.m_u64[0] = ~z_crc.u.m_u64[0];
        inverse.u.m_u64[1] = ~z_crc.u.m_u64[1];
        return inverse;
    }
    u128 zcrc() const {
        u128 inverse; // one's complement of z_crc
        inverse.u.m_u64[0] = ~z_crc.u.m_u64[0];
        inverse.u.m_u64[1] = ~z_crc.u.m_u64[1];
        return inverse;
    }
    void zadd(const void* p, unsigned n) {
        this->_zadd128(p, n);
    }
    void zadd(Iov const& src) {
        this->_zadd128(src.iov_base, src.iov_len);
    }
    Zcrc128& operator<<(Iov const& v) {
        if (v.iov_base && v.iov_len) {
            _zadd128(v.iov_base, v.iov_len);
        }
        return *this;
    }
    Zcrc128& operator<<(const char* s) {
        if (s) {
            this->_zadd128(s, ::strlen(s));
        }
        return *this;
    }
    void zcite(Sink& o) const;
}; // Zcrc128
inline Sink& operator<<(Sink& o, DumpT<Zcrc128> const& x) {
    x.d_t.zcite(o);
    return o;
}
inline Sink& operator<<(Sink& o, CiteT<Zcrc128> const& x) {
    x.c_t.zcite(o);
    return o;
}
The following 256-bit has code was added late in a page revision:

256-bit checksums

Saturday, 18 June 2011
Type Zcrc256 below is not really a crc—it's actually four 64-bit crcs pasted together, for a total of 256-bits worth of good, fast, collision resistant hash. (Except of course this amount of arithmetic isn't really very fast.)

I added this 256-bit hash only to illustrate 64-bit reflected polynomial kMirror, since it gives us another 128 bits of hash if we (once again) use the new lookup table two different ways. I can't imagine a practical use of this code outside testing.
/* ----- Zcrc256 ----- */

class Zcrc256 { // (ACTUALLY just FOUR appended hashes)
private:
    u256 z_crc; // four DIFFERENT Zcrc64 values
    u32  z_len;
    void _zinit() {
        z_len = 0;
        z_crc.u.m_u64[0] = 0xFFFFFFFFFFFFFFFFULL;
        z_crc.u.m_u64[1] = 0xFFFFFFFFFFFFFFFFULL;
        z_crc.u.m_u64[2] = 0xFFFFFFFFFFFFFFFFULL;
        z_crc.u.m_u64[3] = 0xFFFFFFFFFFFFFFFFULL;
    }
    // _zadd256() modifies both z_crc and z_len
    void _zadd256(const void* p, u32 n);
public:
    Zcrc256() { this->_zinit(); }
    Zcrc256(const char* s) {
        this->_zinit();
        if (s) {
            unsigned n = strlen(s);
            if (n) {
                this->_zadd256(s, n);
            }
        }
    }
    Zcrc256(const void* p, u32 n) {
        this->_zinit();
        if (p && n) {
            this->_zadd256(p, n);
        }
    }
    Zcrc256(Iov const& v) {
        this->_zinit();
        if (v.iov_base && v.iov_len) {
            this->_zadd256(v.iov_base, v.iov_len);
        }
    }
    u32 zlen() const { return z_len; }
    
    operator u256() const {
        u256 inverse; // one's complement of z_crc
        inverse.u.m_u64[0] = ~z_crc.u.m_u64[0];
        inverse.u.m_u64[1] = ~z_crc.u.m_u64[1];
        inverse.u.m_u64[2] = ~z_crc.u.m_u64[2];
        inverse.u.m_u64[3] = ~z_crc.u.m_u64[3];
        return inverse;
    }
    u256 zcrc() const {
        u256 inverse; // one's complement of z_crc
        inverse.u.m_u64[0] = ~z_crc.u.m_u64[0];
        inverse.u.m_u64[1] = ~z_crc.u.m_u64[1];
        inverse.u.m_u64[2] = ~z_crc.u.m_u64[2];
        inverse.u.m_u64[3] = ~z_crc.u.m_u64[3];
        return inverse;
    }
    void zadd(const void* p, unsigned n) {
        this->_zadd256(p, n);
    }
    void zadd(Iov const& src) {
        this->_zadd256(src.iov_base, src.iov_len);
    }
    Zcrc256& operator<<(Iov const& v) {
        if (v.iov_base && v.iov_len) {
            _zadd256(v.iov_base, v.iov_len);
        }
        return *this;
    }
    Zcrc256& operator<<(const char* s) {
        if (s) {
            this->_zadd256(s, ::strlen(s));
        }
        return *this;
    }
    void zcite(Sink& o) const;
}; // Zcrc256
inline Sink& operator<<(Sink& o, DumpT<Zcrc256> const& x) {
    x.d_t.zcite(o);
    return o;
}
inline Sink& operator<<(Sink& o, CiteT<Zcrc256> const& x) {
    x.c_t.zcite(o);
    return o;
}

}; // namespace ae

//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789

#endif /* ae_crc_H */
That's the end of header ae_crc.h, so now we look at ae_crc.cc.
/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4 -*- */
/* Copyright (c) 1993-2011 David (Rys) McCusker, BSD license */

#include <stdio.h>

#ifndef ae_crc_H
#include "ae_crc.h"
#endif

#ifndef ae_u8Map_H
#include "ae_u8Map.h"
#endif

#ifndef ae_sink_H
#include "ae_sink.h"
#endif
Method zterms() below prints the lookup table appearing further below. There's no reason to print it again, except to see whether code for the lookup table has been altered (or to see if your polynomical has changed, or code using it).
namespace ae {

void Zcrc64::zterms() { // prints Crc64_terms[] shown below
    u64 table[256 + 1];
    u64* vec = table;
    for (u64 i = 0; i < 256; ++i) {
        u64 t = i << 56;
        int bits = 8 + 1;
        while (--bits) {
            t = (t & 0x8000000000000000ULL)? (t << 1) ^ kpoly : (t << 1);
        }
        vec[ i ] = t;
    }
    printf("const uint64_t g_Zcrc64_terms[] = {\n");
    u64* end = table + 256;
    for (vec -= 4; (vec += 4) < end; ) {
        printf(" 0x%016llxULL, 0x%016llxULL, 0x%016llxULL, 0x%016llxULL,\n",
                  (long long) vec[ 0 ], (long long) vec[ 1 ],
                  (long long) vec[ 2 ], (long long) vec[ 3 ]);
    }
    printf(" 0\n};\n");
    fflush(stdout);
}
Method zmirror() below prints the reflected polynomial lookup table shown further below. There's no reason to print it again, except to see whether code for the lookup table has been altered (or to see if your polynomical has changed, or code using it).
void Zcrc64::zmirror() { // prints g_Zcrc64_mirror[] shown below
    u64 table[256 + 1];
    u64* vec = table;
    for (u64 i = 0; i < 256; ++i) {
        u64 t = i << 56;
        int bits = 8 + 1;
        while (--bits) {
            t = (t & 0x8000000000000000ULL)? (t << 1) ^ kMirror : (t << 1);
        }
        vec[ i ] = t;
    }
    printf("const uint64_t g_Zcrc64_mirror[] = {\n");
    u64* end = table + 256;
    for ( ; vec < end; vec += 4) {
        printf(" 0x%016llxULL, 0x%016llxULL, 0x%016llxULL, 0x%016llxULL,\n",
               (long long) vec[ 0 ], (long long) vec[ 1 ],
               (long long) vec[ 2 ], (long long) vec[ 3 ]);
    }
    printf(" 0\n};\n");
    fflush(stdout);
}
This table should match what is printed by zterms() above:
const uint64_t g_Zcrc64_terms[] = {
 0x0000000000000000ULL, 0x42f0e1eba9ea3693ULL, 0x85e1c3d753d46d26ULL, 0xc711223cfa3e5bb5ULL,
 0x493366450e42ecdfULL, 0x0bc387aea7a8da4cULL, 0xccd2a5925d9681f9ULL, 0x8e224479f47cb76aULL,
 0x9266cc8a1c85d9beULL, 0xd0962d61b56fef2dULL, 0x17870f5d4f51b498ULL, 0x5577eeb6e6bb820bULL,
 0xdb55aacf12c73561ULL, 0x99a54b24bb2d03f2ULL, 0x5eb4691841135847ULL, 0x1c4488f3e8f96ed4ULL,
 0x663d78ff90e185efULL, 0x24cd9914390bb37cULL, 0xe3dcbb28c335e8c9ULL, 0xa12c5ac36adfde5aULL,
 0x2f0e1eba9ea36930ULL, 0x6dfeff5137495fa3ULL, 0xaaefdd6dcd770416ULL, 0xe81f3c86649d3285ULL,
 0xf45bb4758c645c51ULL, 0xb6ab559e258e6ac2ULL, 0x71ba77a2dfb03177ULL, 0x334a9649765a07e4ULL,
 0xbd68d2308226b08eULL, 0xff9833db2bcc861dULL, 0x388911e7d1f2dda8ULL, 0x7a79f00c7818eb3bULL,
 0xcc7af1ff21c30bdeULL, 0x8e8a101488293d4dULL, 0x499b3228721766f8ULL, 0x0b6bd3c3dbfd506bULL,
 0x854997ba2f81e701ULL, 0xc7b97651866bd192ULL, 0x00a8546d7c558a27ULL, 0x4258b586d5bfbcb4ULL,
 0x5e1c3d753d46d260ULL, 0x1cecdc9e94ace4f3ULL, 0xdbfdfea26e92bf46ULL, 0x990d1f49c77889d5ULL,
 0x172f5b3033043ebfULL, 0x55dfbadb9aee082cULL, 0x92ce98e760d05399ULL, 0xd03e790cc93a650aULL,
 0xaa478900b1228e31ULL, 0xe8b768eb18c8b8a2ULL, 0x2fa64ad7e2f6e317ULL, 0x6d56ab3c4b1cd584ULL,
 0xe374ef45bf6062eeULL, 0xa1840eae168a547dULL, 0x66952c92ecb40fc8ULL, 0x2465cd79455e395bULL,
 0x3821458aada7578fULL, 0x7ad1a461044d611cULL, 0xbdc0865dfe733aa9ULL, 0xff3067b657990c3aULL,
 0x711223cfa3e5bb50ULL, 0x33e2c2240a0f8dc3ULL, 0xf4f3e018f031d676ULL, 0xb60301f359dbe0e5ULL,
 0xda050215ea6c212fULL, 0x98f5e3fe438617bcULL, 0x5fe4c1c2b9b84c09ULL, 0x1d14202910527a9aULL,
 0x93366450e42ecdf0ULL, 0xd1c685bb4dc4fb63ULL, 0x16d7a787b7faa0d6ULL, 0x5427466c1e109645ULL,
 0x4863ce9ff6e9f891ULL, 0x0a932f745f03ce02ULL, 0xcd820d48a53d95b7ULL, 0x8f72eca30cd7a324ULL,
 0x0150a8daf8ab144eULL, 0x43a04931514122ddULL, 0x84b16b0dab7f7968ULL, 0xc6418ae602954ffbULL,
 0xbc387aea7a8da4c0ULL, 0xfec89b01d3679253ULL, 0x39d9b93d2959c9e6ULL, 0x7b2958d680b3ff75ULL,
 0xf50b1caf74cf481fULL, 0xb7fbfd44dd257e8cULL, 0x70eadf78271b2539ULL, 0x321a3e938ef113aaULL,
 0x2e5eb66066087d7eULL, 0x6cae578bcfe24bedULL, 0xabbf75b735dc1058ULL, 0xe94f945c9c3626cbULL,
 0x676dd025684a91a1ULL, 0x259d31cec1a0a732ULL, 0xe28c13f23b9efc87ULL, 0xa07cf2199274ca14ULL,
 0x167ff3eacbaf2af1ULL, 0x548f120162451c62ULL, 0x939e303d987b47d7ULL, 0xd16ed1d631917144ULL,
 0x5f4c95afc5edc62eULL, 0x1dbc74446c07f0bdULL, 0xdaad56789639ab08ULL, 0x985db7933fd39d9bULL,
 0x84193f60d72af34fULL, 0xc6e9de8b7ec0c5dcULL, 0x01f8fcb784fe9e69ULL, 0x43081d5c2d14a8faULL,
 0xcd2a5925d9681f90ULL, 0x8fdab8ce70822903ULL, 0x48cb9af28abc72b6ULL, 0x0a3b7b1923564425ULL,
 0x70428b155b4eaf1eULL, 0x32b26afef2a4998dULL, 0xf5a348c2089ac238ULL, 0xb753a929a170f4abULL,
 0x3971ed50550c43c1ULL, 0x7b810cbbfce67552ULL, 0xbc902e8706d82ee7ULL, 0xfe60cf6caf321874ULL,
 0xe224479f47cb76a0ULL, 0xa0d4a674ee214033ULL, 0x67c58448141f1b86ULL, 0x253565a3bdf52d15ULL,
 0xab1721da49899a7fULL, 0xe9e7c031e063acecULL, 0x2ef6e20d1a5df759ULL, 0x6c0603e6b3b7c1caULL,
 0xf6fae5c07d3274cdULL, 0xb40a042bd4d8425eULL, 0x731b26172ee619ebULL, 0x31ebc7fc870c2f78ULL,
 0xbfc9838573709812ULL, 0xfd39626eda9aae81ULL, 0x3a28405220a4f534ULL, 0x78d8a1b9894ec3a7ULL,
 0x649c294a61b7ad73ULL, 0x266cc8a1c85d9be0ULL, 0xe17dea9d3263c055ULL, 0xa38d0b769b89f6c6ULL,
 0x2daf4f0f6ff541acULL, 0x6f5faee4c61f773fULL, 0xa84e8cd83c212c8aULL, 0xeabe6d3395cb1a19ULL,
 0x90c79d3fedd3f122ULL, 0xd2377cd44439c7b1ULL, 0x15265ee8be079c04ULL, 0x57d6bf0317edaa97ULL,
 0xd9f4fb7ae3911dfdULL, 0x9b041a914a7b2b6eULL, 0x5c1538adb04570dbULL, 0x1ee5d94619af4648ULL,
 0x02a151b5f156289cULL, 0x4051b05e58bc1e0fULL, 0x87409262a28245baULL, 0xc5b073890b687329ULL,
 0x4b9237f0ff14c443ULL, 0x0962d61b56fef2d0ULL, 0xce73f427acc0a965ULL, 0x8c8315cc052a9ff6ULL,
 0x3a80143f5cf17f13ULL, 0x7870f5d4f51b4980ULL, 0xbf61d7e80f251235ULL, 0xfd913603a6cf24a6ULL,
 0x73b3727a52b393ccULL, 0x31439391fb59a55fULL, 0xf652b1ad0167feeaULL, 0xb4a25046a88dc879ULL,
 0xa8e6d8b54074a6adULL, 0xea16395ee99e903eULL, 0x2d071b6213a0cb8bULL, 0x6ff7fa89ba4afd18ULL,
 0xe1d5bef04e364a72ULL, 0xa3255f1be7dc7ce1ULL, 0x64347d271de22754ULL, 0x26c49cccb40811c7ULL,
 0x5cbd6cc0cc10fafcULL, 0x1e4d8d2b65facc6fULL, 0xd95caf179fc497daULL, 0x9bac4efc362ea149ULL,
 0x158e0a85c2521623ULL, 0x577eeb6e6bb820b0ULL, 0x906fc95291867b05ULL, 0xd29f28b9386c4d96ULL,
 0xcedba04ad0952342ULL, 0x8c2b41a1797f15d1ULL, 0x4b3a639d83414e64ULL, 0x09ca82762aab78f7ULL,
 0x87e8c60fded7cf9dULL, 0xc51827e4773df90eULL, 0x020905d88d03a2bbULL, 0x40f9e43324e99428ULL,
 0x2cffe7d5975e55e2ULL, 0x6e0f063e3eb46371ULL, 0xa91e2402c48a38c4ULL, 0xebeec5e96d600e57ULL,
 0x65cc8190991cb93dULL, 0x273c607b30f68faeULL, 0xe02d4247cac8d41bULL, 0xa2dda3ac6322e288ULL,
 0xbe992b5f8bdb8c5cULL, 0xfc69cab42231bacfULL, 0x3b78e888d80fe17aULL, 0x7988096371e5d7e9ULL,
 0xf7aa4d1a85996083ULL, 0xb55aacf12c735610ULL, 0x724b8ecdd64d0da5ULL, 0x30bb6f267fa73b36ULL,
 0x4ac29f2a07bfd00dULL, 0x08327ec1ae55e69eULL, 0xcf235cfd546bbd2bULL, 0x8dd3bd16fd818bb8ULL,
 0x03f1f96f09fd3cd2ULL, 0x41011884a0170a41ULL, 0x86103ab85a2951f4ULL, 0xc4e0db53f3c36767ULL,
 0xd8a453a01b3a09b3ULL, 0x9a54b24bb2d03f20ULL, 0x5d45907748ee6495ULL, 0x1fb5719ce1045206ULL,
 0x919735e51578e56cULL, 0xd367d40ebc92d3ffULL, 0x1476f63246ac884aULL, 0x568617d9ef46bed9ULL,
 0xe085162ab69d5e3cULL, 0xa275f7c11f7768afULL, 0x6564d5fde549331aULL, 0x279434164ca30589ULL,
 0xa9b6706fb8dfb2e3ULL, 0xeb46918411358470ULL, 0x2c57b3b8eb0bdfc5ULL, 0x6ea7525342e1e956ULL,
 0x72e3daa0aa188782ULL, 0x30133b4b03f2b111ULL, 0xf7021977f9cceaa4ULL, 0xb5f2f89c5026dc37ULL,
 0x3bd0bce5a45a6b5dULL, 0x79205d0e0db05dceULL, 0xbe317f32f78e067bULL, 0xfcc19ed95e6430e8ULL,
 0x86b86ed5267cdbd3ULL, 0xc4488f3e8f96ed40ULL, 0x0359ad0275a8b6f5ULL, 0x41a94ce9dc428066ULL,
 0xcf8b0890283e370cULL, 0x8d7be97b81d4019fULL, 0x4a6acb477bea5a2aULL, 0x089a2aacd2006cb9ULL,
 0x14dea25f3af9026dULL, 0x562e43b4931334feULL, 0x913f6188692d6f4bULL, 0xd3cf8063c0c759d8ULL,
 0x5dedc41a34bbeeb2ULL, 0x1f1d25f19d51d821ULL, 0xd80c07cd676f8394ULL, 0x9afce626ce85b507ULL,
 0
};
This table should match what is printed by zmirror() above:
const uint64_t g_Zcrc64_mirror[] = {
 0x0000000000000000ULL, 0xc96c5795d7870f42ULL, 0x5bb4f8be788911c6ULL, 0x92d8af2baf0e1e84ULL,
 0xb769f17cf112238cULL, 0x7e05a6e926952cceULL, 0xecdd09c2899b324aULL, 0x25b15e575e1c3d08ULL,
 0xa7bfb56c35a3485aULL, 0x6ed3e2f9e2244718ULL, 0xfc0b4dd24d2a599cULL, 0x35671a479aad56deULL,
 0x10d64410c4b16bd6ULL, 0xd9ba138513366494ULL, 0x4b62bcaebc387a10ULL, 0x820eeb3b6bbf7552ULL,
 0x86133d4dbcc19ff6ULL, 0x4f7f6ad86b4690b4ULL, 0xdda7c5f3c4488e30ULL, 0x14cb926613cf8172ULL,
 0x317acc314dd3bc7aULL, 0xf8169ba49a54b338ULL, 0x6ace348f355aadbcULL, 0xa3a2631ae2dda2feULL,
 0x21ac88218962d7acULL, 0xe8c0dfb45ee5d8eeULL, 0x7a18709ff1ebc66aULL, 0xb374270a266cc928ULL,
 0x96c5795d7870f420ULL, 0x5fa92ec8aff7fb62ULL, 0xcd7181e300f9e5e6ULL, 0x041dd676d77eeaa4ULL,
 0xc54a2d0eae0430aeULL, 0x0c267a9b79833fecULL, 0x9efed5b0d68d2168ULL, 0x57928225010a2e2aULL,
 0x7223dc725f161322ULL, 0xbb4f8be788911c60ULL, 0x299724cc279f02e4ULL, 0xe0fb7359f0180da6ULL,
 0x62f598629ba778f4ULL, 0xab99cff74c2077b6ULL, 0x394160dce32e6932ULL, 0xf02d374934a96670ULL,
 0xd59c691e6ab55b78ULL, 0x1cf03e8bbd32543aULL, 0x8e2891a0123c4abeULL, 0x4744c635c5bb45fcULL,
 0x4359104312c5af58ULL, 0x8a3547d6c542a01aULL, 0x18ede8fd6a4cbe9eULL, 0xd181bf68bdcbb1dcULL,
 0xf430e13fe3d78cd4ULL, 0x3d5cb6aa34508396ULL, 0xaf8419819b5e9d12ULL, 0x66e84e144cd99250ULL,
 0xe4e6a52f2766e702ULL, 0x2d8af2baf0e1e840ULL, 0xbf525d915feff6c4ULL, 0x763e0a048868f986ULL,
 0x538f5453d674c48eULL, 0x9ae303c601f3cbccULL, 0x083bacedaefdd548ULL, 0xc157fb78797ada0aULL,
 0x43f80d888b8f6e1eULL, 0x8a945a1d5c08615cULL, 0x184cf536f3067fd8ULL, 0xd120a2a32481709aULL,
 0xf491fcf47a9d4d92ULL, 0x3dfdab61ad1a42d0ULL, 0xaf25044a02145c54ULL, 0x664953dfd5935316ULL,
 0xe447b8e4be2c2644ULL, 0x2d2bef7169ab2906ULL, 0xbff3405ac6a53782ULL, 0x769f17cf112238c0ULL,
 0x532e49984f3e05c8ULL, 0x9a421e0d98b90a8aULL, 0x089ab12637b7140eULL, 0xc1f6e6b3e0301b4cULL,
 0xc5eb30c5374ef1e8ULL, 0x0c876750e0c9feaaULL, 0x9e5fc87b4fc7e02eULL, 0x57339fee9840ef6cULL,
 0x7282c1b9c65cd264ULL, 0xbbee962c11dbdd26ULL, 0x29363907bed5c3a2ULL, 0xe05a6e926952cce0ULL,
 0x625485a902edb9b2ULL, 0xab38d23cd56ab6f0ULL, 0x39e07d177a64a874ULL, 0xf08c2a82ade3a736ULL,
 0xd53d74d5f3ff9a3eULL, 0x1c5123402478957cULL, 0x8e898c6b8b768bf8ULL, 0x47e5dbfe5cf184baULL,
 0x86b22086258b5eb0ULL, 0x4fde7713f20c51f2ULL, 0xdd06d8385d024f76ULL, 0x146a8fad8a854034ULL,
 0x31dbd1fad4997d3cULL, 0xf8b7866f031e727eULL, 0x6a6f2944ac106cfaULL, 0xa3037ed17b9763b8ULL,
 0x210d95ea102816eaULL, 0xe861c27fc7af19a8ULL, 0x7ab96d5468a1072cULL, 0xb3d53ac1bf26086eULL,
 0x96646496e13a3566ULL, 0x5f08330336bd3a24ULL, 0xcdd09c2899b324a0ULL, 0x04bccbbd4e342be2ULL,
 0x00a11dcb994ac146ULL, 0xc9cd4a5e4ecdce04ULL, 0x5b15e575e1c3d080ULL, 0x9279b2e03644dfc2ULL,
 0xb7c8ecb76858e2caULL, 0x7ea4bb22bfdfed88ULL, 0xec7c140910d1f30cULL, 0x2510439cc756fc4eULL,
 0xa71ea8a7ace9891cULL, 0x6e72ff327b6e865eULL, 0xfcaa5019d46098daULL, 0x35c6078c03e79798ULL,
 0x107759db5dfbaa90ULL, 0xd91b0e4e8a7ca5d2ULL, 0x4bc3a1652572bb56ULL, 0x82aff6f0f2f5b414ULL,
 0x87f01b11171edc3cULL, 0x4e9c4c84c099d37eULL, 0xdc44e3af6f97cdfaULL, 0x1528b43ab810c2b8ULL,
 0x3099ea6de60cffb0ULL, 0xf9f5bdf8318bf0f2ULL, 0x6b2d12d39e85ee76ULL, 0xa24145464902e134ULL,
 0x204fae7d22bd9466ULL, 0xe923f9e8f53a9b24ULL, 0x7bfb56c35a3485a0ULL, 0xb29701568db38ae2ULL,
 0x97265f01d3afb7eaULL, 0x5e4a08940428b8a8ULL, 0xcc92a7bfab26a62cULL, 0x05fef02a7ca1a96eULL,
 0x01e3265cabdf43caULL, 0xc88f71c97c584c88ULL, 0x5a57dee2d356520cULL, 0x933b897704d15d4eULL,
 0xb68ad7205acd6046ULL, 0x7fe680b58d4a6f04ULL, 0xed3e2f9e22447180ULL, 0x2452780bf5c37ec2ULL,
 0xa65c93309e7c0b90ULL, 0x6f30c4a549fb04d2ULL, 0xfde86b8ee6f51a56ULL, 0x34843c1b31721514ULL,
 0x1135624c6f6e281cULL, 0xd85935d9b8e9275eULL, 0x4a819af217e739daULL, 0x83edcd67c0603698ULL,
 0x42ba361fb91aec92ULL, 0x8bd6618a6e9de3d0ULL, 0x190ecea1c193fd54ULL, 0xd06299341614f216ULL,
 0xf5d3c7634808cf1eULL, 0x3cbf90f69f8fc05cULL, 0xae673fdd3081ded8ULL, 0x670b6848e706d19aULL,
 0xe50583738cb9a4c8ULL, 0x2c69d4e65b3eab8aULL, 0xbeb17bcdf430b50eULL, 0x77dd2c5823b7ba4cULL,
 0x526c720f7dab8744ULL, 0x9b00259aaa2c8806ULL, 0x09d88ab105229682ULL, 0xc0b4dd24d2a599c0ULL,
 0xc4a90b5205db7364ULL, 0x0dc55cc7d25c7c26ULL, 0x9f1df3ec7d5262a2ULL, 0x5671a479aad56de0ULL,
 0x73c0fa2ef4c950e8ULL, 0xbaacadbb234e5faaULL, 0x287402908c40412eULL, 0xe11855055bc74e6cULL,
 0x6316be3e30783b3eULL, 0xaa7ae9abe7ff347cULL, 0x38a2468048f12af8ULL, 0xf1ce11159f7625baULL,
 0xd47f4f42c16a18b2ULL, 0x1d1318d716ed17f0ULL, 0x8fcbb7fcb9e30974ULL, 0x46a7e0696e640636ULL,
 0xc40816999c91b222ULL, 0x0d64410c4b16bd60ULL, 0x9fbcee27e418a3e4ULL, 0x56d0b9b2339faca6ULL,
 0x7361e7e56d8391aeULL, 0xba0db070ba049eecULL, 0x28d51f5b150a8068ULL, 0xe1b948cec28d8f2aULL,
 0x63b7a3f5a932fa78ULL, 0xaadbf4607eb5f53aULL, 0x38035b4bd1bbebbeULL, 0xf16f0cde063ce4fcULL,
 0xd4de52895820d9f4ULL, 0x1db2051c8fa7d6b6ULL, 0x8f6aaa3720a9c832ULL, 0x4606fda2f72ec770ULL,
 0x421b2bd420502dd4ULL, 0x8b777c41f7d72296ULL, 0x19afd36a58d93c12ULL, 0xd0c384ff8f5e3350ULL,
 0xf572daa8d1420e58ULL, 0x3c1e8d3d06c5011aULL, 0xaec62216a9cb1f9eULL, 0x67aa75837e4c10dcULL,
 0xe5a49eb815f3658eULL, 0x2cc8c92dc2746accULL, 0xbe1066066d7a7448ULL, 0x777c3193bafd7b0aULL,
 0x52cd6fc4e4e14602ULL, 0x9ba1385133664940ULL, 0x0979977a9c6857c4ULL, 0xc015c0ef4bef5886ULL,
 0x01423b973295828cULL, 0xc82e6c02e5128dceULL, 0x5af6c3294a1c934aULL, 0x939a94bc9d9b9c08ULL,
 0xb62bcaebc387a100ULL, 0x7f479d7e1400ae42ULL, 0xed9f3255bb0eb0c6ULL, 0x24f365c06c89bf84ULL,
 0xa6fd8efb0736cad6ULL, 0x6f91d96ed0b1c594ULL, 0xfd4976457fbfdb10ULL, 0x342521d0a838d452ULL,
 0x11947f87f624e95aULL, 0xd8f8281221a3e618ULL, 0x4a2087398eadf89cULL, 0x834cd0ac592af7deULL,
 0x875106da8e541d7aULL, 0x4e3d514f59d31238ULL, 0xdce5fe64f6dd0cbcULL, 0x1589a9f1215a03feULL,
 0x3038f7a67f463ef6ULL, 0xf954a033a8c131b4ULL, 0x6b8c0f1807cf2f30ULL, 0xa2e0588dd0482072ULL,
 0x20eeb3b6bbf75520ULL, 0xe982e4236c705a62ULL, 0x7b5a4b08c37e44e6ULL, 0xb2361c9d14f94ba4ULL,
 0x978742ca4ae576acULL, 0x5eeb155f9d6279eeULL, 0xcc33ba74326c676aULL, 0x055fede1e5eb6828ULL,
 0
};
The C version of _zadd64() below is called cy_ecma182_crc64() on the CeeHtmlEscape wiki page. It's the main way source of 64-bit crc. Generated assembler is likely non-optimal.
u64 Zcrc64::_zadd64(u64 crc, const void* p, u32 n) {
    const u8* s = (const u8*) p;
    const u8* x = s + n;
    const u64* terms = g_Zcrc64_terms;
    for ( ; s < x; ++s) {
        crc = ( crc << 8 ) ^ terms[ (( crc >> 56 ) ^ *s) & 0x0FF ];
    }
    return crc;
}
Method _zmir64() below closely resembles _zadd64() above, except it uses the lookup table of reflected polynomial kMirror from ECMA-182. This has the same strength and bit error detection ability as above, but generates an independent hash.
u64 Zcrc64::_zmir64(u64 crc, const void* p, u32 n) {
    const u8* s = (const u8*) p;
    const u8* x = s + n;
    const u64* mirror = g_Zcrc64_mirror;
    for ( ; s < x; ++s) { // variant:
        crc = ( crc << 8 ) ^ mirror[ (( crc >> 56 ) ^ *s) & 0x0FF ];
    }
    return crc;
}
Method _zsub64() generates an alternative 64-bit crc, using the same lookup table. In terms of detecting patterns of bit errors, which a crc is typically designed to detect, a good crc polynomial remains good when it is bitwise reversed end-to-end. So each good polynomial is actually two good polynomials, if you reflect bit order. (Note I'm saying you reflect polynomial bits and not the hex digits—those are two different things.)
u64 Zcrc64::_zsub64(u64 crc, const void* p, u32 n) {
    const u8* s = (const u8*) p;
    const u8* x = s + n;
    const u64* terms = g_Zcrc64_terms;
    for ( ; s < x; ++s) { // variant:
        crc = ( crc >> 8 ) ^ terms[ (crc ^ *s) & 0x0FF ];
    }
    return crc;
}
void Zcrc64::zcite(Sink& o) const {
    o.sf("<Zcrc64 crc=0x%016llx len=%lu/>",
         ae_ull(this->zcrc()), ae_ul(this->zlen()));
}
void Zcrc32::zcite(Sink& o) const {
    o.sf("<Zcrc32 crc=0x%08lx len=%lu/>",
         ae_ul(this->zcrc()), ae_ul(this->zlen()));
}
    
/* ----- Crc128 ----- */

// (ACTUALLY just TWO appended 64-bit hashes)
Method _zadd128() just calculates both the 64-bit crcs supported by the lookup table, in just one pass over the bytes hashed—likely faster than two passes.
void Zcrc128::_zadd128(const void* p, u32 n) {
    // _zadd128() modifies both z_crc and z_len
    u64 c0 = z_crc.u.m_u64[0];
    u64 c1 = z_crc.u.m_u64[1];
    const u8* s = (const u8*) p;
    const u8* x = s + n;
    const u64* terms = g_Zcrc64_terms;
    for ( ; s < x; ++s) { // two different 64-bit hashes:
        c0 = ( c0 << 8 ) ^ terms[ (( c0 >> 56 ) ^ *s) & 0x0FF ];
        c1 = ( c1 >> 8 ) ^ terms[ (c1 ^ *s) & 0x0FF ];
    }
    z_crc.u.m_u64[0] = c0;
    z_crc.u.m_u64[1] = c1;
    z_len += n;
}
void Zcrc128::zcite(Sink& o) const {
    u128 val = this->zcrc();
    o.sf("<Zcrc128 crc=0x%016llx%016llx len=%lu/>",
         ae_ull(val.u.m_u64[0]), ae_ull(val.u.m_u64[1]),
         ae_ul(this->zlen()));
}

/* ----- Crc256 ----- */

// (ACTUALLY just FOUR appended 64-bit hashes)
Method _zadd256() just calculates all four 64-bit crcs supported by both lookup tables, in just one pass over the bytes hashed—likely faster than multiple passes. (I'm only sure two of the hashes are CRCs. Hashes created by using lookup tables in alternative fashion might not correspond to a CRC.)
void Zcrc256::_zadd256(const void* p, u32 n) {
    // _zadd256() modifies both z_crc and z_len
    u64 c0 = z_crc.u.m_u64[0];
    u64 c1 = z_crc.u.m_u64[1];
    u64 c2 = z_crc.u.m_u64[2];
    u64 c3 = z_crc.u.m_u64[3];
    const u8* s = (const u8*) p;
    const u8* x = s + n;
    const u64* terms = g_Zcrc64_terms;
    const u64* mirror = g_Zcrc64_mirror;
    for ( ; s < x; ++s) { // four different 64-bit hashes:
        c0 = ( c0 << 8 ) ^ terms[ (( c0 >> 56 ) ^ *s) & 0x0FF ];
        c1 = ( c1 >> 8 ) ^ terms[ (c1 ^ *s) & 0x0FF ];
        c2 = ( c2 << 8 ) ^ mirror[ (( c2 >> 56 ) ^ *s) & 0x0FF ];
        c3 = ( c3 >> 8 ) ^ mirror[ (c3 ^ *s) & 0x0FF ];
    }
    z_crc.u.m_u64[0] = c0;
    z_crc.u.m_u64[1] = c1;
    z_crc.u.m_u64[2] = c2;
    z_crc.u.m_u64[3] = c3;
    z_len += n;
}
void Zcrc256::zcite(Sink& o) const {
    u256 val = this->zcrc();
    o.sf("<Zcrc256 crc=0x%016llx%016llx%016llx%016llx len=%lu/>",
         ae_ull(val.u.m_u64[0]), ae_ull(val.u.m_u64[1]),
         ae_ull(val.u.m_u64[2]), ae_ull(val.u.m_u64[3]),
         ae_ul(this->zlen()));
}

}; // namespace ae

#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>

extern "C" {
    int ae_crc_test_main(int argc, const char* const* argv);
}

//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
Each file opened from the command provides the fd file descriptor used here to read and hash all file bytes. Arg bits must be 32, 64, 128, or 256.
static void
ae_crc_cmd_line_file(int fd, int bits, const char* path) {
    int err = 0;
    ae::Zcrc32 c32;
    ae::Zcrc64 c64;
    ae::Zcrc128 c128;
    ae::Zcrc256 c256;
    char tmp[ 4096 + 4 ];
    for (;;) { // forever, until eof or fatal error in read()
        int actual = read(fd, tmp, 4096);
        if (actual < 0) {
            err = errno;
            if (EAGAIN == err || EINTR == err) {
                continue; // try again
            }
            else {
                printf("# %.256s: read(fd=%d, tmp, 4096) errno=%d (%.96s)\n",
                       path, fd, err, strerror(err)); /* string.h */
                break; // end for loop
            }
        }
        else if (0 == actual) { // eof?
            break; // end for loop
        }
        else { // actual > 0?
            ae::Iov frag(tmp, actual);
            if (32 == bits) {
                c32 << frag;
            }
            else if (64 == bits) {
                c64 << frag;
            }
            else if (128 == bits) {
                c128 << frag;
            }
            else if (256 == bits) {
                c256 << frag;
            }
        }
    }
    if (32 == bits) {
        printf("%.96s: crc32=%08lx len=%lu\n", path,
               (unsigned long) c32.zcrc(), (unsigned long) c32.zlen());
    }
    else if (64 == bits) {
        printf("%.96s: crc64=%016llx len=%lu\n", path,
               (unsigned long long) c64.zcrc(), (unsigned long) c64.zlen());
    }
    else if (128 == bits) {
        ae::u128 hash = c128; // operator u128()
        printf("%.96s: c64x2=%016llx %016llx len=%lu\n", path,
               (unsigned long long) hash.u.m_u64[0], 
               (unsigned long long) hash.u.m_u64[1], 
               (unsigned long) c128.zlen());
    }
    else if (256 == bits) {
        ae::u256 hash = c256; // operator u256()
        printf("%.96s: c64x4=%016llx %016llx %016llx %016llx len=%lu\n", path,
               (unsigned long long) hash.u.m_u64[0], 
               (unsigned long long) hash.u.m_u64[1], 
               (unsigned long long) hash.u.m_u64[2], 
               (unsigned long long) hash.u.m_u64[3], 
               (unsigned long) c256.zlen());
    }
}
The following simple command line tool exercises all four crc hashes defined on this page.
int ae_crc_test_main(int argc, const char* const* argv) {
    const char* app = argv[0];
    int val = 0;
    int err = 0;
    int bits = 64;
    if (1 == argc) { // no args after program name?
        printf("%.256s [-t] [-b(32|64|128|256)] [path]*\n", app);
    }
    for (int i = 1; i < argc; ++i) {
        const char* a = argv[i];
        if ('-' == *a) { // option?
            if ('b' == a[1] && a[2] && isdigit(a[2])) { // change crc bits?
                val = atoi(a + 2); /* stdlib.h */
                if (val == 32 || val == 64 || val == 128 || val == 256) {
                    bits = val;
                }
                else {
                    printf("# %.256s: -b%d need bits in {32, 64, 128, 256}\n",
                           app, val);
                }
            }
            else if ('t' == a[1] && 0 == a[2]) { // -t to print terms?
                ae::Zcrc64::zterms();
            }
        }
        else {
            int fd = open(a, O_RDONLY); /* fcntl.h */
            if (fd < 0) {
                err = errno;
                printf("# %.256s: open(%.96s, O_RDONLY)=%d errno=%d (%.96s)\n",
                       app, a, fd, err, strerror(err)); /* string.h */
            }
            else {
                ae_crc_cmd_line_file(fd, bits, /*path*/ a);
                if (close(fd) < 0) {
                    err = errno;
                    printf("# %.256s: close(fd=%d) [%.96s] errno=%d (%.96s)\n",
                           app, fd, a, err, strerror(err));
                }
            }
        }
    }
    fflush(stdout);
    return 0;
}

//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
Here's an example of command line usage, exercising all four bit sizes:
% ~/bin/crcN ae_crc.cc -b32 ae_crc.cc -b128 ae_crc.cc -b256 ae_crc.cc 
ae_crc.cc: crc64=f472d3f000580bae len=20931
ae_crc.cc: crc32=d87504c0 len=20931
ae_crc.cc: c64x2=f472d3f000580bae 1739c6c080d9c06b len=20931
ae_crc.cc: c64x4=f472d3f000580bae 1739c6c080d9c06b bc835a6c368f7b4d 49d3c577a0bf30bb len=20931
As expected, longer hashes begin with the original 64-bit hash.