cs11dotnet7/vscode/Chapter08/Ch08Ex03NumbersAsWordsLib/NumbersToWords.cs
2022-03-02 08:51:03 +00:00

221 lines
6.6 KiB
C#

using System.Diagnostics; // Trace
using System.IO; // File
using System.Numerics; // BigInteger
namespace Packt.Shared
{
public static class NumbersToWords
{
// Single-digit and small number names
private static string[] smallNumbers = new string[]
{
"zero", "one", "two", "three", "four", "five", "six", "seven", "eight",
"nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen",
"sixteen", "seventeen", "eighteen", "nineteen"
};
// Tens number names from twenty upwards
private static string[] tens = new string[]
{
"", "", "twenty", "thirty", "forty", "fifty", "sixty", "seventy",
"eighty", "ninety"
};
// Scale number names for use during recombination
private static string[] scaleNumbers = new string[]
{
"", "thousand", "million", "billion", "trillion",
"quadrillion", "quintillion"
};
private static int groups = 7; // i.e. up to quintillion
public static string ToWords(this int number)
{
return ToWords((BigInteger)number);
}
public static string ToWords(this long number)
{
return ToWords((BigInteger)number);
}
public static string ToWords(this BigInteger number)
{
/*
Convert A Number into Words
by Richard Carr, published at http://www.blackwasp.co.uk/numbertowords.aspx
*/
/*
Zero Rule.
If the value is 0 then the number in words is 'zero' and no other rules apply.
*/
if (number == 0)
{
return "zero";
}
/*
Three Digit Rule.
The integer value is split into groups of three digits starting from the
right-hand side. Each set of three digits is then processed individually
as a number of hundreds, tens and units. Once converted to text, the
three-digit groups are recombined with the addition of the relevant scale
number (thousand, million, billion).
*/
// Array to hold the specified number of three-digit groups
int[] digitGroups = new int[groups];
// Ensure a positive number to extract from
var positive = BigInteger.Abs(number);
// Extract the three-digit groups
for (int i = 0; i < groups; i++)
{
digitGroups[i] = (int)(positive % 1000);
positive /= 1000;
}
// write to a text file in the project folder
Trace.Listeners.Add(new TextWriterTraceListener(
File.AppendText("log.txt")));
// text writer is buffered, so this option calls
// Flush() on all listeners after writing
Trace.AutoFlush = true;
// log array of group numbers
for (int x = 0; x < digitGroups.Length; x++)
{
Trace.WriteLine(string.Format(
format: "digitGroups[{0}] = {1}",
arg0: x,
arg1: digitGroups[x]));
}
// Convert each three-digit group to words
string[] groupTexts = new string[groups];
for (int i = 0; i < groups; i++)
{
// call a local function (see below)
groupTexts[i] = ThreeDigitGroupToWords(digitGroups[i]);
}
// log array of group texts
for (int x = 0; x < groupTexts.Length; x++)
{
Trace.WriteLine(string.Format(
format: "groupTexts[{0}] = {1}",
arg0: x,
arg1: groupTexts[x]));
}
/*
Recombination Rules.
When recombining the translated three-digit groups, each group except the
last is followed by a large number name and a comma, unless the group is
blank and therefore not included at all. One exception is when the final
group does not include any hundreds and there is more than one non-blank
group. In this case, the final comma is replaced with 'and'. eg.
'one billion, one million and twelve'.
*/
// Recombine the three-digit groups
string combined = groupTexts[0];
bool appendAnd;
// Determine whether an 'and' is needed
appendAnd = (digitGroups[0] > 0) && (digitGroups[0] < 100);
// Process the remaining groups in turn, smallest to largest
for (int i = 1; i < groups; i++)
{
// Only add non-zero items
if (digitGroups[i] != 0)
{
// Build the string to add as a prefix
string prefix = groupTexts[i] + " " + scaleNumbers[i];
if (combined.Length != 0)
{
prefix += appendAnd ? " and " : ", ";
}
// Opportunity to add 'and' is ended
appendAnd = false;
// Add the three-digit group to the combined string
combined = prefix + combined;
}
}
// Converts a three-digit group into English words
string ThreeDigitGroupToWords(int threeDigits)
{
// Initialise the return text
string groupText = "";
// Determine the hundreds and the remainder
int hundreds = threeDigits / 100;
int tensUnits = threeDigits % 100;
/*
Hundreds Rules.
If the hundreds portion of a three-digit group is not zero, the number of
hundreds is added as a word. If the three-digit group is exactly divisible
by one hundred, the text 'hundred' is appended. If not, the text
"hundred and" is appended. eg. 'two hundred' or 'one hundred and twelve'
*/
if (hundreds != 0)
{
groupText += smallNumbers[hundreds] + " hundred";
if (tensUnits != 0)
{
groupText += " and ";
}
}
// Determine the tens and units
int tens = tensUnits / 10;
int units = tensUnits % 10;
/* Tens Rules.
If the tens section of a three-digit group is two or higher, the appropriate
'-ty' word (twenty, thirty, etc.) is added to the text and followed by the
name of the third digit (unless the third digit is a zero, which is ignored).
If the tens and the units are both zero, no text is added. For any other value,
the name of the one or two-digit number is added as a special case.
*/
if (tens >= 2)
{
groupText += NumbersToWords.tens[tens];
if (units != 0)
{
groupText += " " + smallNumbers[units];
}
}
else if (tensUnits != 0)
groupText += smallNumbers[tensUnits];
return groupText;
}
/* Negative Rule.
Negative numbers are always preceded by the text 'negative'.
*/
if (number < 0)
{
combined = "negative " + combined;
}
return combined;
}
}
}