using System.Collections.Generic;
private static readonly dynamic TestValues = new[]
new { value = 1, expectedText = "one"},
new { value = 2, expectedText = "two"},
new { value = 3, expectedText = "three"},
new { value = 4, expectedText = "four"},
new { value = 5, expectedText = "five"},
new { value = 6, expectedText = "six"},
new { value = 7, expectedText = "seven"},
new { value = 8, expectedText = "eight"},
new { value = 9, expectedText = "nine"},
new { value = 10, expectedText = "ten"},
new { value = 11, expectedText = "eleven"},
new { value = 12, expectedText = "twelve"},
new { value = 13, expectedText = "thirteen"},
new { value = 14, expectedText = "fourteen"},
new { value = 15, expectedText = "fifteen"},
new { value = 16, expectedText = "sixteen"},
new { value = 17, expectedText = "seventeen"},
new { value = 18, expectedText = "eighteen"},
new { value = 19, expectedText = "nineteen"},
new { value = 20, expectedText = "twenty"},
new { value = 21, expectedText = "twenty one"},
new { value = 30, expectedText = "thirty"},
new { value = 32, expectedText = "thirty two"},
new { value = 40, expectedText = "fourty"},
new { value = 43, expectedText = "fourty three"},
new { value = 50, expectedText = "fifty"},
new { value = 54, expectedText = "fifty four"},
new { value = 60, expectedText = "sixty"},
new { value = 65, expectedText = "sixty five"},
new { value = 70, expectedText = "seventy"},
new { value = 76, expectedText = "seventy six"},
new { value = 80, expectedText = "eighty"},
new { value = 87, expectedText = "eighty seven"},
new { value = 90, expectedText = "ninety"},
new { value = 98, expectedText = "ninety eight"},
new { value = 100, expectedText = "one hundred"},
new { value = 105, expectedText = "one hundred five"},
new { value = 119, expectedText = "one hundred nineteen"},
new { value = 199, expectedText = "one hundred ninety nine"},
new { value = 500, expectedText = "five hundreds"},
new { value = 505, expectedText = "five hundreds five"},
new { value = 519, expectedText = "five hundreds nineteen"},
new { value = 550, expectedText = "five hundreds fifty"},
new { value = 599, expectedText = "five hundreds ninety nine"}
private static readonly decimal MaxSupportedValueExcluding = 1000;
private static readonly decimal MinSupportedValue = 0;
private static readonly int GroupSize = 3;
private static readonly Dictionary<char, string> _singleNumberToText = new Dictionary<char, string>()
private static readonly Dictionary<string, string> _doubledNumbersToText = new Dictionary<string, string>()
private static string PadNumberWithLeadZeroes(string numberAsText)
var zeroesToAdd = numberAsText.Length % GroupSize;
numberAsText = numberAsText.PadLeft(numberAsText.Length + (GroupSize - zeroesToAdd), '0');
private static void NumberGroupToText(List<string> result, char s1, char s2, char s3)
result.Add(_singleNumberToText[s1]);
result.Add(s1 == '1' ? "hundred" : "hundreds");
result.Add(_doubledNumbersToText[new string(new char[] {s2, s3})]);
result.Add(_doubledNumbersToText[new string(new char[] {s2, '0'})]);
if (proceed && s3 != '0')
result.Add(_singleNumberToText[s3]);
private static string GetGroupName(int groupPos, bool plural)
throw new Exception("The number is too big");
private static string NumberToText(string numberAsText)
return _singleNumberToText['0'];
numberAsText = PadNumberWithLeadZeroes(numberAsText);
var result = new List<string>();
var groupPos = numberAsText.Length / GroupSize - 1;
for (var i = 0; i < numberAsText.Length; i += GroupSize, groupPos--)
NumberGroupToText(result, numberAsText[i], numberAsText[i+1], numberAsText[i+2]);
var isGroupPlural = numberAsText[i] != 0 && numberAsText[i] != '1';
var groupName = GetGroupName(groupPos, isGroupPlural);
if (!string.IsNullOrEmpty(groupName))
return string.Join(" ", result);
static string DollarsToWords(decimal value)
if (value > MaxSupportedValueExcluding)
throw new Exception("The number is too big");
if (value < MinSupportedValue)
throw new Exception("The number is too small");
var dollarsPart = Math.Truncate(value);
var centsPart = Math.Truncate((value - dollarsPart) * 100);
var dollarsPartText = dollarsPart.ToString();
var centsPartText = centsPart.ToString();
var dollarsValue = dollarsPart > 0 ? NumberToText(dollarsPartText) + (dollarsPart == 1 ? " dollar" : " dollars") : null;
var centsValue = centsPart > 0 ? NumberToText(centsPartText) + (centsPart == 1 ? " cent" : " cents") : null;
if (!string.IsNullOrEmpty(dollarsValue))
return !string.IsNullOrEmpty(centsValue) ? dollarsValue + " and " + centsValue : dollarsValue;
else if (!string.IsNullOrEmpty(centsValue))
public static void Main()
TestExpectException(1000);
foreach (var testValue in TestValues)
Test(testValue.value, testValue.expectedText + (testValue.value == 1 ? " dollar" : " dollars"));
foreach (var testValue in TestValues)
if (testValue.value > 0 && testValue.value < 100)
Test(testValue.value / 100.0m, testValue.expectedText + (testValue.value == 1 ? " cent" : " cents"));
Test(1.01m, "one dollar and one cent");
Test(5.09m, "five dollars and nine cents");
Test(19.30m, "nineteen dollars and thirty cents");
Test(123.99m, "one hundred twenty three dollars and ninety nine cents");
static void TestExpectException(decimal number)
Console.WriteLine($"{number}: FAIL");
Console.WriteLine(" Expected: exception");
Console.WriteLine($"{number}: PASS");
static void Test(decimal number, string expected)
var actual = DollarsToWords(number);
Console.WriteLine($"{number} - {expected}: PASS");
Console.WriteLine($"{number}: FAIL");
Console.WriteLine($" Expected: {expected}");
Console.WriteLine($" Actual: {actual}");