【问题标题】:implementing luhn algorithm using c#使用 c# 实现 luhn 算法
【发布时间】:2014-02-10 13:08:45
【问题描述】:

我正在使用以下代码在 c# 语言中实现用于信用卡检查的 Luhn 算法,但无法获得输出以生成校验和其显示有效性:请帮助我。提前谢谢

public class Program
{
    private static void Main(string[]creditcard)
    {
        int sum = 0, d;
        string num ="7992739871";
        int a = 0;

        for (int i = num.Length - 2; i >= 0; i--)
        {
            d = Convert.ToInt32(num.Substring(i, 1));
            if (a % 2 == 0)
                d = d * 2;
            if (d > 9)
                d -= 9;
            sum += d;
            a++;
        }

        if ((10 - (sum % 10) == Convert.ToInt32(num.Substring(num.Length - 1))))
            Console.WriteLine("valid");

        Console.WriteLine("sum of digits of the number" + sum);
    }
}    

【问题讨论】:

  • 什么不起作用?你有错误吗?如果是这样,请将其与您的堆栈跟踪一起发布
  • 我想在这个过程中生成我无法得到的校验和
  • 我认为这段代码有错误。 sum ≡ 0 mod 10 工作不正常(检查总是失败)。

标签: c# checksum luhn


【解决方案1】:

以下是一些扩展方法,它们计算 Luhn 校验位、使用校验位验证数字以及将校验位添加到数字中。在 .NET 4.5 中测试。

有字符串、整数、int64s和IList的扩展方法。

我从rosettacode.org得到了一些想法

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;

public static class CheckDigitExtension
{
    static readonly int[] Results = { 0, 2, 4, 6, 8, 1, 3, 5, 7, 9 };

    #region extension methods for IList<int>

    /// <summary>
    /// For a list of digits, compute the ending checkdigit 
    /// </summary>
    /// <param name="digits">The list of digits for which to compute the check digit</param>
    /// <returns>the check digit</returns>
    public static int CheckDigit(this IList<int> digits)
    {
        var i = 0;
        var lengthMod = digits.Count%2;
        return (digits.Sum(d => i++ % 2 == lengthMod ? d : Results[d]) * 9) % 10;
    }

    /// <summary>
    /// Return a list of digits including the checkdigit
    /// </summary>
    /// <param name="digits">The original list of digits</param>
    /// <returns>the new list of digits including checkdigit</returns>
    public static IList<int> AppendCheckDigit(this IList<int> digits)
    {
        var result = digits;
        result.Add(digits.CheckDigit());
        return result;
    }

    /// <summary>
    /// Returns true when a list of digits has a valid checkdigit
    /// </summary>
    /// <param name="digits">The list of digits to check</param>
    /// <returns>true/false depending on valid checkdigit</returns>
    public static bool HasValidCheckDigit(this IList<int> digits)
    {
        return digits.Last() == CheckDigit(digits.Take(digits.Count - 1).ToList());
    }

    #endregion extension methods for IList<int>

    #region extension methods for strings

    /// <summary>
    /// Internal conversion function to convert string into a list of ints
    /// </summary>
    /// <param name="digits">the original string</param>
    /// <returns>the list of ints</returns>
    private static IList<int> ToDigitList(this string digits)
    {
        return digits.Select(d => d - 48).ToList();
    }

    /// <summary>
    /// For a string of digits, compute the ending checkdigit 
    /// </summary>
    /// <param name="digits">The string of digits for which to compute the check digit</param>
    /// <returns>the check digit</returns>
    public static string CheckDigit(this string digits)
    {
        return digits.ToDigitList().CheckDigit().ToString(CultureInfo.InvariantCulture);
    }

    /// <summary>
    /// Return a string of digits including the checkdigit
    /// </summary>
    /// <param name="digits">The original string of digits</param>
    /// <returns>the new string of digits including checkdigit</returns>
    public static string AppendCheckDigit(this string digits)
    {
        return digits + digits.CheckDigit(); 
    }

    /// <summary>
    /// Returns true when a string of digits has a valid checkdigit
    /// </summary>
    /// <param name="digits">The string of digits to check</param>
    /// <returns>true/false depending on valid checkdigit</returns>
    public static bool HasValidCheckDigit(this string digits)
    {
        return digits.ToDigitList().HasValidCheckDigit();
    }

