Here is a sample I played with. Hope it's useful.
I'd consider decomposing this into reusable functions.
package com.bluenoteandroid.experimental.ints;
import static com.google.common.base.Optional.*;
import static com.google.common.base.Preconditions.*;
import static java.lang.Math.*;
import java.util.Arrays;
import java.util.LinkedList;
import com.google.common.annotations.Beta;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
public final class Ints {
public static final int BASE_10 = 10;
public static final String MINUS = "minus";
public static final ImmutableMap<Integer, String> NUMBERS = ImmutableMap.<Integer, String>builder()
.put(0,"zero")
.put(1,"one")
.put(2,"two")
.put(3,"three")
.put(4,"four")
.put(5,"five")
.put(6,"six")
.put(7,"seven")
.put(8,"eight")
.put(9,"nine")
.put(10,"ten")
.put(11,"eleven")
.put(12,"twelve")
.put(13,"thirteen")
.put(14,"fourteen")
.put(15,"fifteen")
.put(16,"sixteen")
.put(17,"seventeen")
.put(18,"eighteen")
.put(19,"nineteen")
.put(20,"twenty")
.put(30,"thirty")
.put(40,"forty")
.put(50,"fifty")
.put(60,"sixty")
.put(70,"seventy")
.put(80,"eighty")
.put(90,"ninety")
.put(100,"hundred")
.put(1000,"thousand")
.put(1000000,"million")
.put(1000000000,"billion")
.build();
private Ints() { /* disabled */ }
public static boolean allZeros(int[] range) {
for (int n : range) {
if (n != 0) {
return false;
}
}
return true;
}
/**
* Counts digits in an integer
* @param number
* @return digits count in the number
*/
public static int digitCount(final int number) {
return (int) ((number == 0) ? 1 : log10(abs(number)) + 1);
}
/**
* Sums digits in an integer
* @param number number
* @return sum of the n's digits
*/
public static int digitSum(final int number) {
int _n = abs(number);
int sum = 0;
do {
sum += _n % BASE_10;
} while ((_n /= BASE_10) > 0);
return sum;
}
/**
* Gets digit index in an integer, counting from right
* @param number number
* @param index (from right)
* @return index-th digit from n
*/
public static int digitFromRight(final int number, final int index) {
checkElementIndex(index, digitCount(number));
return (int) ((abs(number) / pow(BASE_10, index)) % BASE_10);
}
/**
* Gets digit of index in an integer, counting from left
* @param number
* @param index (from left)
* @return index-th digit from n
*/
public static int digitFromLeft(final int number, final int index) {
checkElementIndex(index, digitCount(number));
return digitFromRight(number, digitCount(number) - index - 1);
}
/**
* Converts a number to the array of it's digits
* @param number number
* @return array of digits
*/
public static int[] digits(final int number) {
return digits(number, digitCount(number));
}
/**
* Converts a number to a reversed array of it's digits
* @param number number
* @return reversed array of digits
*/
public static int[] digitsReversed(final int number) {
return digitsReversed(number, digitCount(number));
}
private static int[] digits(final int number, final int digitCount) {
final int[] digits = new int[digitCount];
int _n = abs(number);
int i = digitCount - 1;
do {
digits[i--] = _n % BASE_10;
} while ((_n /= BASE_10) > 0);
return digits;
}
/**
* Converts a number to a reversed array of it's digits
* @param number number
* @param digitCount digit count
* @return reversed array of digits
*/
private static int[] digitsReversed(final int number, final int digitCount) {
final int[] reversedDigits = new int[digitCount];
int _n = abs(number);
int i = 0;
do {
reversedDigits[i++] = _n % BASE_10;
} while ((_n /= BASE_10) > 0);
return reversedDigits;
}
public static int fromDigits(final int[] digits) {
int n = 0;
int i = 0;
do {
n += digits[i++];
if (i >= digits.length) {
break;
}
n *= BASE_10;
} while (true);
return n;
}
public static int fromDigitsReversed(final int[] reversedDigits) {
int n = 0;
int i = reversedDigits.length;
do {
n += reversedDigits[--i];
if (i <= 0) {
break;
}
n *= BASE_10;
} while (true);
return n;
}
@Beta
public static String numberInWords(final int number) {
Optional<String> name = fromNullable(NUMBERS.get(number));
if (name.isPresent()) {
return name.get();
} else {
return numberToName(number);
}
}
private static String numberToName(int number) {
final LinkedList<String> words = Lists.newLinkedList();
final int[] digits = digitsReversed(number);
for (int factor = 0; factor < digits.length; factor += 3) {
final int[] range = Arrays.copyOfRange(digits, factor, factor + 3);
if (allZeros(range)) {
continue;
}
switch (factor) {
case 0: /* nothing */
break;
case 3: /* thousand */
words.addFirst(NUMBERS.get((int) pow(BASE_10, 3)));
break;
case 6: /* million */
words.addFirst(NUMBERS.get((int) pow(BASE_10, 6)));
break;
case 9: /* billion */
words.addFirst(NUMBERS.get((int) pow(BASE_10, 9)));
break;
default:
throw new IllegalStateException("Unknown factor: " + factor);
}
String part = tripletToWords(range);
words.addFirst(part);
}
if (number < 0) { // negative
words.addFirst(MINUS);
}
return Joiner.on(' ').join(words);
}
private static String tripletToWords(final int[] reversedDigits) {
checkArgument(reversedDigits.length == 3, "This is not a triplet of digits, size: " + reversedDigits.length);
final int number = fromDigitsReversed(reversedDigits);
final int[] range = Arrays.copyOfRange(reversedDigits, 0, 2);
final String dubletWords = dubletToWords(range);
if (number >= 100) {
final int thirdDigit = reversedDigits[2];
final int factor = BASE_10 * BASE_10;
final String dublet = allZeros(range) ? null : dubletWords;
return Joiner.on(' ').skipNulls().join(
NUMBERS.get(thirdDigit),
NUMBERS.get(factor),
dublet);
} else {
return dubletWords;
}
}
private static String dubletToWords(final int[] reversedDigits) {
checkArgument(reversedDigits.length == 2, "This is not a dublet of digits, size: " + reversedDigits.length);
final int number = fromDigitsReversed(reversedDigits);
Optional<String> name = fromNullable(NUMBERS.get(number));
if (name.isPresent()) {
return name.get();
} else {
final int firstDigit = reversedDigits[0];
final int secondDigit = reversedDigits[1];
final int tens = BASE_10 * secondDigit;
return Joiner.on('-').join(
NUMBERS.get(tens),
NUMBERS.get(firstDigit));
}
}
}
With unit tests:
package com.bluenoteandroid.experimental.ints;
import static com.bluenoteandroid.experimental.ints.Ints.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.util.Arrays;
import java.util.Collection;
import org.junit.Test;
import org.junit.experimental.categories.Categories;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.junit.runners.Suite.SuiteClasses;
import com.bluenoteandroid.experimental.ints.IntsTests.*;
@RunWith(Categories.class)
@SuiteClasses({
CountTest.class,
SumTest.class,
GetDigitRightTest.class,
GetDigitRightPeconditionsTest.class,
GetDigitLeftTest.class,
GetDigitLeftPeconditionsTest.class,
DigitArrayTest.class,
FromDigitArrayTest.class,
ReverseDigitArrayTest.class,
FromReverseDigitArrayTest.class,
NumberToWordTest.class
})
public class IntsTests {
@RunWith(value = Parameterized.class)
public static class CountTest {
private final int input;
private final int expected;
public CountTest(final int input, final int expected) {
this.input = input;
this.expected = expected;
}
@Parameters
public static Collection<Integer[]> data() {
return Arrays.asList(new Integer[][] {
{ 0, 1 }, /* input, expected */
{ -1, 1 },
{ 900003245, 9 },
});
}
@Test
public void shouldCountDigits() {
assertThat(digitCount(input), is(expected));
}
}
@RunWith(value = Parameterized.class)
public static class SumTest {
private final int input;
private final int expected;
public SumTest(final int input, final int expected) {
this.input = input;
this.expected = expected;
}
@Parameters
public static Collection<Integer[]> data() {
return Arrays.asList(new Integer[][] {
{ 0, 0 }, /* input, expected */
{ -1, 1 },
{ 1001, 2 },
{ 1234, 1+2+3+4 }
});
}
@Test
public void shouldSumDigits() {
assertThat(digitSum(input), is(expected));
}
}
@RunWith(value = Parameterized.class)
public static class GetDigitRightTest {
private final int input;
private final int index;
private final int expected;
public GetDigitRightTest(final int input, final int index, final int expected) {
this.input = input;
this.index = index;
this.expected = expected;
}
@Parameters
public static Collection<Integer[]> data() {
return Arrays.asList(new Integer[][] {
{ 0, 0, 0 }, /* input, expected */
{ -1004, 0, 4 },
{ 1234, 1, 3 },
{ -1234, 2, 2 },
{ 1234, 3, 1 },
});
}
@Test
public void shouldGetDigits() {
assertThat(digitFromRight(input, index), is(expected));
}
}
public static class GetDigitRightPeconditionsTest {
@Test(expected=IndexOutOfBoundsException.class)
public void shouldNotAllowNegativeIndex() {
digitFromRight(1234, -1);
}
@Test(expected=IndexOutOfBoundsException.class)
public void shouldNotAllowToBigIndex() {
digitFromRight(1234, 4);
}
}
@RunWith(value = Parameterized.class)
public static class GetDigitLeftTest {
private final int input;
private final int index;
private final int expected;
public GetDigitLeftTest(final int input, final int index, final int expected) {
this.input = input;
this.index = index;
this.expected = expected;
}
@Parameters
public static Collection<Integer[]> data() {
return Arrays.asList(new Integer[][] {
{ 0, 0, 0 }, /* input, expected */
{ -1004, 0, 1 },
{ 1234, 1, 2 },
{ -1234, 2, 3 },
{ 1234, 3, 4 },
});
}
@Test
public void shouldGetDigits() {
assertThat(digitFromLeft(input, index), is(expected));
}
}
public static class GetDigitLeftPeconditionsTest {
@Test(expected=IndexOutOfBoundsException.class)
public void shouldNotAllowNegativeIndex() {
digitFromLeft(1234, -1);
}
@Test(expected=IndexOutOfBoundsException.class)
public void shouldNotAllowToBigIndex() {
digitFromLeft(1234, 4);
}
}
public static class DigitArrayTest {
@Test
public void shouldGetAllDigits() {
final int[] result = digits(-1234);
final int[] expected = new int[] {1,2,3,4};
assertThat(result, is(expected));
}
}
public static class FromDigitArrayTest {
@Test
public void shouldConvertDigits() {
final int result = fromDigits(new int[] {1,2,3,4});
final int expected = 1234;
assertThat(result, is(expected));
}
}
public static class ReverseDigitArrayTest {
@Test
public void shouldGetAllDigits() {
final int[] result = digitsReversed(-1234);
final int[] expected = new int[] {4,3,2,1};
assertThat(result, is(expected));
}
}
public static class FromReverseDigitArrayTest {
@Test
public void shouldConvertDigits() {
final int result = fromDigitsReversed(new int[] {4,3,2,1});
final int expected = 1234;
assertThat(result, is(expected));
}
}
@RunWith(value = Parameterized.class)
public static class NumberToWordTest {
private final int input;
private final String expected;
public NumberToWordTest(final int input, final String expected) {
this.input = input;
this.expected = expected;
}
@Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][] {
{ 0, "zero" },
{ 1, "one" },
{ 10, "ten" },
{ 15, "fifteen" },
{ 60, "sixty" },
{ 67, "sixty-seven" },
{ 72, "seventy-two" },
{ 101, "one hundred one" },
{ 205, "two hundred five" },
{ 4589, "four thousand five hundred eighty-nine" },
{ 3333, "three thousand three hundred thirty-three" },
{ 67500, "sixty-seven thousand five hundred" },
{ 100000, "one hundred thousand" },
{ 100567, "one hundred thousand five hundred sixty-seven" },
{ 172346, "one hundred seventy-two thousand three hundred forty-six" },
{ 600700, "six hundred thousand seven hundred" },
{ 678900, "six hundred seventy-eight thousand nine hundred" },
{ 890000, "eight hundred ninety thousand" },
{ 999999, "nine hundred ninety-nine thousand nine hundred ninety-nine" },
{ 999999999, "nine hundred ninety-nine million nine hundred ninety-nine thousand nine hundred ninety-nine" },
{ 1999999999, "one billion nine hundred ninety-nine million nine hundred ninety-nine thousand nine hundred ninety-nine" },
{ -21239, "minus twenty-one thousand two hundred thirty-nine"},
});
}
@Test
public void test() {
assertEquals(expected, numberInWords(input));
}
}
}