Repository: MycroftAI/lingua-franca

Issue:
extractnumber_en() summing "leading hundreds"
```python
>>> extract_number("ninety nine million")
99000000
>>> extract_number("nine hundred ninety nine million")
99000900


Discussion:

Patch:
diff --git a/lingua_franca/lang/parse_en.py b/lingua_franca/lang/parse_en.py
index cf316d9..8054bf0 100644
--- a/lingua_franca/lang/parse_en.py
+++ b/lingua_franca/lang/parse_en.py
@@ -406,7 +406,9 @@ def _extract_whole_number_with_text_en(tokens, short_scale, ordinals):
 
         # is the prev word a number and should we sum it?
         # twenty two, fifty six
-        if prev_word in _SUMS and val and val < 10:
+        if (prev_word in _SUMS and val and val < 10) or all([prev_word in
+                                                             multiplies,
+                                                             val < prev_val if prev_val else False]):
             val = prev_val + val
 
         # is the prev word a number and should we multiply it?
@@ -445,23 +447,86 @@ def _extract_whole_number_with_text_en(tokens, short_scale, ordinals):
 
         else:
             if all([
-                prev_word in _SUMS,
-                word not in _SUMS,
-                word not in multiplies,
-                current_val >= 10]):
+                    prev_word in _SUMS,
+                    word not in _SUMS,
+                    word not in multiplies,
+                    current_val >= 10]):
                 # Backtrack - we've got numbers we can't sum.
                 number_words.pop()
                 val = prev_val
                 break
             prev_val = val
 
-            # handle long numbers
-            # six hundred sixty six
-            # two million five hundred thousand
             if word in multiplies and next_word not in multiplies:
-                to_sum.append(val)
-                val = 0
-                prev_val = 0
+                # handle long numbers
+                # six hundred sixty six
+                # two million five hundred thousand
+                #
+                # This logic is somewhat complex, and warrants
+                # extensive documentation for the next coder's sake.
+                #
+                # The current word is a power of ten. `current_val` is
+                # its integer value. `val` is our working sum
+                # (above, when `current_val` is 1 million, `val` is
+                # 2 million.)
+                #
+                # We have a dict `string_num_scale` containing [value, word]
+                # pairs for "all" powers of ten: string_num_scale[10] == "ten.
+                #
+                # We need go over the rest of the tokens, looking for other
+                # powers of ten. If we find one, we compare it with the current
+                # value, to see if it's smaller than the current power of ten.
+                #
+                # Numbers which are not powers of ten will be passed over.
+                #
+                # If all the remaining powers of ten are smaller than our
+                # current value, we can set the current value aside for later,
+                # and begin extracting another portion of our final result.
+                # For example, suppose we have the following string.
+                # The current word is "million".`val` is 9000000.
+                # `current_val` is 1000000.
+                #
+                #    "nine **million** nine *hundred* seven **thousand**
+                #     six *hundred* fifty seven"
+                #
+                # Iterating over the rest of the string, the current
+                # value is larger than all remaining powers of ten.
+                #
+                # The if statement passes, and nine million (9000000)
+                # is appended to `to_sum`.
+                #
+                # The main variables are reset, and the main loop begins
+                # assembling another number, which will also be appended
+                # under the same conditions.
+                #
+                # By the end of the main loop, to_sum will be a list of each
+                # "place" from 100 up: [9000000, 907000, 600]
+                #
+                # The final three digits will be added to the sum of that list
+                # at the end of the main loop, to produce the extracted number:
+                #
+                #    sum([9000000, 907000, 600]) + 57
+                # == 9,000,000 + 907,000 + 600 + 57
+                # == 9,907,657
+                #
+                # >>> foo = "nine million nine hundred seven thousand six
+                #            hundred fifty seven"
+                # >>> extract_number(foo)
+                # 9907657
+
+                time_to_sum = True
+                for other_token in tokens[idx+1:]:
+                    if other_token.word in multiplies:
+                        if string_num_scale[other_token.word] >= current_val:
+                            time_to_sum = False
+                        else:
+                            continue
+                    if not time_to_sum:
+                        break
+                if time_to_sum:
+                    to_sum.append(val)
+                    val = 0
+                    prev_val = 0
 
     if val is not None and to_sum:
         val += sum(to_sum)


Test patch:
diff --git a/test/test_parse.py b/test/test_parse.py
index 845dc14..5046f42 100644
--- a/test/test_parse.py
+++ b/test/test_parse.py
@@ -136,11 +136,20 @@ class TestNormalize(unittest.TestCase):
         self.assertEqual(extract_number("twenty thousand"), 20000)
         self.assertEqual(extract_number("fifty million"), 50000000)
 
-        # This test fails due to 
-        # self.assertEqual(extract_number("twenty billion three hundred million \
-        #                                 nine hundred fifty thousand six hundred \
-        #                                 seventy five point eight six"),
-        #                                 20300950675.86)
+        # Verify smaller powers of ten no longer cause miscalculation of larger
+        # powers of ten (see MycroftAI#86)
+        self.assertEqual(extract_number("twenty billion three hundred million \
+                                        nine hundred fifty thousand six hundred \
+                                        seventy five point eight"),
+                         20300950675.8)
+        self.assertEqual(extract_number("nine hundred ninety nine million nine \
+                                        hundred ninety nine thousand nine \
+                                        hundred ninety nine point nine"),
+                         999999999.9)
+
+        # TODO why does "trillion" result in xxxx.0?
+        self.assertEqual(extract_number("eight hundred trillion two hundred \
+                                        fifty seven"), 800000000000257.0)
 
         # TODO handle this case
         # self.assertEqual(