    #endregion extension methods for strings

    #region extension methods for integers

    /// <summary>
    /// Internal conversion function to convert int into a list of ints, one for each digit
    /// </summary>
    /// <param name="digits">the original int</param>
    /// <returns>the list of ints</returns>
    private static IList<int> ToDigitList(this int digits)
    {
        return digits.ToString(CultureInfo.InvariantCulture).Select(d => d - 48).ToList();
    }

    /// <summary>
    /// For an integer, compute the ending checkdigit 
    /// </summary>
    /// <param name="digits">The integer for which to compute the check digit</param>
    /// <returns>the check digit</returns>
    public static int CheckDigit(this int digits)
    {
        return digits.ToDigitList().CheckDigit();
    }

    /// <summary>
    /// Return an integer including the checkdigit
    /// </summary>
    /// <param name="digits">The original integer</param>
    /// <returns>the new integer including checkdigit</returns>
    public static int AppendCheckDigit(this int digits)
    {
        return digits * 10 + digits.CheckDigit();
    }

    /// <summary>
    /// Returns true when an integer has a valid checkdigit
    /// </summary>
    /// <param name="digits">The integer to check</param>
    /// <returns>true/false depending on valid checkdigit</returns>
    public static bool HasValidCheckDigit(this int digits)
    {
        return digits.ToDigitList().HasValidCheckDigit();
    }

    #endregion extension methods for integers

    #region extension methods for int64s

    /// <summary>
    /// Internal conversion function to convert int into a list of ints, one for each digit
    /// </summary>
    /// <param name="digits">the original int</param>
    /// <returns>the list of ints</returns>
    private static IList<int> ToDigitList(this Int64 digits)
    {
        return digits.ToString(CultureInfo.InvariantCulture).Select(d => d - 48).ToList();
    }

    /// <summary>
    /// For an integer, compute the ending checkdigit 
    /// </summary>
    /// <param name="digits">The integer for which to compute the check digit</param>
    /// <returns>the check digit</returns>
    public static int CheckDigit(this Int64 digits)
    {
        return digits.ToDigitList().CheckDigit();
    }

    /// <summary>
    /// Return an integer including the checkdigit
    /// </summary>
    /// <param name="digits">The original integer</param>
    /// <returns>the new integer including checkdigit</returns>
    public static Int64 AppendCheckDigit(this Int64 digits)
    {
        return digits * 10 + digits.CheckDigit();
    }

    /// <summary>
    /// Returns true when an integer has a valid checkdigit
    /// </summary>
    /// <param name="digits">The integer to check</param>
    /// <returns>true/false depending on valid checkdigit</returns>
    public static bool HasValidCheckDigit(this Int64 digits)
    {
        return digits.ToDigitList().HasValidCheckDigit();
    }

    #endregion extension methods for int64s
}

这里有一些 XUnit 测试用例展示了扩展方法是如何工作的。

public class CheckDigitExtensionShould
{
    [Fact]
    public void ComputeCheckDigits()
    {
        Assert.Equal(0, (new List<int> { 0 }).CheckDigit());
        Assert.Equal(8, (new List<int> { 1 }).CheckDigit());
        Assert.Equal(6, (new List<int> { 2 }).CheckDigit());

        Assert.Equal(0, (new List<int> { 3, 6, 1, 5, 5 }).CheckDigit());
        Assert.Equal(0, 36155.CheckDigit());
        Assert.Equal(8, (new List<int> { 3, 6, 1, 5, 6 }).CheckDigit());
        Assert.Equal(8, 36156.CheckDigit());
        Assert.Equal(6, 36157.CheckDigit());
        Assert.Equal("6", "36157".CheckDigit());
        Assert.Equal("3", "7992739871".CheckDigit());
    }

    [Fact]
    public void ValidateCheckDigits()
    {
        Assert.True((new List<int> { 3, 6, 1, 5, 6, 8 }).HasValidCheckDigit());
        Assert.True(361568.HasValidCheckDigit());
        Assert.True("361568".HasValidCheckDigit());
        Assert.True("79927398713".HasValidCheckDigit());
    }

