银行卡号码校验算法
银行卡号码校验算法
背景
最近在办卡的时候发现,两个相邻的人办卡,他们的卡号乍一看并不是连续的,但是把最后一位除掉之后却是连续的,很容易猜想最后一位应该是校验位。
银行卡号码的校验规则
银行卡号码的校验采用Luhn算法,假设有银行卡号如下:
我们使用不同的颜色对奇数和偶数部分进行区分。
红色部分表示从右往左数奇数位,蓝色部分表示从右往左数偶数位。
对于偶数位(蓝色部分),我们保留原数:
\[f\left(x\right) = x
\]
constexpr auto even = [](int x) {
return x;
};
对于奇数位(红色部分),我们假设\(g\left(x\right)\)表示\(x\)的各位数之和,则有:
\[f\left(x\right) = g\left(2x\right)
\]
constexpr auto odd = [](int x) {
x *= 2;
return x / 10 + x % 10;
};
这样我们就获得了每一位数字变化后的结果
constexpr auto check_digit = [](auto&& idx_and_digit) {
auto [idx, digit] = idx_and_digit;
int x = digit - '0';
// Since C++ started with 0, we swap even and odd.
return idx & 1 ? even(x) : odd(x);
};
最后我们将每个奇数位和偶数位对应的结果全部相加并对\(10\)取余,如果余数为\(0\)则校验位为\(0\),否则用\(10\)减去余数,即:
\[
s = Result \mod 10
\\
S(x)=\left\{
\begin{aligned}
0 & , & if\ s = 0, \\
10 - s & , & otherwise
\end{aligned}
\right.
\]
constexpr int Luhn(std::string_view numbers)
{
auto rg = numbers | std::views::reverse | std::views::enumerate | std::views::transform(check_digit);
int digit_sum = std::ranges::fold_left(rg, 0, std::plus<>()) % 10;
return digit_sum == 0 ? 0 : 10 - digit_sum;
}
代码
// https://en.wikipedia.org/wiki/Luhn_algorithm
#include <string_view>
#include <ranges>
#include <algorithm>
#include <cstdlib>
#include <functional>
constexpr auto odd = [](int x) {
x *= 2;
return x / 10 + x % 10;
};
constexpr auto even = [](int x) {
return x;
};
constexpr auto check_digit = [](auto&& idx_and_digit) {
auto [idx, digit] = idx_and_digit;
int x = digit - '0';
return idx & 1 ? even(x) : odd(x); // Since C++ started with 0, we swap even and odd.
};
constexpr int Luhn(std::string_view numbers)
{
auto rg = numbers | std::views::reverse | std::views::enumerate | std::views::transform(check_digit);
int digit_sum = std::ranges::fold_left(rg, 0, std::plus<>()) % 10;
return digit_sum == 0 ? 0 : 10 - digit_sum;
}
int main(int argc, char const *argv[])
{
static_assert(Luhn("62122802001416649") == 7);
return 0;
}