class AbstractTextToNumberFilter extends RuleFilter {

  protected static Map<String, Float> numbers = new HashMap<>();
  protected static Map<String, Float> multipliers = new HashMap<>();

  @Override
  public RuleMatch acceptRuleMatch(RuleMatch match, Map<String, String> arguments, int patternTokenPos,
                                   AnalyzedTokenReadings[] patternTokens, List<Integer> tokenPositions) throws IOException {

    int posWord = 0;
    float total = 0;
    float current = 0;
    float totalDecimal = 0;
    float currentDecimal = 0;
    int addedZeros = 0;
    boolean percentage = false;
    boolean decimal = false;
    while (posWord < patternTokens.length && patternTokens[posWord].getEndPos() <= match.getToPos()) {
      // inside <marker>
      if (patternTokens[posWord].getStartPos() >= match.getFromPos()
          && patternTokens[posWord].getEndPos() <= match.getToPos()) {
        String form = patternTokens[posWord].getToken().toLowerCase();
        if (posWord > 0 && isPercentage(patternTokens, posWord)) {
          percentage = true;
          break;
        }
        if (isComma(form)) {
          decimal = true;
          posWord++;
          continue;
        }
        List<String> forms = tokenize(form);
        for (String subForm : forms) {
          if (!decimal) {
            if (numbers.containsKey(subForm)) {
              current += numbers.get(subForm);
            } else if (multipliers.containsKey(subForm)) {
              if (current == 0) {// mil
                current = 1;
              }
              total += current * multipliers.get(subForm);
              current = 0;
            }
          } else {
            if (numbers.containsKey(subForm)) {
              int zerosToAdd = format((numbers.get(subForm)), false).length();
              currentDecimal += numbers.get(subForm) / Math.pow(10, addedZeros + zerosToAdd);
              addedZeros++;
            } /* else: multipliers after the decimal comma are not expected */
          }
        }

      }
      posWord++;
    }
    total += current;
    totalDecimal += currentDecimal;
    total = total + totalDecimal /* / (Float.toString(totalDecimal).length() + addedZeros) */;
    RuleMatch ruleMatch = match;
    String sugg = format(total, percentage);

    ruleMatch.addSuggestedReplacement(sugg);
    return ruleMatch;
  }

  private String format(float d, boolean percentage) {<FILL_FUNCTION_BODY>}
  
  abstract protected boolean isComma(String s);
  
  abstract protected boolean isPercentage(AnalyzedTokenReadings[] patternTokens, int i);
  
  protected String formatResult(String s) {
    return s;
  };
  
  protected List<String> tokenize(String s) {
    return Collections.singletonList(s);
  };
  

}

    String result;
    if (d == (long) d) {
      result = String.format("%d", (long) d);
    } else {
      result = String.format("%s", d);
    }
    if (percentage) {
      result = result + "\u202F%"; // narrow non-breaking space + percentage
    }
    return formatResult(result);
  
<methods>public non-sealed void <init>() ,public abstract org.languagetool.rules.RuleMatch acceptRuleMatch(org.languagetool.rules.RuleMatch, Map<java.lang.String,java.lang.String>, int, org.languagetool.AnalyzedTokenReadings[], List<java.lang.Integer>) throws java.io.IOException,public boolean matches(Map<java.lang.String,java.lang.String>, org.languagetool.AnalyzedTokenReadings[], int, List<java.lang.Integer>) throws java.io.IOException<variables>