summaryrefslogtreecommitdiff
path: root/src/main/java/bjc/inflexion/NumberUtils.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/bjc/inflexion/NumberUtils.java')
-rw-r--r--src/main/java/bjc/inflexion/NumberUtils.java570
1 files changed, 570 insertions, 0 deletions
diff --git a/src/main/java/bjc/inflexion/NumberUtils.java b/src/main/java/bjc/inflexion/NumberUtils.java
new file mode 100644
index 0000000..3a0200d
--- /dev/null
+++ b/src/main/java/bjc/inflexion/NumberUtils.java
@@ -0,0 +1,570 @@
+package bjc.inflexion;
+
+/**
+ * A variety of functions for doing useful stuff with numbers.
+ *
+ * @author EVE
+ *
+ */
+public class NumberUtils {
+ /*
+ * @TODO 2/12/18 Ben Culkin :RomanExpansion
+ *
+ * Use U+305 for large roman numerals, as well as excels 'concise'
+ * numerals (as implemented by roman()).
+ */
+
+ /**
+ * Convert a number into a roman numeral.
+ *
+ * @param number
+ * The number to convert.
+ * @param classic
+ * Whether to use classic roman numerals (use IIII
+ * instead of IV, and such).
+ * @return The number as a roman numeral.
+ */
+ public static String toRoman(long number, boolean classic) {
+ StringBuilder work = new StringBuilder();
+
+ long currNumber = number;
+
+ if (currNumber == 0) { return "N"; }
+
+ if (currNumber < 0) {
+ currNumber *= -1;
+
+ work.append("-");
+ }
+
+ if (currNumber >= 1000) {
+ int numM = (int) (currNumber / 1000);
+ currNumber = currNumber % 1000;
+
+ for (int i = 0; i < numM; i++) {
+ work.append("M");
+ }
+ }
+
+ if (currNumber >= 900 && !classic) {
+ currNumber = currNumber % 900;
+
+ work.append("CM");
+ }
+
+ if (currNumber >= 500) {
+ currNumber = currNumber % 500;
+
+ work.append("D");
+ }
+
+ if (currNumber >= 400 && !classic) {
+ currNumber = currNumber % 400;
+
+ work.append("CD");
+ }
+
+ if (currNumber >= 100) {
+ int numC = (int) (currNumber / 100);
+ currNumber = currNumber % 100;
+
+ for (int i = 0; i < numC; i++) {
+ work.append("C");
+ }
+ }
+
+ if (currNumber >= 90 && !classic) {
+ currNumber = currNumber % 90;
+
+ work.append("XC");
+ }
+
+ if (currNumber >= 50) {
+ currNumber = currNumber % 50;
+
+ work.append("L");
+ }
+
+ if (currNumber >= 40 && !classic) {
+ currNumber = currNumber % 40;
+
+ work.append("XL");
+ }
+
+ if (currNumber >= 10) {
+ int numX = (int) (currNumber / 10);
+ currNumber = currNumber % 10;
+
+ for (int i = 0; i < numX; i++) {
+ work.append("X");
+ }
+ }
+
+ if (currNumber >= 9 && !classic) {
+ currNumber = currNumber % 9;
+
+ work.append("IX");
+ }
+
+ if (currNumber >= 5) {
+ currNumber = currNumber % 5;
+
+ work.append("V");
+ }
+
+ if (currNumber >= 4 && !classic) {
+ currNumber = currNumber % 4;
+
+ work.append("IV");
+ }
+
+ if (currNumber >= 1) {
+ int numI = (int) (currNumber / 1);
+ currNumber = currNumber % 1;
+
+ for (int i = 0; i < numI; i++) {
+ work.append("I");
+ }
+ }
+
+ return work.toString();
+ }
+
+ private static String[] summaryNums = new String[] { "no", "one", "a couple of", "a few", "several" };
+
+ private static String[] summaryNumsEnd = new String[] { "none", "one", "a couple", "a few", "several" };
+
+ private static int[] summaryMap = new int[] {
+ /* no */
+ 0,
+ /* one */
+ 1,
+ /* a couple of */
+ 2,
+ /* a few */
+ 3, 3, 3,
+ /* several */
+ 4, 4, 4, 4 };
+
+ /**
+ * Summarize an integer.
+ *
+ * @param num
+ * The number to summarize.
+ *
+ * @param atEnd
+ * Whether or not the integer is at the end of a string.
+ *
+ * @return A string summarizing the integer.
+ */
+ public static String summarizeNumber(final long num, final boolean atEnd) {
+ if (num >= 0 && num < 10) {
+ if (atEnd) return summaryNumsEnd[summaryMap[(int) num]];
+
+ return summaryNums[summaryMap[(int) num]];
+ }
+
+ return "many";
+ }
+
+ /**
+ * Convert a number into a cardinal number, up to a threshold.
+ *
+ * @param number
+ * The number to convert
+ * @param thresh
+ * The threshold to stop at.
+ * @return The number as a cardinal.
+ */
+ public static String toCardinal(long number, long thresh) {
+ if (number < thresh) return toCardinal(number, null);
+
+ return Long.toString(number);
+ }
+
+ /**
+ * Convert a number into a cardinal number.
+ *
+ * @param number
+ * The number to convert
+ * @return The number as a cardinal.
+ */
+ public static String toCardinal(long number) {
+ return toCardinal(number, null);
+ }
+
+ private static String[] cardinals = new String[] { "zero", "one", "two", "three", "four", "five", "six",
+ "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen",
+ "sixteen", "seventeen", "eighteen", "nineteen", "twenty", };
+
+ /**
+ * Convert a number into a cardinal number.
+ *
+ * @param number
+ * The number to convert to a cardinal.
+ * @param custom
+ * The customizations to use.
+ * @return The number as a cardinal.
+ */
+ public static String toCardinal(long number, CardinalState custom) {
+ if (custom != null) {
+ String res = custom.handleCustom(number);
+
+ if (res != null) return res;
+ }
+
+ if (number < 0) return "negative " + toCardinal(number * -1, custom);
+
+ if (number <= 20) return cardinals[(int) number];
+
+ if (number < 100) {
+ if (number % 10 == 0) {
+ switch ((int) number) {
+ case 30:
+ return "thirty";
+ case 40:
+ return "forty";
+ case 50:
+ return "fifty";
+ case 60:
+ return "sixty";
+ case 70:
+ return "seventy";
+ case 80:
+ return "eighty";
+ case 90:
+ return "ninety";
+ default:
+ /*
+ * Shouldn't happen.
+ */
+ assert (false);
+ }
+ }
+
+ long numTens = number / 10;
+ long numOnes = number % 10;
+
+ return toCardinal(numTens, custom) + "-" + toCardinal(numOnes, custom);
+ }
+
+ if (number < 1000) {
+ long numHundreds = number / 100;
+ long rest = number % 100;
+
+ return toCardinal(numHundreds, custom) + " hundred and " + toCardinal(rest, custom);
+ }
+
+ long MILLION = (long) (Math.pow(10, 6));
+ if (number < MILLION) {
+ long numThousands = number / 1000;
+ long rest = number % 1000;
+
+ return toCardinal(numThousands, custom) + " thousand, " + toCardinal(rest, custom);
+ }
+
+ long BILLION = (long) (Math.pow(10, 9));
+ if (number < BILLION) {
+ long numMillions = number / MILLION;
+ long rest = number % MILLION;
+
+ return toCardinal(numMillions, custom) + " million, " + toCardinal(rest, custom);
+ }
+
+ long TRILLION = (long) (Math.pow(10, 12));
+ if (number < TRILLION) {
+ long numBillions = number / BILLION;
+ long rest = number % BILLION;
+
+ return toCardinal(numBillions, custom) + " billion, " + toCardinal(rest, custom);
+ }
+
+ throw new IllegalArgumentException(
+ "Numbers greater than or equal to 1 trillion are not supported yet.");
+ }
+
+ /**
+ * Convert a number into an ordinal, up to a certain value.
+ *
+ * @param number
+ * The number to convert to an ordinal.
+ * @param thresh
+ * The threshold value to stop converting at.
+ * @return The number as an ordinal.
+ */
+ public static String toOrdinal(long number, long thresh) {
+ return toOrdinal(number, thresh, true);
+ }
+
+ /**
+ * Convert a number into an ordinal, up to a certain value.
+ *
+ * @param number
+ * The number to convert to an ordinal.
+ * @param thresh
+ * The threshold value to stop converting at.
+ * @param longForm
+ * Whether or not to use long-form ordinals (zeroth,
+ * first, etc.) instead of (0th, 1st, etc.)
+ * @return The number as an ordinal.
+ */
+ public static String toOrdinal(long number, long thresh, boolean longForm) {
+ if (number < thresh) return toOrdinal(number, longForm);
+
+ return Long.toString(number);
+ }
+
+ /**
+ * Convert a number into an ordinal.
+ *
+ * @param number
+ * The number to convert to an ordinal.
+ * @return The number as an ordinal.
+ */
+ public static String toOrdinal(long number) {
+ return toOrdinal(number, true);
+ }
+
+ /**
+ * Convert a number into an ordinal.
+ *
+ * @param number
+ * The number to convert to an ordinal.
+ * @param longForm
+ * Whether or not to use long-form ordinals (zeroth,
+ * first, etc.) instead of (0th, 1st, etc.)
+ * @return The number as an ordinal.
+ */
+ public static String toOrdinal(long number, boolean longForm) {
+ if (number < 0) { return "minus " + toOrdinal(number, longForm); }
+
+ if (longForm) {
+ if (number < 20) {
+ switch ((int) number) {
+ case 0:
+ return "zeroth";
+ case 1:
+ return "first";
+ case 2:
+ return "second";
+ case 3:
+ return "third";
+ case 4:
+ return "fourth";
+ case 5:
+ return "fifth";
+ case 6:
+ return "sixth";
+ case 7:
+ return "seventh";
+ case 8:
+ return "eighth";
+ case 9:
+ return "ninth";
+ case 10:
+ return "tenth";
+ case 11:
+ return "eleventh";
+ case 12:
+ return "twelfth";
+ case 13:
+ return "thirteenth";
+ case 14:
+ return "fourteenth";
+ case 15:
+ return "fifteenth";
+ case 16:
+ return "sixteenth";
+ case 17:
+ return "seventeenth";
+ case 18:
+ return "eighteenth";
+ case 19:
+ return "nineteenth";
+ default:
+ /*
+ * Shouldn't happen.
+ */
+ assert (false);
+ }
+ }
+
+ if (number < 100) {
+ if (number % 10 == 0) {
+ switch ((int) number) {
+ case 20:
+ return "twentieth";
+ case 30:
+ return "thirtieth";
+ case 40:
+ return "fortieth";
+ case 50:
+ return "fiftieth";
+ case 60:
+ return "sixtieth";
+ case 70:
+ return "seventieth";
+ case 80:
+ return "eightieth";
+ case 90:
+ return "ninetieth";
+ default:
+ throw new IllegalArgumentException(
+ String.format("Illegal number %d", number));
+ }
+ }
+
+ long numPostfix = number % 10;
+ return toCardinal(number - numPostfix) + "-" + toOrdinal(numPostfix, longForm);
+ }
+ }
+
+ long procNum = number % 100;
+ long tens = procNum / 10;
+ long ones = procNum % 10;
+
+ if (tens == 1) { return Long.toString(number) + "th"; }
+
+ switch ((int) ones) {
+ case 1:
+ return Long.toString(number) + "st";
+ case 2:
+ return Long.toString(number) + "nd";
+ case 3:
+ return Long.toString(number) + "rd";
+ default:
+ return Long.toString(number) + "th";
+ }
+ }
+
+ private static char[] radixChars = new char[62];
+ static {
+ int idx = 0;
+
+ for (char i = 0; i < 10; i++) {
+ radixChars[idx] = (char) ('0' + i);
+
+ idx += 1;
+ }
+
+ for (char i = 0; i < 26; i++) {
+ radixChars[idx] = (char) ('A' + i);
+
+ idx += 1;
+ }
+
+ for (char i = 0; i < 26; i++) {
+ radixChars[idx] = (char) ('a' + i);
+
+ idx += 1;
+ }
+ }
+
+ /**
+ * Convert a number into a commafied string.
+ *
+ * @param val
+ * The number to convert.
+ * @param mincols
+ * The minimum number of columns to use.
+ * @param padchar
+ * The padding char to use.
+ * @param commaInterval
+ * The interval to place commas at.
+ * @param commaChar
+ * The character to use as a comma
+ * @param signed
+ * Whether or not to always display a sign
+ * @param radix
+ * The radix to use
+ * @return The number as a commafied string.
+ */
+ public static String toCommaString(long val, int mincols, char padchar, int commaInterval, char commaChar,
+ boolean signed, int radix) {
+ if (radix > radixChars.length) { throw new IllegalArgumentException(String.format(
+ "Radix %d is larger than largest supported radix %d", radix, radixChars.length)); }
+
+ StringBuilder work = new StringBuilder();
+
+ boolean isNeg = false;
+ long currVal = val;
+ if (currVal < 0) {
+ isNeg = true;
+ currVal *= -1;
+ }
+
+ if (currVal == 0) {
+ work.append(radixChars[0]);
+ } else {
+ int valCounter = 0;
+
+ while (currVal != 0) {
+ valCounter += 1;
+
+ int radDigit = (int) (currVal % radix);
+ work.append(radixChars[radDigit]);
+ currVal = currVal / radix;
+
+ if (commaInterval != 0 && valCounter % commaInterval == 0 && currVal != 0)
+ work.append(commaChar);
+ }
+ }
+
+ if (isNeg)
+ work.append("-");
+ else if (signed) work.append("+");
+
+ work.reverse();
+
+ /*
+ * @TODO
+ *
+ * Should we have some way to specify how to pad?
+ *
+ * By this, I mean specify padding direction (left, right,
+ * balanced...)
+ */
+ StringBuilder pad = new StringBuilder();
+
+ if (work.length() < mincols) {
+ @SuppressWarnings("unused")
+ int padCount = 0;
+ for (int i = work.length(); i < mincols; i++) {
+ // @NOTE 9/6/18 :CommaPad
+ //
+ // I have no idea if this is the intended
+ // behavior, or if something is wrong with the
+ // example case in the menu
+ // if (commaInterval != 0 && padCount != 0) {
+ // if (Character.isDigit(padchar) && padCount % commaInterval == 0)
+ // pad.append(commaChar);
+ // else
+ pad.append(padchar);
+ // }
+
+ padCount++;
+ }
+ }
+
+ return pad.toString() + work.toString();
+ }
+
+ /**
+ * Convert a number to a normal commafied string.
+ *
+ * @param val
+ * The value to convert.
+ * @param mincols
+ * The minimum number of columns.
+ * @param padchar
+ * The padding char to use.
+ * @param signed
+ * Whether or not to display the sign.
+ * @param radix
+ * The radix to use.
+ * @return The number as a normal commafied string.
+ */
+ public static String toNormalString(long val, int mincols, char padchar, boolean signed, int radix) {
+ return toCommaString(val, mincols, padchar, 0, ',', signed, radix);
+ }
+}