How can I convert and validate user inputted fractions and decimals, into a decimal, in Google's Dart programming language?

StackOverflow https://stackoverflow.com/questions/21812084

I would like an approach that takes a users inputted fraction or decimal, that then validates it (in a reasonably forgiving way), and then converts it to a decimal.

Similar questions have been asked for other programming languages, but they all appear to rely on eval() which doesn't appear to exist in Dart, allows only for fractions, or requires the input to already be valid.

I would like it to handle the users input in the following ways:

    //generic
    print("'5': expect:5, actual:" + fractionStringToNum("5").toString());
    print("'5/8': expect:0.625, actual:" + fractionStringToNum("5/8").toString());
    print("'5 5/8': expect:5.625, actual:" + fractionStringToNum("5 5/8").toString());
    print("'5 10/8': expect:6.25, actual:" + fractionStringToNum("5 10/8").toString());
    print("'5.625': expect:5.625, actual:" + fractionStringToNum("5.625").toString());
    print("'3 1/3': expect:3.33333, actual:" + fractionStringToNum("3 1/3").toString());
    print("'0.625': expect:0.625, actual:" + fractionStringToNum("0.625").toString());
    print("'.625': expect:0.625, actual:" + fractionStringToNum(".625").toString());
    print("'0': expect:0, actual:" + fractionStringToNum("0").toString());

    //negatives
    print("'-5': expect:-5, actual:" + fractionStringToNum("-5").toString());
    print("'- 5': expect:NaN, actual:" + fractionStringToNum("- 5").toString());
    print("'-5.625': expect:-5.625, actual:" + fractionStringToNum("-5.625").toString());
    print("'-5 5/8': expect:-5.625, actual:" + fractionStringToNum("-5 5/8").toString());
    print("'5/-8': expect:-0.625, actual:" + fractionStringToNum("5/-8").toString());
    print("'5 5/-8': expect:4.375, actual:" + fractionStringToNum("5 5/-8").toString());
    print("'-5/-8': expect:0.625, actual:" + fractionStringToNum("-5/-8").toString());
    print("'-5 5/-8': expect:-4.375, actual:" + fractionStringToNum("-5 5/-8").toString());
    print("'- 5 - 5/8': expect:NaN, actual:" + fractionStringToNum("- 5 - 5/8").toString());
    print("'--5': expect:NaN, actual:" + fractionStringToNum("--5").toString());
    print("'-5-': expect:NaN, actual:" + fractionStringToNum("-5-").toString());
    print("'-asdf': expect:NaN, actual:" + fractionStringToNum("-asdf").toString());
    print("'- asdf': expect:NaN, actual:" + fractionStringToNum("- asdf").toString());

    //spaces
    print("' 5 5/8': expect:5.625, actual:" + fractionStringToNum(" 5 5/8").toString());
    print("'5  5/8': expect:5.625, actual:" + fractionStringToNum("5  5/8").toString());
    print("'5 / 8': expect:0.625, actual:" + fractionStringToNum("5 / 8").toString());
    print("'5 5 / 8': expect:5.625, actual:" + fractionStringToNum("5 5 / 8").toString());
    print("'- 5 5/8': expect:NaN, actual:" + fractionStringToNum("- 5 5/8").toString());
    print("'5 5': expect:NaN, actual:" + fractionStringToNum("5 5").toString());
    print("'5 .8': expect:NaN, actual:" + fractionStringToNum("5 .8").toString());
    print("'5 5 5/8': expect:NaN, actual:" + fractionStringToNum("5 5 5/8").toString());
    print("'5 5.8': expect:NaN, actual:" + fractionStringToNum("5 5.8").toString());
    print("'5 5 .8': expect:NaN, actual:" + fractionStringToNum("5 5 .8").toString());

    //fractions
    print("'5/5.625': expect:0.88888, actual:" + fractionStringToNum("5/5.625").toString());
    print("'5.625/8': expect:0.703125, actual:" + fractionStringToNum("5.625/8").toString());
    print("'5/.625': expect:8, actual:" + fractionStringToNum("5/.625").toString());
    print("'.625/8': expect:0.078125, actual:" + fractionStringToNum(".625/8").toString());
    print("'0/8': expect:0, actual:" + fractionStringToNum("0/8").toString());
    print("'0.625/0.625': expect:1, actual:" + fractionStringToNum("0.625/0.625").toString());
    print("'.625/.625': expect:1, actual:" + fractionStringToNum(".625/.625").toString());
    print("'5/0': expect:Infinity, actual:" + fractionStringToNum("5/0").toString());
    print("'0/0': expect:NaN, actual:" + fractionStringToNum("0/0").toString());
    print("'1 0/8': expect:1, actual:" + fractionStringToNum("1 0/8").toString());
    print("'1 5/0': expect:Infinity, actual:" + fractionStringToNum("1 5/0").toString());
    print("'1 0/0': expect:NaN, actual:" + fractionStringToNum("1 0/0").toString());
    print("'1/1/1970': expect:NaN, actual:" + fractionStringToNum("1/1/1970").toString());
    print("'1/1 1': expect:NaN, actual:" + fractionStringToNum("1/1 1").toString());
    print("'5/8 5/8': expect:NaN, actual:" + fractionStringToNum("5/8 5/8").toString());

    //strings
    print("'': expect:NaN, actual:" + fractionStringToNum("").toString());
    print("'asdf': expect:NaN, actual:" + fractionStringToNum("asdf").toString());
    print("'5/asdf': expect:NaN, actual:" + fractionStringToNum("5/asdf").toString());
    print("'asdf/8': expect:NaN, actual:" + fractionStringToNum("asdf/8").toString());
    print("'asdf/ghjkl': expect:NaN, actual:" + fractionStringToNum("asdf/ghjkl").toString());
    print("'5 asdf/ghjkl': expect:NaN, actual:" + fractionStringToNum("5 asdf/ghjkl").toString());
    print("'asdf.625': expect:NaN, actual:" + fractionStringToNum("asdf.625").toString());
    print("'5.asdf': expect:NaN, actual:" + fractionStringToNum("5.asdf").toString());
    print("'1 / asdf': expect:NaN, actual:" + fractionStringToNum("1 / asdf").toString());

    //misc
    print("'.625.625': expect:NaN, actual:" + fractionStringToNum(".625.625").toString());
    print("'.625 .625': expect:NaN, actual:" + fractionStringToNum(".625 .625").toString());
    print("'5.625 5/8': expect:NaN, actual:" + fractionStringToNum("2.1 1/2").toString());
    print("'.625 5/8': expect:NaN, actual:" + fractionStringToNum("2.1 1/2").toString());
