Routing number checksums

or why range-v3 is 🔥 and you should use it now

Ryan Graham
5 min readMay 20, 2020
Photo by Avel Chuklanov on Unsplash

An ABA Routing Transit Number is a nine digit code printed at the bottom of your checks. Let’s be real though. It’s 2020 not 1910. You don’t have paper checks anymore. But I bet you do use ACH for direct deposit paychecks or to pay your rent. The ACH network uses RTNs to route payments to the correct bank that manages your account.

Let’s google my routing number and use it as an example.

Yep. That sure is a number. Nine digits and all.

So let’s suppose you just started a new job and you’re setting up direct deposit. You’ll be asked to provide your bank account number and your routing number. But what if you typo your routing number or it gets smudged and/or some OCR software doesn’t pick it up correctly? Will the deposit go to the wrong bank? Will the erroneous RTN match to a real bank at all?

I’m familiar with three popular ways to mitigate that risk.

  1. Pay Accuity for the latest RTN database and match the nine digit number against the name of the bank. Pricing is a mystery, but chances are its over $500.
  2. Run test micro deposits and ask the user to confirm the amounts.
  3. Validate the checksum of the RTN.

Today we’re going to implement the third option. It may be the least effective. It only verifies that the RTN is valid. It could still be a valid RTN for the wrong bank. Someone else at that bank might even have the same account number as you and receive your entire paycheck. But it is the cheapest and fastest validation if you want to be sure the data isn’t just noise. High speed check sorting equipment verifies RTNs using this method for similar reasons.

RTN Checksum Formula

This is the formula for RTN checksums.

3(d1 + d4 + d7) + 7(d2 + d5 + d8) + (d3 + d6 + d9) mod 10 = 0

Lets try it in IRB real quick with my routing number digits (325081403):

(3 * (3 + 0 + 4) + 7 * (2 + 8 + 0) + (5 + 1 + 3)) % 10 == 0
=> true

Or closer to how we’ll actually implement it later with a nine digit array of weights [3, 7, 1, 3, 7, 1, 3, 7, 1] that we can zip with the RTN digits.

(3*3 + 7*2 + 1*5 + 3*0 + 7*8 + 1*1 + 3*4 + 7*0 + 1*3) % 10 == 0
=> true

So if I break that down into 3 steps.

  1. Multiply each digit of the RTN by the corresponding weight.
  2. Sum all nine products.
  3. Check whether the sum is divisible by 10. If so, we have a valid routing number.

Elixir Implementation

You can write this in minutes in Elixir. This is my personal take.

I’ll break it down line by line for the uninitiated.

  1. “325081403” is our example routing number input
  2. graphemes/1 splits the RTN string into a list of individual digits
  3. Convert each character to an integer before we apply math
  4. zip/2 combines our list of digits with our list of weights into a list of tuples
  5. Multiply each digit by its weight and create a list of products
  6. Sum all nine products
  7. Check whether the sum is divisible by 10

Side note: I think the Elixir solution is fairly aesthetic, but one line of it could be so much better in python or ruby. [3, 7, 1, 3, 7, 1, 3, 7, 1] could have easily been [3, 7, 1] * 3. I’ll never understand the Elixir community’s resistance to operator overloading.

C++ Implementation

How long do you think it would take you to write the same formula in C++? The same 5–10 minutes as Elixir? I was certain it would take me longer.

But what if I told you C++20 was going to add all of the functions we just used in our Elixir implementation? You wouldn’t have to rewrite anything in imperative style. You can copy paste the seven line Elixir solution and convert to C++ syntax. You’re practically done!

Now you might be saying C++20 isn’t ready. But you can bump your compiler to use C++17 and add range-v3. range-v3 is a super set of C++20’s new range support. Discovering it was a serious lightbulb moment for me and made me love C++ again. Using C++11, STL, and Boost, and doing it poorly at that, I was beginning to believe I would always be slow to produce in this language. range-v3 proved me wrong.

Imagine my excitement when I realized range-v3 also supported lazy evaluation like my Elixir Stream functions above. And even moreso… PIPE SUPPORT. range-v3/C++20 overloads | instead of Elixir’s |> token. And I can’t figure out why I can’t pipe into zip or accumulate. BUT PIPES. OMG. YES PLEASE.

So brace yourself. When you see this you might decide I’m trolling. But honestly I think its so much better than the implementation I’d previously envisioned without range-v3.

There isn’t a one-to-one mapping of line numbers between this and the Elixir. But the steps are all the same. So I’ll just call out a few of the similarities.

routing_number | views::transform(to_int)

Is roughly equivalent to our first three lines of Elixir. Think of transform as map. 🤷‍♂

"325081403" |> String.graphemes() |> Stream.map(&String.to_integer/1)

And after that

views::zip_with(multiply, routing_digits, multipliers)

zip_with and its accompanying multiply lambda replace zip and map.

Stream.zip([3, 7, 1, 3, 7, 1, 3, 7, 1]) |> Stream.map(fn {a, b} -> a * b end)

Accumulate is equivalent to Sum. I won’t even bother to paste that.

Then our check for division is nearly identical.

sum % 10 == 0

Replaces

rem(10) == 0

And we’re done.

So I know this checksum formula isn’t all that exciting. I wanted something simple and easy to follow while comparing and contrasting languages.

In conclusion, I feel like things which are trivial to write in higher order languages are now just as easy in C++17/range-v3.

BRB converting my C++11 projects.

--

--