Repository: TheAlgorithms/Java
Pull request #6195 (2025-03-14 02:32:50)

Add feature to convert numeric words to their number representation
<!--
Thank you for your contribution!
In order to reduce the number of notifications sent to the maintainers, please:
- create your PR as draft, cf. https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests#draft-pull-requests,
- make sure that all of the CI checks pass,
- mark your PR as ready for review, cf. https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/changing-the-stage-of-a-pull-request#marking-a-pull-request-as-ready-for-review
-->

<!-- For completed items, change [ ] to [x] -->

- [X] I have read [CONTRIBUTING.md](https://github.com/TheAlgorithms/Java/blob/master/CONTRIBUTING.md).
- [X] This pull request is all my own work -- I have not plagiarized it.
- [X] All filenames are in PascalCase.
- [X] All functions and variable names follow Java naming conventions.
- [ ] All new algorithms have a URL in their comments that points to Wikipedia or other similar explanations.
- [X] All new code is formatted with `clang-format -i --style=file path/to/your/file.java`

## Features

- **Accurate Word-to-Number Conversion:** Converts English word representations of numbers into their numeric form, supporting values from small fractions to trillions.

- **Handles Decimals and Negatives:** Properly interprets decimal numbers (e.g., "three point one four" → 3.14) and negative values.

- **Robust Input Validation:** Detects invalid phrases, incorrect formatting, and duplicate words (e.g., "one hundred hundred" → Invalid).

- **BigDecimal Support:** Provides a method to directly convert word-based numbers into BigDecimal, ensuring precision for financial and scientific calculations.

- **Optimized for Readability:** Modular design ensures clean, maintainable, and extensible code.

## Example Usage
Here’s how you can use the WordsToNumber class in your Java application:
```java
import java.math.BigDecimal;

public class Main {
    public static void main(String[] args) {
        // Standard conversion
        System.out.println(WordsToNumber.convert("three hundred and forty two")); // Output: 342
        System.out.println(WordsToNumber.convert("negative five thousand two hundred and one")); // Output: -5201
        System.out.println(WordsToNumber.convert("one billion point zero zero zero zero one")); // Output: 1000000000.00001
        System.out.println(WordsToNumber.convert("negative zero point zero zero zero zero zero zero one")); // Output: -1E-7

        // BigDecimal conversion
        BigDecimal bd1 = WordsToNumber.convertToBigDecimal("one thousand five hundred and seventy five point zero three");
        System.out.println(bd1); // Output: 1575.03

        BigDecimal bd2 = WordsToNumber.convertToBigDecimal("negative zero point zero zero zero zero one");
        System.out.println(bd2); // Output: -0.00001

        BigDecimal bd3 = WordsToNumber.convertToBigDecimal("invalid input");
        System.out.println(bd3); // Output: null
    }
}
```

codecov-commenter: ## [Codecov](https://app.codecov.io/gh/TheAlgorithms/Java/pull/6195?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TheAlgorithms) Report
Attention: Patch coverage is `87.39496%` with `15 lines` in your changes missing coverage. Please review.
> Project coverage is 73.89%. Comparing base [(`e6073f8`)](https://app.codecov.io/gh/TheAlgorithms/Java/commit/e6073f8fefa7f1b63a9170ac56712f04469c5442?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TheAlgorithms) to head [(`75dfd9e`)](https://app.codecov.io/gh/TheAlgorithms/Java/commit/75dfd9e6dbacdde30861755c72d1893f274a1b99?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TheAlgorithms).

| [Files with missing lines](https://app.codecov.io/gh/TheAlgorithms/Java/pull/6195?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TheAlgorithms) | Patch % | Lines |
|---|---|---|
| [...a/com/thealgorithms/conversions/WordsToNumber.java](https://app.codecov.io/gh/TheAlgorithms/Java/pull/6195?src=pr&el=tree&filepath=src%2Fmain%2Fjava%2Fcom%2Fthealgorithms%2Fconversions%2FWordsToNumber.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TheAlgorithms#diff-c3JjL21haW4vamF2YS9jb20vdGhlYWxnb3JpdGhtcy9jb252ZXJzaW9ucy9Xb3Jkc1RvTnVtYmVyLmphdmE=) | 87.39% | [3 Missing and 12 partials :warning: ](https://app.codecov.io/gh/TheAlgorithms/Java/pull/6195?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TheAlgorithms) |

<details><summary>Additional details and impacted files</summary>


```diff
@@             Coverage Diff              @@
##             master    #6195      +/-   ##
============================================
+ Coverage     73.78%   73.89%   +0.10%     
- Complexity     5219     5257      +38     
============================================
  Files           667      668       +1     
  Lines         17983    18102     +119     
  Branches       3457     3488      +31     
============================================
+ Hits          13269    13376     +107     
- Misses         4196     4198       +2     
- Partials        518      528      +10     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/TheAlgorithms/Java/pull/6195?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TheAlgorithms).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TheAlgorithms).

<details><summary>🚀 New features to boost your workflow: </summary>

- ❄ [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
DenizAltunkapan: Your implementation is well-structured and effectively handles a variety of edge cases, including decimals, negative numbers, and large values. 
However, the `convert()` method is quite complex, with multiple nested conditions and a high cyclomatic complexity. Breaking it into smaller helper methods would improve readability and maintainability. Using an `Enum` instead of `HashMap` for number mappings could enhance clarity and reduce lookup overhead. Additionally, error handling could be streamlined by using exceptions instead of tracking an error message string.  
:)
DenizAltunkapan: Thank you for your patience. In my opinion, everything is working now. @siriak 

Patch:
diff --git a/DIRECTORY.md b/DIRECTORY.md
index f53a6220..fe9e440d 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -118,6 +118,7 @@
             * [TurkishToLatinConversion](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/TurkishToLatinConversion.java)
             * [UnitConversions](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/UnitConversions.java)
             * [UnitsConverter](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/UnitsConverter.java)
+            * [WordsToNumber](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/WordsToNumber.java)
           * datastructures
             * bags
               * [Bag](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/bags/Bag.java)
@@ -840,6 +841,7 @@
             * [TurkishToLatinConversionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/TurkishToLatinConversionTest.java)
             * [UnitConversionsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/UnitConversionsTest.java)
             * [UnitsConverterTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/UnitsConverterTest.java)
+            * [WordsToNumberTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/WordsToNumberTest.java)
           * datastructures
             * bag
               * [BagTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/bag/BagTest.java)
diff --git a/src/main/java/com/thealgorithms/conversions/WordsToNumber.java b/src/main/java/com/thealgorithms/conversions/WordsToNumber.java
new file mode 100644
index 00000000..e2b81a0f
--- /dev/null
+++ b/src/main/java/com/thealgorithms/conversions/WordsToNumber.java
@@ -0,0 +1,343 @@
+package com.thealgorithms.conversions;
+
+import java.io.Serial;
+import java.math.BigDecimal;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ A Java-based utility for converting English word representations of numbers
+ into their numeric form. This utility supports whole numbers, decimals,
+ large values up to trillions, and even scientific notation where applicable.
+ It ensures accurate parsing while handling edge cases like negative numbers,
+ improper word placements, and ambiguous inputs.
+ *
+ */
+
+public final class WordsToNumber {
+
+    private WordsToNumber() {
+    }
+
+    private enum NumberWord {
+        ZERO("zero", 0),
+        ONE("one", 1),
+        TWO("two", 2),
+        THREE("three", 3),
+        FOUR("four", 4),
+        FIVE("five", 5),
+        SIX("six", 6),
+        SEVEN("seven", 7),
+        EIGHT("eight", 8),
+        NINE("nine", 9),
+        TEN("ten", 10),
+        ELEVEN("eleven", 11),
+        TWELVE("twelve", 12),
+        THIRTEEN("thirteen", 13),
+        FOURTEEN("fourteen", 14),
+        FIFTEEN("fifteen", 15),
+        SIXTEEN("sixteen", 16),
+        SEVENTEEN("seventeen", 17),
+        EIGHTEEN("eighteen", 18),
+        NINETEEN("nineteen", 19),
+        TWENTY("twenty", 20),
+        THIRTY("thirty", 30),
+        FORTY("forty", 40),
+        FIFTY("fifty", 50),
+        SIXTY("sixty", 60),
+        SEVENTY("seventy", 70),
+        EIGHTY("eighty", 80),
+        NINETY("ninety", 90);
+
+        private final String word;
+        private final int value;
+
+        NumberWord(String word, int value) {
+            this.word = word;
+            this.value = value;
+        }
+
+        public static Integer getValue(String word) {
+            for (NumberWord num : values()) {
+                if (word.equals(num.word)) {
+                    return num.value;
+                }
+            }
+            return null;
+        }
+    }
+
+    private enum PowerOfTen {
+        THOUSAND("thousand", new BigDecimal("1000")),
+        MILLION("million", new BigDecimal("1000000")),
+        BILLION("billion", new BigDecimal("1000000000")),
+        TRILLION("trillion", new BigDecimal("1000000000000"));
+
+        private final String word;
+        private final BigDecimal value;
+
+        PowerOfTen(String word, BigDecimal value) {
+            this.word = word;
+            this.value = value;
+        }
+
+        public static BigDecimal getValue(String word) {
+            for (PowerOfTen power : values()) {
+                if (word.equals(power.word)) {
+                    return power.value;
+                }
+            }
+            return null;
+        }
+    }
+
+    public static String convert(String numberInWords) {
+        if (numberInWords == null) {
+            throw new WordsToNumberException(WordsToNumberException.ErrorType.NULL_INPUT, "");
+        }
+
+        ArrayDeque<String> wordDeque = preprocessWords(numberInWords);
+        BigDecimal completeNumber = convertWordQueueToBigDecimal(wordDeque);
+
+        return completeNumber.toString();
+    }
+
+    public static BigDecimal convertToBigDecimal(String numberInWords) {
+        String conversionResult = convert(numberInWords);
+        return new BigDecimal(conversionResult);
+    }
+
+    private static ArrayDeque<String> preprocessWords(String numberInWords) {
+        String[] wordSplitArray = numberInWords.trim().split("[ ,-]");
+        ArrayDeque<String> wordDeque = new ArrayDeque<>();
+        for (String word : wordSplitArray) {
+            if (word.isEmpty()) {
+                continue;
+            }
+            wordDeque.add(word.toLowerCase());
+        }
+        if (wordDeque.isEmpty()) {
+            throw new WordsToNumberException(WordsToNumberException.ErrorType.NULL_INPUT, "");
+        }
+        return wordDeque;
+    }
+
+    private static void handleConjunction(boolean prevNumWasHundred, boolean prevNumWasPowerOfTen, ArrayDeque<String> wordDeque) {
+        if (wordDeque.isEmpty()) {
+            throw new WordsToNumberException(WordsToNumberException.ErrorType.INVALID_CONJUNCTION, "");
+        }
+
+        String nextWord = wordDeque.pollFirst();
+        String afterNextWord = wordDeque.peekFirst();
+
+        wordDeque.addFirst(nextWord);
+
+        Integer number = NumberWord.getValue(nextWord);
+
+        boolean isPrevWordValid = prevNumWasHundred || prevNumWasPowerOfTen;
+        boolean isNextWordValid = number != null && (number >= 10 || afterNextWord == null || "point".equals(afterNextWord));
+
+        if (!isPrevWordValid || !isNextWordValid) {
+            throw new WordsToNumberException(WordsToNumberException.ErrorType.INVALID_CONJUNCTION, "");
+        }
+    }
+
+    private static BigDecimal handleHundred(BigDecimal currentChunk, String word, boolean prevNumWasPowerOfTen) {
+        boolean currentChunkIsZero = currentChunk.compareTo(BigDecimal.ZERO) == 0;
+        if (currentChunk.compareTo(BigDecimal.TEN) >= 0 || prevNumWasPowerOfTen) {
+            throw new WordsToNumberException(WordsToNumberException.ErrorType.UNEXPECTED_WORD, word);
+        }
+        if (currentChunkIsZero) {
+            currentChunk = currentChunk.add(BigDecimal.ONE);
+        }
+        return currentChunk.multiply(BigDecimal.valueOf(100));
+    }
+
+    private static void handlePowerOfTen(List<BigDecimal> chunks, BigDecimal currentChunk, BigDecimal powerOfTen, String word, boolean prevNumWasPowerOfTen) {
+        boolean currentChunkIsZero = currentChunk.compareTo(BigDecimal.ZERO) == 0;
+        if (currentChunkIsZero || prevNumWasPowerOfTen) {
+            throw new WordsToNumberException(WordsToNumberException.ErrorType.UNEXPECTED_WORD, word);
+        }
+        BigDecimal nextChunk = currentChunk.multiply(powerOfTen);
+
+        if (!(chunks.isEmpty() || isAdditionSafe(chunks.getLast(), nextChunk))) {
+            throw new WordsToNumberException(WordsToNumberException.ErrorType.UNEXPECTED_WORD, word);
+        }
+        chunks.add(nextChunk);
+    }
+
+    private static BigDecimal handleNumber(Collection<BigDecimal> chunks, BigDecimal currentChunk, String word, Integer number) {
+        boolean currentChunkIsZero = currentChunk.compareTo(BigDecimal.ZERO) == 0;
+        if (number == 0 && !(currentChunkIsZero && chunks.isEmpty())) {
+            throw new WordsToNumberException(WordsToNumberException.ErrorType.UNEXPECTED_WORD, word);
+        }
+        BigDecimal bigDecimalNumber = BigDecimal.valueOf(number);
+
+        if (!currentChunkIsZero && !isAdditionSafe(currentChunk, bigDecimalNumber)) {
+            throw new WordsToNumberException(WordsToNumberException.ErrorType.UNEXPECTED_WORD, word);
+        }
+        return currentChunk.add(bigDecimalNumber);
+    }
+
+    private static void handlePoint(Collection<BigDecimal> chunks, BigDecimal currentChunk, ArrayDeque<String> wordDeque) {
+        boolean currentChunkIsZero = currentChunk.compareTo(BigDecimal.ZERO) == 0;
+        if (!currentChunkIsZero) {
+            chunks.add(currentChunk);
+        }
+
+        String decimalPart = convertDecimalPart(wordDeque);
+        chunks.add(new BigDecimal(decimalPart));
+    }
+
+    private static void handleNegative(boolean isNegative) {
+        if (isNegative) {
+            throw new WordsToNumberException(WordsToNumberException.ErrorType.MULTIPLE_NEGATIVES, "");
+        }
+        throw new WordsToNumberException(WordsToNumberException.ErrorType.INVALID_NEGATIVE, "");
+    }
+
+    private static BigDecimal convertWordQueueToBigDecimal(ArrayDeque<String> wordDeque) {
+        BigDecimal currentChunk = BigDecimal.ZERO;
+        List<BigDecimal> chunks = new ArrayList<>();
+
+        boolean isNegative = "negative".equals(wordDeque.peek());
+        if (isNegative) {
+            wordDeque.poll();
+        }
+
+        boolean prevNumWasHundred = false;
+        boolean prevNumWasPowerOfTen = false;
+
+        while (!wordDeque.isEmpty()) {
+            String word = wordDeque.poll();
+
+            switch (word) {
+                case "and" -> {
+                    handleConjunction(prevNumWasHundred, prevNumWasPowerOfTen, wordDeque);
+                    continue;
+                }
+                case "hundred" -> {
+                    currentChunk = handleHundred(currentChunk, word, prevNumWasPowerOfTen);
+                    prevNumWasHundred = true;
+                    continue;
+                }
+                default -> {
+
+                }
+            }
+            prevNumWasHundred = false;
+
+            BigDecimal powerOfTen = PowerOfTen.getValue(word);
+            if (powerOfTen != null) {
+                handlePowerOfTen(chunks, currentChunk, powerOfTen, word, prevNumWasPowerOfTen);
+                currentChunk = BigDecimal.ZERO;
+                prevNumWasPowerOfTen = true;
+                continue;
+            }
+            prevNumWasPowerOfTen = false;
+
+            Integer number = NumberWord.getValue(word);
+            if (number != null) {
+                currentChunk = handleNumber(chunks, currentChunk, word, number);
+                continue;
+            }
+
+            switch (word) {
+                case "point" -> {
+                    handlePoint(chunks, currentChunk, wordDeque);
+                    currentChunk = BigDecimal.ZERO;
+                    continue;
+                }
+                case "negative" -> {
+                    handleNegative(isNegative);
+                }
+                default -> {
+
+                }
+            }
+
+            throw new WordsToNumberException(WordsToNumberException.ErrorType.UNKNOWN_WORD, word);
+        }
+
+        if (currentChunk.compareTo(BigDecimal.ZERO) != 0) {
+            chunks.add(currentChunk);
+        }
+
+        BigDecimal completeNumber = combineChunks(chunks);
+        return isNegative ? completeNumber.multiply(BigDecimal.valueOf(-1))
+                :
+                    completeNumber;
+                }
+
+                private static boolean isAdditionSafe(BigDecimal currentChunk, BigDecimal number) {
+                    int chunkDigitCount = currentChunk.toString().length();
+                    int numberDigitCount = number.toString().length();
+                    return chunkDigitCount > numberDigitCount;
+                }
+
+                private static String convertDecimalPart(ArrayDeque<String> wordDeque) {
+                    StringBuilder decimalPart = new StringBuilder(".");
+
+                    while (!wordDeque.isEmpty()) {
+                        String word = wordDeque.poll();
+                        Integer number = NumberWord.getValue(word);
+                        if (number == null) {
+                            throw new WordsToNumberException(WordsToNumberException.ErrorType.UNEXPECTED_WORD_AFTER_POINT, word);
+                        }
+                        decimalPart.append(number);
+                    }
+
+                    boolean missingNumbers = decimalPart.length() == 1;
+                    if (missingNumbers) {
+                        throw new WordsToNumberException(WordsToNumberException.ErrorType.MISSING_DECIMAL_NUMBERS, "");
+                    }
+                    return decimalPart.toString();
+                }
+
+                private static BigDecimal combineChunks(List<BigDecimal> chunks) {
+                    BigDecimal completeNumber = BigDecimal.ZERO;
+                    for (BigDecimal chunk : chunks) {
+                        completeNumber = completeNumber.add(chunk);
+                    }
+                    return completeNumber;
+                }
+        }
+
+        class WordsToNumberException extends RuntimeException {
+
+                @Serial private static final long serialVersionUID = 1L;
+
+                enum ErrorType {
+                    NULL_INPUT("'null' or empty input provided"),
+                    UNKNOWN_WORD("Unknown Word: "),
+                    UNEXPECTED_WORD("Unexpected Word: "),
+                    UNEXPECTED_WORD_AFTER_POINT("Unexpected Word (after Point): "),
+                    MISSING_DECIMAL_NUMBERS("Decimal part is missing numbers."),
+                    MULTIPLE_NEGATIVES("Multiple 'Negative's detected."),
+                    INVALID_NEGATIVE("Incorrect 'negative' placement"),
+                    INVALID_CONJUNCTION("Incorrect 'and' placement");
+
+                    private final String message;
+
+                    ErrorType(String message) {
+                        this.message = message;
+                    }
+
+                    public String formatMessage(String details) {
+                        return "Invalid Input. " + message + (details.isEmpty() ? "" : details);
+                    }
+                }
+
+                public final ErrorType errorType;
+
+                WordsToNumberException(ErrorType errorType, String details) {
+                    super(errorType.formatMessage(details));
+                    this.errorType = errorType;
+                }
+
+                public ErrorType getErrorType() {
+                    return errorType;
+                }
+        }


Test patch:
diff --git a/src/test/java/com/thealgorithms/conversions/WordsToNumberTest.java b/src/test/java/com/thealgorithms/conversions/WordsToNumberTest.java
new file mode 100644
index 00000000..fbf63e37
--- /dev/null
+++ b/src/test/java/com/thealgorithms/conversions/WordsToNumberTest.java
@@ -0,0 +1,114 @@
+package com.thealgorithms.conversions;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.math.BigDecimal;
+import org.junit.jupiter.api.Test;
+
+public class WordsToNumberTest {
+
+    @Test
+    void testNullInput() {
+        WordsToNumberException exception = assertThrows(WordsToNumberException.class, () -> WordsToNumber.convert(null));
+        assertEquals(WordsToNumberException.ErrorType.NULL_INPUT, exception.getErrorType(), "Exception should be of type NULL_INPUT");
+    }
+
+    @Test
+    void testStandardCases() {
+        assertEquals("0", WordsToNumber.convert("zero"), "'zero' should convert to '0'");
+        assertEquals("5", WordsToNumber.convert("five"), "'five' should convert to '5'");
+        assertEquals("21", WordsToNumber.convert("twenty one"), "'twenty one' should convert to '21'");
+        assertEquals("101", WordsToNumber.convert("one hundred one"), "'one hundred' should convert to '101'");
+        assertEquals("342", WordsToNumber.convert("three hundred and forty two"), "'three hundred and forty two' should convert to '342'");
+    }
+
+    @Test
+    void testLargeNumbers() {
+        assertEquals("1000000", WordsToNumber.convert("one million"), "'one million' should convert to '1000000'");
+        assertEquals("1000000000", WordsToNumber.convert("one billion"), "'one billion' should convert to '1000000000'");
+        assertEquals("1000000000000", WordsToNumber.convert("one trillion"), "'one trillion' should convert to '1000000000000'");
+        assertEquals("999000000900999", WordsToNumber.convert("nine hundred ninety nine trillion nine hundred thousand nine hundred and ninety nine"), "'nine hundred ninety nine trillion nine hundred thousand nine hundred and ninety nine' should convert to '999000000900999'");
+    }
+
+    @Test
+    void testNegativeNumbers() {
+        assertEquals("-5", WordsToNumber.convert("negative five"), "'negative five' should convert to '-5'");
+        assertEquals("-120", WordsToNumber.convert("negative one hundred and twenty"), "'negative one hundred and twenty' should convert correctly");
+    }
+
+    @Test
+    void testNegativeLargeNumbers() {
+        assertEquals("-1000000000000", WordsToNumber.convert("negative one trillion"), "'negative one trillion' should convert to '-1000000000000'");
+        assertEquals("-9876543210987", WordsToNumber.convert("Negative Nine Trillion Eight Hundred Seventy Six Billion Five Hundred Forty Three Million Two Hundred Ten Thousand Nine Hundred Eighty Seven"), "");
+    }
+
+    @Test
+    void testDecimalNumbers() {
+        assertEquals("3.1415", WordsToNumber.convert("three point one four one five"), "'three point one four one five' should convert to '3.1415'");
+        assertEquals("-2.718", WordsToNumber.convert("negative two point seven one eight"), "'negative two point seven one eight' should convert to '-2.718'");
+        assertEquals("-1E-7", WordsToNumber.convert("negative zero point zero zero zero zero zero zero one"), "'negative zero point zero zero zero zero zero zero one' should convert to '-1E-7'");
+    }
+
+    @Test
+    void testLargeDecimalNumbers() {
+        assertEquals("1000000000.0000000001", WordsToNumber.convert("one billion point zero zero zero zero zero zero zero zero zero one"), "Tests a large whole number with a tiny fractional part");
+        assertEquals("999999999999999.9999999999999",
+            WordsToNumber.convert("nine hundred ninety nine trillion nine hundred ninety nine billion nine hundred ninety nine million nine hundred ninety nine thousand nine hundred ninety nine point nine nine nine nine nine nine nine nine nine nine nine nine nine"),
+            "Tests maximum scale handling for large decimal numbers");
+        assertEquals("0.505", WordsToNumber.convert("zero point five zero five"), "Tests a decimal with an internal zero, ensuring correct parsing");
+        assertEquals("42.00000000000001", WordsToNumber.convert("forty two point zero zero zero zero zero zero zero zero zero zero zero zero zero one"), "Tests a decimal with leading zeros before a significant figure");
+        assertEquals("7.89E-7", WordsToNumber.convert("zero point zero zero zero zero zero zero seven eight nine"), "Tests scientific notation for a small decimal with multiple digits");
+        assertEquals("0.999999", WordsToNumber.convert("zero point nine nine nine nine nine nine"), "Tests a decimal close to one with multiple repeated digits");
+    }
+
+    @Test
+    void testCaseInsensitivity() {
+        assertEquals("21", WordsToNumber.convert("TWENTY-ONE"), "Uppercase should still convert correctly");
+        assertEquals("-100.0001", WordsToNumber.convert("negAtiVe OnE HuNdReD, point ZeRO Zero zERo ONE"), "Mixed case should still convert correctly");
+        assertEquals("-225647.00019", WordsToNumber.convert("nEgative twO HundRed, and twenty-Five thOusaNd, six huNdred Forty-Seven, Point zero zero zero One nInE"));
+    }
+
+    @Test
+    void testInvalidInputs() {
+        WordsToNumberException exception;
+
+        exception = assertThrows(WordsToNumberException.class, () -> WordsToNumber.convert("negative one hundred AlPha"));
+        assertEquals(WordsToNumberException.ErrorType.UNKNOWN_WORD, exception.getErrorType());
+
+        exception = assertThrows(WordsToNumberException.class, () -> WordsToNumber.convert("twenty thirteen"));
+        assertEquals(WordsToNumberException.ErrorType.UNEXPECTED_WORD, exception.getErrorType());
+
+        exception = assertThrows(WordsToNumberException.class, () -> WordsToNumber.convert("negative negative ten"));
+        assertEquals(WordsToNumberException.ErrorType.MULTIPLE_NEGATIVES, exception.getErrorType());
+
+        exception = assertThrows(WordsToNumberException.class, () -> WordsToNumber.convert("one hundred hundred"));
+        assertEquals(WordsToNumberException.ErrorType.UNEXPECTED_WORD, exception.getErrorType());
+
+        exception = assertThrows(WordsToNumberException.class, () -> WordsToNumber.convert("one thousand and hundred"));
+        assertEquals(WordsToNumberException.ErrorType.INVALID_CONJUNCTION, exception.getErrorType());
+
+        exception = assertThrows(WordsToNumberException.class, () -> WordsToNumber.convert("one thousand hundred"));
+        assertEquals(WordsToNumberException.ErrorType.UNEXPECTED_WORD, exception.getErrorType());
+
+        exception = assertThrows(WordsToNumberException.class, () -> WordsToNumber.convert("nine hundred and nine hundred"));
+        assertEquals(WordsToNumberException.ErrorType.INVALID_CONJUNCTION, exception.getErrorType());
+
+        exception = assertThrows(WordsToNumberException.class, () -> WordsToNumber.convert("forty two point"));
+        assertEquals(WordsToNumberException.ErrorType.MISSING_DECIMAL_NUMBERS, exception.getErrorType());
+
+        exception = assertThrows(WordsToNumberException.class, () -> WordsToNumber.convert("sixty seven point hello"));
+        assertEquals(WordsToNumberException.ErrorType.UNEXPECTED_WORD_AFTER_POINT, exception.getErrorType());
+
+        exception = assertThrows(WordsToNumberException.class, () -> WordsToNumber.convert("one negative"));
+        assertEquals(WordsToNumberException.ErrorType.INVALID_NEGATIVE, exception.getErrorType());
+    }
+
+    @Test
+    void testConvertToBigDecimal() {
+        assertEquals(new BigDecimal("-100000000000000.056"), WordsToNumber.convertToBigDecimal("negative one hundred trillion point zero five six"), "should convert to appropriate BigDecimal value");
+
+        WordsToNumberException exception = assertThrows(WordsToNumberException.class, () -> WordsToNumber.convertToBigDecimal(null));
+        assertEquals(WordsToNumberException.ErrorType.NULL_INPUT, exception.getErrorType(), "Exception should be of type NULL_INPUT");
+    }
+}