有帮助吗?

解决方案

I guess the syntax you are trying to match is:

fraction ::= signed_number
           | signed_number / signed_number
           | signed_number number / signed_number
signed_number ::= number | '-' number
number ::= digit* '.' digit+ | digit+

with white-space allowed. When doing complex stuff, always ensure that you know what you are doing before you start. For parsing, that's usually about writing a grammar.

Using RegExp for parsing (which isn't necessarily better, just shorter), I'd do:

num fractionStringToNum(String string) {
  var numRE = r"\d*(?:\.\d+|\d)";
  var fracRE = "^\\s*(-)?($numRE)(?:\\s+($numRE))?(?:\\s*/\\s*(-)?($numRE))?\\s*\$";
  var re = new RegExp(fracRE);
  var match = re.firstMatch(string);
  if (match == null) return double.NAN;
  num result = num.parse(match[2]);
  if (match[3] != null) {
     double num2 = double.parse(match[3]);
     if (match[5] == null) return double.NAN;
     double num3 = double.parse(match[5]);
     // if (match[4] == '-') result = -result;  // Don't want to do this.
     result += num2 / num3;
  } else if (match[5] != null) {
     result /= double.parse(match[5]);
  }
  if (match[1] != match[4]) result = -result;
  return result;
}

This hits most of your test cases, but disagrees on "5 5/-8". I would just prefer to disallow that expression (a negative divisor when you have a compound expression), because I can't see any obvious interpretation of it - and more than one unobvious. I put the fix in the code, but commented it out because I don't like it :)

其他提示