    [Fact]
    public void AppendCheckDigits()
    {
        Console.WriteLine("36156".CheckDigit());
        Console.WriteLine("36156".AppendCheckDigit());
        Assert.Equal("361568", "36156".AppendCheckDigit());
        Assert.Equal("79927398713", "7992739871".AppendCheckDigit());
    }
}

【讨论】:

  • 我会把它放在一个自己的类中,而不是扩展内置类型,我会认为内置类型的污染。
  • 很好的解释。完整答案,我相信
【解决方案2】:

紧凑的 Luhn 检查:

public static bool Luhn(string digits)
{
    return digits.All(char.IsDigit) && digits.Reverse()
        .Select(c => c - 48)
        .Select((thisNum, i) => i % 2 == 0
            ? thisNum
            :((thisNum *= 2) > 9 ? thisNum - 9 : thisNum)
        ).Sum() % 10 == 0;
}

小提琴:https://dotnetfiddle.net/CCwE48

【讨论】:

  • 为什么使用“-48”?
  • 将字符转换为其数值。 0 的字符代码是 48,1 是 49 等等。减去 48,你就有了数值。
【解决方案3】:

这是一个正确且快速的实现:

bool PassesLuhnCheck(string value)
{
    long sum = 0;

    for (int i = 0; i < value.Length; i++)
    {
        var digit = value[value.Length - 1 - i] - '0';
        sum += (i % 2 != 0) ? GetDouble(digit) : digit;
    }

    return sum % 10 == 0;

    int GetDouble(long i)
    {
        switch (i)
        {
            case 0: return 0;
            case 1: return 2;
            case 2: return 4;
            case 3: return 6;
            case 4: return 8;
            case 5: return 1;
            case 6: return 3;
            case 7: return 5;
            case 8: return 7;
            case 9: return 9;
            default: return 0;
        }
    }
}

