Zgodnie z obowiązującym w Polsce prawem, faktura powinna zawierać przynajmniej:
(…)
– kwotę należności ogółem wraz z należnym podatkiem (brutto), wyrażoną cyframi i słownie.
Tak podaje wikipedia. Dziś zajmę się pogrubionym fragmentem cytatu: zamianą kwoty pieniędzy określonej waluty na jej słowną reprezentację. Zakładam, że wystawiając faktury na kwoty powyżej 999999,99 (czyli milion i więcej) jednostek monetarnych, użytkownik – z nieopisaną satysfakcją – wpisze kwotę słownie własnoręcznie.
Liczby => słowa
Na początek zajmę się zamianą liczb całkowitych na słowa. Zastosuję poniższy algorytm:
1. podział liczby na grupy cyfr (przykładowo liczbę 123456 podzielę na 123 i 456, a 12345 – na 12 i 345),
2. zamiana bardziej znaczącej grupy na słowa,
3. dodanie słowa “tysiące” w odpowiedniej formie,
4. zamiana mniej znaczącej grupy na słowa.
W implementacji algorytmu przyda mi się extension method zamieniająca liczbę na wektor jej cyfr:
private static int[] ToDigitArray(this int number) { // przykładowo zamienia 123456 na { 6, 5, 4, 3, 2, 1 } // (najmniej znacząca cyfra -> indeks 0) string str = number.ToString(); int[] digitArray = new int[str.Length]; for (int i = 0; i != digitArray.Length; i++) digitArray[i] = int.Parse(str[str.Length - 1 - i].ToString()); return digitArray; }
Metody zamieniające kolejne cyfry na słowa wyglądają, tu fajerwerków nie będzie, tak:
(zamienia jedności:)
private static string ConvertUnits(int number) { switch (number) { case 1: return "jeden"; case 2: return "dwa"; case 3: return "trzy"; case 4: return "cztery"; case 5: return "pięć"; case 6: return "sześć"; case 7: return "siedem"; case 8: return "osiem"; case 9: return "dziewięć"; default: return string.Empty; } }
(zamienia “nastki”:)
private static string ConvertTeens(int number) { switch (number) { case 11: return "jedenaście"; case 12: return "dwanaście"; case 13: return "trzynaście"; case 14: return "czternaście"; case 15: return "pięnaście"; case 16: return "szesnaście"; case 17: return "siedemnaście"; case 18: return "osiemnaście"; case 19: return "dziewiętnaście"; default: return string.Empty; } }
(zamienia dziesiątki:)
private static string ConvertTens(int number) { switch (number) { case 1: return "dziesięć"; case 2: return "dwadzieścia"; case 3: return "trzydzieści"; case 4: return "czterdzieści"; case 5: return "pięćdziesiąt"; case 6: return "sześćdziesiąt"; case 7: return "siedemdziesiąt"; case 8: return "osiemdziesiąt"; case 9: return "dziewięćdziesiąt"; default: return string.Empty; } }
(zamienia setki:)
private static string ConvertHundreds(int number) { switch (number) { case 1: return "sto"; case 2: return "dwieście"; case 3: return "trzysta"; case 4: return "czterysta"; case 5: return "pięćset"; case 6: return "sześćset"; case 7: return "siedemset"; case 8: return "osiemset"; case 9: return "dziewięćset"; default: return string.Empty; } }
Zamiana grup cyfr (o których mowa w pkt. 1 algorytmu) na słowa wygląda więc tak,
private static string GetHundreds(int[] numberArray) { string result; switch (numberArray.Length) { case 1: // np. 9 result = ConvertUnits(numberArray[0]); break; case 2: // np. 99 if (numberArray[1] == 1) // np. 91 result = ConvertTeens(10 + numberArray[0]); else result = ConvertTens(numberArray[1]) + " " + ConvertUnits(numberArray[0]); break; case 3: // np. 999 if (numberArray[1] == 1) // np. 919 result = ConvertHundreds(numberArray[2]) + " " + ConvertTeens(10 + numberArray[0]); else result = ConvertHundreds(numberArray[2]) + " " + ConvertTens(numberArray[1]) + " " + ConvertUnits(numberArray[0]); break; default: result = string.Empty; break; } return result; }
metoda używana przy trzecim kroku algorytmu – tak,
private static string GetGrammaticalThousands(int lastDigit) { switch (lastDigit) { case 1: return "tysiąc"; case 2: case 3: case 4: return "tysiące"; default: return "tysięcy"; } }
a cały algorytm – tak (number to liczba pobrana w argumencie):
string result = string.Empty; int[] digitArray = number.ToDigitArray(); if (digitArray.Length == 6) // e.g. 123456 { int[] moreSignificantDigits = { digitArray[3], digitArray[4], digitArray[5] }; // e.g. { 3, 2, 1 } int[] lessSignificantDigits = { digitArray[0], digitArray[1], digitArray[2] }; // e.g. { 6, 5, 4 } if (digitArray[4] == 1) // e.g. 919999 result = GetHundreds(moreSignificantDigits) + " tysięcy " + GetHundreds(lessSignificantDigits); else result = GetHundreds(moreSignificantDigits) + " " + GetGrammaticalThousands(digitArray[3]) + " " + GetHundreds(lessSignificantDigits); } else if (digitArray.Length == 5) // e.g. 12345 { int[] moreSignificantDigits = { digitArray[3], digitArray[4] }; // e.g. { 2, 1 } int[] lessSignificantDigits = { digitArray[0], digitArray[1], digitArray[2] }; // e.g. { 5, 4, 3 } if (digitArray[4] == 1) // 91999 result = GetHundreds(moreSignificantDigits) + " tysięcy " + GetHundreds(lessSignificantDigits); else result = GetHundreds(moreSignificantDigits) + " " + GetGrammaticalThousands(digitArray[3]) + " " + GetHundreds(lessSignificantDigits); } else if (digitArray.Length == 4) // e.g. 1234 { int[] moreSignificantDigits = { digitArray[3] }; // e.g. { 4 } int[] lessSignificantDigits = { digitArray[0], digitArray[1], digitArray[2] }; // e.g. { 3, 2, 1 } result = GetHundreds(moreSignificantDigits) + " " + GetGrammaticalThousands(digitArray[3]) + " " + GetHundreds(lessSignificantDigits); } else { result = GetHundreds(digitArray); }
Dla number == 0 metoda konwertująca (o niezbyt zaskakującej nazwie Convert) zwraca “zero”, a dla number >= 1,000,000 – “wpisz ręcznie”.
Kwota => słowa
Zamiana kwoty pieniężnej na słowa to już sprawa prosta:
public static string ConvertMoney(decimal amount, string currency) { string result = Convert((int)amount); // konwersja części całkowitej result += " " + currency + " "; // dodanie waluty decimal decimals = (amount - decimal.Floor(amount)) * 100; // wyłuskanie części ułamkowej result += ((int)decimals).ToString(); // dodanie części ułamkowej result += "/100"; return result; }
W ten sposób kwota 123456,78 PLN jest zamieniana na: “sto dwadzieścia trzy tysiące czterysta pięćdziesiąt sześć PLN 78/100”.
Na koniec małe usprawiedliwienie. Nie można zaprzeczyć, że wpis jest rażąco lakoniczy i, co tu kryć, pisany na szybko. Jest to spowodowane tym, że aktualnie się przeprowadzam. Z jednego miejsca zamieszkania – w trzy (szczegóły tej sytuacji ciężko byłoby opisać w kilku słowach…). Dodatkowo, post ten napisałem na kilka dni przed jego opublikowaniem, ponieważ w chwili, gdy czytelnik beznamiętnie przewija kolejne kawały zaprezentowanego przeze mnie kodu, ja beztrosko bujam się na falach któregoś z jezior mazurskich. Taki natłok wydarzeń uniemożliwia mi regularne blogowanie, dlatego czeka mnie przynajmniej tydzień “urlopu” od konkursu.
łojej .. na studiach takie prace domowe dostawaliśmy 😉 Ale jakoś to prosciej wygladało :p