There must be a cleaner and less complex way than what I have created so far below:

  double fractionStringToNum(String fraction) {
    //remove white space
    String frac = fraction.trim();

    double leftPart = 0.0;
    double rightPart = 0.0;
    double result = double.NAN;

    List spaceSplitNum = frac.split(' ');
    List decimalSplitNum = frac.split('.');
    List slashSplitNum = frac.split('/');

    if(slashSplitNum.length == 1){
      if(decimalSplitNum.length > 2){
        //there are multiple decimal components eg. '.625.625', '.625 .625'
        return double.NAN; 
      }
      if(spaceSplitNum.length > 1){
        //there are unneccessary spaces eg. '5 5', '5 .8', '5 5.8', '5 5 .8', '- 5', '- asdf'
        return double.NAN; 
      }
    }else if(slashSplitNum.length > 2){
      //there are multiple slashes eg. '1/1/1970', '5/8 5/8'
      return double.NAN; 
    }

    //attempt to seperate whole number component from fraction component
    if(slashSplitNum.length > 1){
      int i = 0;
      if(spaceSplitNum.length>1){   
        if(double.parse(spaceSplitNum[0], (e) {return double.NAN;}).isNaN){
          //there is spaces in unusual places eg. '1/1 1', '- 5 - 5/8'
          return double.NAN;  
        }else{
          leftPart = double.parse(spaceSplitNum[0], (e) {return double.NAN;});
          i = 1;
        }
      }

      //handle additional spaces eg. '5 5 / 8'
      frac = "";
      bool lastSegmentWasANumber = false;
      for(i; i<spaceSplitNum.length; i++){
        if(lastSegmentWasANumber == true && spaceSplitNum[i].length>0 && !double.parse(spaceSplitNum[i][0], (e) {return double.NAN;}).isNaN){
          //if last segment and the start of this segment are numbers, it's not clear how to handle it eg. '5 5 5/8'
          return double.NAN;
        }

        if(spaceSplitNum[i].length >= 1 && !double.parse(spaceSplitNum[i][spaceSplitNum[i].length-1], (e) {return double.NAN;}).isNaN){
          lastSegmentWasANumber = true;
        }else{
          lastSegmentWasANumber = false;
        }
        frac += spaceSplitNum[i];
      }
    }

    if(frac.indexOf('/') >= 0){
      //there is a fraction component
      List splitFrac = frac.split('/');
      if(splitFrac[0] == ""){
        //if there is no numerator, then we must use the value previously stored as is the case with '5 / 8'
        if(double.parse(splitFrac[1], (e) {return double.NAN;}).isNaN){
          //there are non numeric characters eg. '1 / asdf'
          return double.NAN;
        }else{
          rightPart = leftPart / double.parse(splitFrac[1], (e) {return double.NAN;});
          leftPart = 0.0;
        }
      }else{
        if(double.parse(splitFrac[0], (e) {return double.NAN;}).isNaN || double.parse(splitFrac[1], (e) {return double.NAN;}).isNaN){
          //there are non numeric characters eg. '5/asdf', 'asdf/8', 'asdf/ghjkl', '5 asdf/ghjkl'
          return double.NAN;
        }else{
          //divide numberator by the denominator
          rightPart = double.parse(splitFrac[0], (e) {return double.NAN;}) / double.parse(splitFrac[1], (e) {return double.NAN;}); 
          if(rightPart.isNaN){
            //we are likely dividing by 0 eg. '0/0', '1 0/0'
            return double.NAN;
          }
        }
      }
    }else if(frac.indexOf('.') >= 0){
      //there is a decimal component
      List splitDec = frac.split('.'); 
      if(splitDec[0].length != 0){ 
        if(double.parse(splitDec[0], (e) {return double.NAN;}).isNaN){
          //there are non numeric characters eg. 'asdf.625'
          return double.NAN;
        }else{
          //if the number has a integar part eg. not '.625' 
          leftPart = double.parse(splitDec[0], (e) {return double.NAN;});
        }
      }

      if(double.parse("0." + splitDec[1], (e) {return double.NAN;}).isNaN){
        //there are non numeric characters eg. '5.asdf'
        return double.NAN;
      }else{
        rightPart = double.parse("0." + splitDec[1], (e) {return double.NAN;});
      }
    }else{
      if(double.parse(frac, (e) {return double.NAN;}).isNaN){
        //there are non numeric characters eg. '', 'asdf', '-asdf', '--5', '-5-'
        return double.NAN;
      }else{
        leftPart = double.parse(frac, (e) {return double.NAN;});
      }
    }

    if(!leftPart.isNaN && leftPart.floor() != leftPart && rightPart!=0){
      //There is a decimal in the first segment and a second segment, it's not clear how to handle it eg. '5.625 5/8', '.625 5/8'
      return double.NAN;
    }

    if(leftPart<0){
      result = leftPart - rightPart;
    }else{
      result = leftPart + rightPart;
    }

    return result;
  }
}
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top