【讨论】:

    【解决方案4】:

    我已经尝试过这段代码,它可能对未来的其他人有所帮助:

        public string GenerateLuhnNumber(string baseNumber)
        {
            if (!double.TryParse(baseNumber, out double baseNumberInt))
                throw new InvalidWorkflowException($"Field contains non-numeric character(s) : {baseNumber}");
    
            var step2 = string.Empty;
            for (var index = baseNumber.Length - 1; index >= 0; index -= 2)
            {
                var doubleTheValue = (int.Parse(baseNumber[index].ToString())) * 2;
    
                if (doubleTheValue > 9)
                    doubleTheValue = Math.Abs(doubleTheValue).ToString().Sum(c => Convert.ToInt32(c.ToString()));
    
                step2 = step2.Insert(0, (index != 0 ? baseNumber[index - 1].ToString() : "") + doubleTheValue);
            }
            var step3 = Math.Abs(Convert.ToDouble(step2)).ToString(CultureInfo.InvariantCulture).Sum(c => Convert.ToDouble(c.ToString())).ToString(CultureInfo.InvariantCulture);
    
            var lastDigitStep3 = Convert.ToInt32(step3[step3.Length - 1].ToString());
            string checkDigit = "0";
    
            if (lastDigitStep3 != 0)
                checkDigit = (10 - lastDigitStep3).ToString();
    
            return baseNumber + checkDigit;
        }
    

    【讨论】:

    • 我注意到如果字符串长度为18,要生成第19位,这个方法通过异常
    【解决方案5】:

    你可以很简单地做到这一点(reference),

        public static bool Mod10Check(string creditCardNumber)
        {              
            // check whether input string is null or empty
            if (string.IsNullOrEmpty(creditCardNumber))
            {
                return false;
            }
    
            int sumOfDigits = creditCardNumber.Where((e) => e >= '0' && e <= '9')
                            .Reverse()
                            .Select((e, i) => ((int)e - 48) * (i % 2 == 0 ? 1 : 2))
                            .Sum((e) => e / 10 + e % 10);
    
    
            return sumOfDigits % 10 == 0;
        }
    

    【讨论】:

    • 这对于无效的信用卡号码返回 true。尝试使用“adfafda”作为卡号。
    【解决方案6】:

    我相信这个会做到的:

    static void Main(string[] args)
        {
            string number = "1762483";
            int digit = 0;
            int sum = 0;
    
            for (int i = 0; i <= number.Length - 1; i++)
            {
    
                if (i % 2 == 1)
                {
                    digit = int.Parse(number.Substring(i, 1));
                    sum += DoubleDigitValue(digit);
    
                    Console.WriteLine(digit);
                }
                else
                {
                    digit = int.Parse(number.Substring(i, 1));
                    sum += digit;
                }
    
            }
            Console.WriteLine(sum);
            if (sum % 10 == 0)
            {
                Console.WriteLine("valid");
            }
            else
            {
                Console.WriteLine("Invalid");
            }
        }
        static int DoubleDigitValue(int digit)
        {
            int sum;
            int doubledDigit = digit * 2; 
            if (doubledDigit >= 10)
            {
                sum = 1 + doubledDigit % 10;
            } else
            {
                sum = doubledDigit; 
            }
            return sum; 
        }
    

    【讨论】:

      【解决方案7】:

      这些是我验证和计算最后一位数字的方法。要验证一个数字,只需检查第一种方法的结果是否为 0;

      private int LuhnChecksum(string input)
          {
              var length = input.Length; 
              var even = length % 2; 
              var sum = 0;
      
              for (var i = length - 1; i >= 0; i--)  
              {
                  var d = int.Parse(input[i].ToString());
                  if (i % 2 == even)
                      d *= 2;
                  if (d > 9)
                      d -= 9;
                  sum += d;
              }
              return sum % 10;
          }
      
          private int LuhnCalculateLastDigit(string input) 
          {
              var checksum = LuhnChecksum(input + "0");
              return checksum == 0 ? 0 : 10 - checksum;
          }
      

      【讨论】:

        【解决方案8】:

        我只是将代码从 C 解释为 C#。您可以在那里找到 C 代码:(https://uk.wikipedia.org/wiki/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9B%D1%83%D0%BD%D0%B0)。我检查了几台设备。

        byte[] data = new byte[19];
        
        //fill the data[] 
        ...
        
        //use it
        if (checkByLuhn(data))
        {
          //check complete
        }
        ...
        private bool checkByLuhn(byte[] pPurposed)
        {
            int nSum = 0;
            int nDigits = pPurposed.Length;
            int nParity = (nDigits - 1) % 2;
            char[] cDigit = new char[] { '0','\0' };
        
            for (int i = nDigits; i > 0; i--)
            {
                cDigit[0] = (char)pPurposed[i - 1];
                int nDigit = (int)Char.GetNumericValue(cDigit[0]);
        
                if (nParity == i % 2)
                {
                    nDigit = nDigit * 2;
                }
                nSum += nDigit / 10;
                nSum += nDigit % 10;
            }
            return 0 == nSum % 10;
        }
        

        【讨论】:

        • 你可以通过解释你做了什么以及它为什么起作用来改进它。
        • 我只是将代码从 C 解释为 C#。你可以在那里找到 C 中的代码:(ru.wikipedia.org/wiki/…) 我检查了一些设备。
        【解决方案9】:

        您的算法是正确的,但您正在以错误的方式对其进行测试。

        我可以看到您的示例输入来自 wiki 页面:Luhn algorithm。不同之处在于,他们正在计算"7992739871X" 的校验位,其中X 是他们正在寻找的校验位。您的代码验证您已有的号码!

        将您的输入更改为"79927398713",它会将其标记为正确的数字。

        更新

        好的,我知道问题出在哪里。您没有正确使用这部分算法:

        从最右边的数字开始,即校验位,向左移动,每隔一个数字加一倍;

        您的代码需要每隔一个数字,但不必从最左边的数字开始。试试这个代码:

        for (int i = 0; i < num.Length; i++)
        {
            d = Convert.ToInt32(num.Substring(num.Length - 1 - i, 1));
            if (a % 2 == 0)
                d = d * 2;
            if (d > 9)
                d -= 9;
            sum += d;
            a++;
        }
        
        var checkDigit = 10 - (sum % 10);
        

        【讨论】:

        • 这是错误代码。仅当位数为偶数时才正确。例如,对于具有奇数位数(15 或 13)的数字,它会将错误数字加倍。 Marcin,你从从右边开始计算第二个数字,而不是从左边开始计算。此代码从左侧数起。 :(这就是为什么原来的代码从右边开始计算的原因。
        • 另外,当总和为 0 时,它返回 10 而不是 0。
        最近更新 更多