• Jump To … +
    abbreviations.js adjectives.js convertables.js dates.js demonyms.js firstnames.js honourifics.js irregular_nouns.js irregular_verbs.js misc.js multiples.js numbers.js organisations.js phrasal_verbs.js places.js uncountables.js verbs.js fns.js index.js lexicon.js negate.js passive_voice.js contractions.js fancy_lumping.js grammar_rules.js parts_of_speech.js phrasal_verbs.js tagger.js word_rules.js question.js sentence.js statement.js tense.js adjective.js to_adverb.js to_comparative.js to_noun.js to_superlative.js adverb.js to_adjective.js is_acronym.js article.js date.js date_rules.js is_date.js parse_date.js is_plural.js is_uncountable.js noun.js is_organisation.js organisation.js gender.js is_person.js parse_name.js person.js is_place.js place.js pluralize.js pronoun.js singularize.js is_value.js numbers.js to_number.js units.js value.js term.js conjugate.js from_infinitive.js predict_form.js suffix_rules.js to_actor.js to_infinitive.js negate.js verb.js sentence_parser.js text.js
  • to_number.js

  • ¶

    converts spoken numbers into integers “fifty seven point eight” -> 57.8

    Spoken numbers take the following format [sixty five] (thousand) [sixty five] (hundred) [sixty five] aka: [one/teen/ten] (multiple) [one/teen/ten] (multiple) … combile the [one/teen/ten]s as ‘current_sum’, then multiply it by its following multiple multiple not repeat

    
    'use strict';
    const nums = require('./numbers.js');
  • ¶

    these sets of numbers each have different rules [tenth, hundreth, thousandth..] are ambiguous because they could be ordinal like fifth, or decimal like one-one-hundredth, so are ignored let decimal_multiple={‘tenth’:0.1, ‘hundredth’:0.01, ‘thousandth’:0.001, ‘millionth’:0.000001,’billionth’:0.000000001};

  • ¶

    test for nearly-values, like phonenumbers, or whatever

    const is_number = function(s) {
  • ¶

    phone numbers, etc

      if (s.match(/[:@]/)) {
        return false;
      }
  • ¶

    if there’s a number, then something, then a number

      if (s.match(/[0-9][^0-9,\.][0-9]/)) {
        return false;
      }
      return true;
    };
  • ¶

    try the best to turn this into a integer/float

    const to_number = function(s) {
      if (s === null || s === undefined) {
        return null;
      }
  • ¶

    if it’s already a number,

      if (typeof s === 'number') {
        return s;
      }
  • ¶

    remove symbols, commas, etc

      if (is_number(s) !== true) {
        return null;
      }
      s = s.replace(/[\$%\(\)~,]/g, '');
      s = s.trim();
  • ¶

    if it’s a number-as-string

      if (s.match(/^[0-9\.\-]+$/)) {
        return parseFloat(s);
      }
  • ¶

    remember these concerns for possible errors

      let ones_done = false;
      let teens_done = false;
      let tens_done = false;
      const multiple_done = {};
      let total = 0;
      let global_multiplier = 1;
  • ¶

    pretty-printed numbers

      s = s.replace(/, ?/g, '');
  • ¶

    parse-out currency

      s = s.replace(/[$£€]/, '');
  • ¶

    try to die fast. (phone numbers or times)

      if (s.match(/[0-9][\-:][0-9]/)) {
        return null;
      }
  • ¶

    support global multipliers, like ‘half-million’ by doing ‘million’ then multiplying by 0.5

      const mults = [{
        reg: /^(minus|negative)[\s\-]/i,
        mult: -1
      }, {
        reg: /^(a\s)?half[\s\-](of\s)?/i,
        mult: 0.5
      }, {
        reg: /^(a\s)?quarter[\s\-]/i,
        mult: 0.25
      }];
      for (let i = 0; i < mults.length; i++) {
        if (s.match(mults[i].reg)) {
          global_multiplier = mults[i].mult;
          s = s.replace(mults[i].reg, '');
          break;
        }
      }
  • ¶

    do each word in turn..

      const words = s.toString().split(/[\s\-]+/);
      let w, x;
      let current_sum = 0;
      let local_multiplier = 1;
      let decimal_mode = false;
      for (let i = 0; i < words.length; i++) {
        w = words[i];
  • ¶

    skip ‘and’ eg. five hundred and twelve

        if (w === 'and') {
          continue;
        }
  • ¶

    ..we’re doing decimals now

        if (w === 'point' || w === 'decimal') {
          if (decimal_mode) {
            return null;
          } //two point one point six
          decimal_mode = true;
          total += current_sum;
          current_sum = 0;
          ones_done = false;
          local_multiplier = 0.1;
          continue;
        }
  • ¶

    handle special rules following a decimal

        if (decimal_mode) {
          x = null;
  • ¶

    allow consecutive ones in decimals eg. ‘two point zero five nine’

          if (nums.ones[w] !== undefined) {
            x = nums.ones[w];
          }
          if (nums.teens[w] !== undefined) {
            x = nums.teens[w];
          }
          if (parseInt(w, 10) === w) {
            x = parseInt(w, 10);
          }
          if (!x) {
            return null;
          }
          if (x < 10) {
            total += x * local_multiplier;
            local_multiplier = local_multiplier * 0.1; // next number is next decimal place
            current_sum = 0;
            continue;
          }
  • ¶

    two-digit decimals eg. ‘two point sixteen’

          if (x < 100) {
            total += x * (local_multiplier * 0.1);
            local_multiplier = local_multiplier * 0.01; // next number is next decimal place
            current_sum = 0;
            continue;
          }
        }
  • ¶

    if it’s already an actual number

        if (w.match(/^[0-9\.]+$/)) {
          current_sum += parseFloat(w);
          continue;
        }
        if (parseInt(w, 10) === w) {
          current_sum += parseInt(w, 10);
          continue;
        }
  • ¶

    ones rules

        if (nums.ones[w] !== undefined) {
          if (ones_done) {
            return null;
          } // eg. five seven
          if (teens_done) {
            return null;
          } // eg. five seventeen
          ones_done = true;
          current_sum += nums.ones[w];
          continue;
        }
  • ¶

    teens rules

        if (nums.teens[w]) {
          if (ones_done) {
            return null;
          } // eg. five seventeen
          if (teens_done) {
            return null;
          } // eg. fifteen seventeen
          if (tens_done) {
            return null;
          } // eg. sixty fifteen
          teens_done = true;
          current_sum += nums.teens[w];
          continue;
        }
  • ¶

    tens rules

        if (nums.tens[w]) {
          if (ones_done) {
            return null;
          } // eg. five seventy
          if (teens_done) {
            return null;
          } // eg. fiveteen seventy
          if (tens_done) {
            return null;
          } // eg. twenty seventy
          tens_done = true;
          current_sum += nums.tens[w];
          continue;
        }
  • ¶

    multiple rules

        if (nums.multiples[w]) {
          if (multiple_done[w]) {
            return null;
          } // eg. five hundred six hundred
          multiple_done[w] = true;
  • ¶

    reset our concerns. allow ‘five hundred five’

          ones_done = false;
          teens_done = false;
          tens_done = false;
  • ¶

    case of ‘hundred million’, (2 consecutive multipliers)

          if (current_sum === 0) {
            total = total || 1; //dont ever multiply by 0
            total *= nums.multiples[w];
          } else {
            current_sum *= nums.multiples[w];
            total += current_sum;
          }
          current_sum = 0;
          continue;
        }
  • ¶

    if word is not a known thing now, die

        return null;
      }
      if (current_sum) {
        total += (current_sum || 1) * local_multiplier;
      }
  • ¶

    combine with global multiplier, like ‘minus’ or ‘half’

      total = total * global_multiplier;
    
      return total;
    };
  • ¶

    console.log(to_number(‘minus five hundred’)); console.log(to_number(“a hundred”)) console.log(to_number(‘four point six’));

  • ¶

    kick it into module

    module.exports = to_number;