using System.Numerics; namespace GeradoresService.Core; public static class Checksums { /// /// Dígito de controlo módulo 11 com pesos decrescentes (n+1 .. 2), usado no NIF e no NIC do CC. /// Resto 0 ou 1 dá dígito 0. /// public static int Mod11(ReadOnlySpan digits) { var sum = 0; for (var i = 0; i < digits.Length; i++) sum += (digits[i] - '0') * (digits.Length + 1 - i); var remainder = sum % 11; return remainder < 2 ? 0 : 11 - remainder; } /// /// Soma Luhn a partir da direita, duplicando posições alternadas. /// Aceita letras (A=10 .. Z=35), como no número de documento do Cartão de Cidadão. /// public static int LuhnSum(ReadOnlySpan value) { var sum = 0; var doubleIt = false; for (var i = value.Length - 1; i >= 0; i--) { var v = CharValue(value[i]); if (doubleIt) { v *= 2; if (v > 9) v -= 9; } sum += v; doubleIt = !doubleIt; } return sum; } public static bool LuhnValid(ReadOnlySpan value) => LuhnSum(value) % 10 == 0; /// Calcula o dígito Luhn a acrescentar a . public static int LuhnCheckDigit(string payload) { var sum = LuhnSum(payload + "0"); return (10 - sum % 10) % 10; } /// ISO 7064 MOD 97-10 sobre uma string de dígitos. public static int Mod97(string digits) => (int)(BigInteger.Parse(digits) % 97); /// Converte letras em números (A=10 .. Z=35) para cálculo de IBAN. public static string LettersToDigits(string value) => string.Concat(value.Select(c => char.IsLetter(c) ? (char.ToUpperInvariant(c) - 'A' + 10).ToString() : c.ToString())); private static int CharValue(char c) => c switch { >= '0' and <= '9' => c - '0', >= 'A' and <= 'Z' => c - 'A' + 10, >= 'a' and <= 'z' => c - 'a' + 10, _ => throw new ArgumentException($"Carácter inválido '{c}'."), }; /// Remove espaços, hífens, pontos e barras. public static string Clean(string value) => new(value.Where(c => !char.IsWhiteSpace(c) && c is not '-' and not '.' and not '/').ToArray()); public static bool AllDigits(string value) => value.Length > 0 && value.All(char.IsAsciiDigit); }