851 lines
33 KiB
Java
851 lines
33 KiB
Java
/* GENERATED SOURCE. DO NOT MODIFY. */
|
|
// © 2016 and later: Unicode, Inc. and others.
|
|
// License & terms of use: http://www.unicode.org/copyright.html
|
|
/*
|
|
*******************************************************************************
|
|
* Copyright (C) 1996-2015, International Business Machines Corporation and *
|
|
* others. All Rights Reserved. *
|
|
*******************************************************************************
|
|
*/
|
|
package android.icu.text;
|
|
|
|
import java.text.ParsePosition;
|
|
import java.util.ArrayList;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
import java.util.Objects;
|
|
|
|
import android.icu.impl.PatternProps;
|
|
|
|
/**
|
|
* A collection of rules used by a RuleBasedNumberFormat to format and
|
|
* parse numbers. It is the responsibility of a RuleSet to select an
|
|
* appropriate rule for formatting a particular number and dispatch
|
|
* control to it, and to arbitrate between different rules when parsing
|
|
* a number.
|
|
*/
|
|
|
|
final class NFRuleSet {
|
|
//-----------------------------------------------------------------------
|
|
// data members
|
|
//-----------------------------------------------------------------------
|
|
|
|
/**
|
|
* The rule set's name
|
|
*/
|
|
private final String name;
|
|
|
|
/**
|
|
* The rule set's regular rules
|
|
*/
|
|
private NFRule[] rules;
|
|
|
|
/**
|
|
* The rule set's non-numerical rules like negative, fractions, infinity and NaN
|
|
*/
|
|
final NFRule[] nonNumericalRules = new NFRule[6];
|
|
|
|
/**
|
|
* These are a pile of fraction rules in declared order. They may have alternate
|
|
* ways to represent fractions.
|
|
*/
|
|
LinkedList<NFRule> fractionRules;
|
|
|
|
/** -x */
|
|
static final int NEGATIVE_RULE_INDEX = 0;
|
|
/** x.x */
|
|
static final int IMPROPER_FRACTION_RULE_INDEX = 1;
|
|
/** 0.x */
|
|
static final int PROPER_FRACTION_RULE_INDEX = 2;
|
|
/** x.0 */
|
|
static final int DEFAULT_RULE_INDEX = 3;
|
|
/** Inf */
|
|
static final int INFINITY_RULE_INDEX = 4;
|
|
/** NaN */
|
|
static final int NAN_RULE_INDEX = 5;
|
|
|
|
/**
|
|
* The RuleBasedNumberFormat that owns this rule
|
|
*/
|
|
final RuleBasedNumberFormat owner;
|
|
|
|
/**
|
|
* True if the rule set is a fraction rule set. A fraction rule set
|
|
* is a rule set that is used to format the fractional part of a
|
|
* number. It is called from a >> substitution in another rule set's
|
|
* fraction rule, and is only called upon to format values between
|
|
* 0 and 1. A fraction rule set has different rule-selection
|
|
* behavior than a regular rule set.
|
|
*/
|
|
private boolean isFractionRuleSet = false;
|
|
|
|
/**
|
|
* True if the rule set is parseable.
|
|
*/
|
|
private final boolean isParseable;
|
|
|
|
/**
|
|
* Limit of recursion. It's about a 64 bit number formatted in base 2.
|
|
*/
|
|
private static final int RECURSION_LIMIT = 64;
|
|
|
|
//-----------------------------------------------------------------------
|
|
// construction
|
|
//-----------------------------------------------------------------------
|
|
|
|
/**
|
|
* Constructs a rule set.
|
|
* @param owner The formatter that owns this rule set
|
|
* @param descriptions An array of Strings representing rule set
|
|
* descriptions. On exit, this rule set's entry in the array will
|
|
* have been stripped of its rule set name and any trailing whitespace.
|
|
* @param index The index into "descriptions" of the description
|
|
* for the rule to be constructed
|
|
*/
|
|
public NFRuleSet(RuleBasedNumberFormat owner, String[] descriptions, int index) throws IllegalArgumentException {
|
|
this.owner = owner;
|
|
String description = descriptions[index];
|
|
|
|
if (description.length() == 0) {
|
|
throw new IllegalArgumentException("Empty rule set description");
|
|
}
|
|
|
|
// if the description begins with a rule set name (the rule set
|
|
// name can be omitted in formatter descriptions that consist
|
|
// of only one rule set), copy it out into our "name" member
|
|
// and delete it from the description
|
|
if (description.charAt(0) == '%') {
|
|
int pos = description.indexOf(':');
|
|
if (pos == -1) {
|
|
throw new IllegalArgumentException("Rule set name doesn't end in colon");
|
|
}
|
|
else {
|
|
String name = description.substring(0, pos);
|
|
this.isParseable = !name.endsWith("@noparse");
|
|
if (!this.isParseable) {
|
|
name = name.substring(0,name.length()-8); // Remove the @noparse from the name
|
|
}
|
|
this.name = name;
|
|
|
|
//noinspection StatementWithEmptyBody
|
|
while (pos < description.length() && PatternProps.isWhiteSpace(description.charAt(++pos))) {
|
|
}
|
|
description = description.substring(pos);
|
|
descriptions[index] = description;
|
|
}
|
|
}
|
|
else {
|
|
// if the description doesn't begin with a rule set name, its
|
|
// name is "%default"
|
|
name = "%default";
|
|
isParseable = true;
|
|
}
|
|
|
|
if (description.length() == 0) {
|
|
throw new IllegalArgumentException("Empty rule set description");
|
|
}
|
|
|
|
// all of the other members of NFRuleSet are initialized
|
|
// by parseRules()
|
|
}
|
|
|
|
/**
|
|
* Construct the subordinate data structures used by this object.
|
|
* This function is called by the RuleBasedNumberFormat constructor
|
|
* after all the rule sets have been created to actually parse
|
|
* the description and build rules from it. Since any rule set
|
|
* can refer to any other rule set, we have to have created all of
|
|
* them before we can create anything else.
|
|
* @param description The textual description of this rule set
|
|
*/
|
|
public void parseRules(String description) {
|
|
// (the number of elements in the description list isn't necessarily
|
|
// the number of rules-- some descriptions may expend into two rules)
|
|
List<NFRule> tempRules = new ArrayList<>();
|
|
|
|
// we keep track of the rule before the one we're currently working
|
|
// on solely to support >>> substitutions
|
|
NFRule predecessor = null;
|
|
|
|
// Iterate through the rules. The rules
|
|
// are separated by semicolons (there's no escape facility: ALL
|
|
// semicolons are rule delimiters)
|
|
int oldP = 0;
|
|
int descriptionLen = description.length();
|
|
int p;
|
|
do {
|
|
p = description.indexOf(';', oldP);
|
|
if (p < 0) {
|
|
p = descriptionLen;
|
|
}
|
|
|
|
// makeRules (a factory method on NFRule) will return either
|
|
// a single rule or an array of rules. Either way, add them
|
|
// to our rule vector
|
|
NFRule.makeRules(description.substring(oldP, p),
|
|
this, predecessor, owner, tempRules);
|
|
if (!tempRules.isEmpty()) {
|
|
predecessor = tempRules.get(tempRules.size() - 1);
|
|
}
|
|
|
|
oldP = p + 1;
|
|
}
|
|
while (oldP < descriptionLen);
|
|
|
|
// for rules that didn't specify a base value, their base values
|
|
// were initialized to 0. Make another pass through the list and
|
|
// set all those rules' base values. We also remove any special
|
|
// rules from the list and put them into their own member variables
|
|
long defaultBaseValue = 0;
|
|
|
|
for (NFRule rule : tempRules) {
|
|
long baseValue = rule.getBaseValue();
|
|
if (baseValue == 0) {
|
|
// if the rule's base value is 0, fill in a default
|
|
// base value (this will be 1 plus the preceding
|
|
// rule's base value for regular rule sets, and the
|
|
// same as the preceding rule's base value in fraction
|
|
// rule sets)
|
|
rule.setBaseValue(defaultBaseValue);
|
|
}
|
|
else {
|
|
// if it's a regular rule that already knows its base value,
|
|
// check to make sure the rules are in order, and update
|
|
// the default base value for the next rule
|
|
if (baseValue < defaultBaseValue) {
|
|
throw new IllegalArgumentException("Rules are not in order, base: " +
|
|
baseValue + " < " + defaultBaseValue);
|
|
}
|
|
defaultBaseValue = baseValue;
|
|
}
|
|
if (!isFractionRuleSet) {
|
|
++defaultBaseValue;
|
|
}
|
|
}
|
|
|
|
// finally, we can copy the rules from the vector into a
|
|
// fixed-length array
|
|
rules = new NFRule[tempRules.size()];
|
|
tempRules.toArray(rules);
|
|
}
|
|
|
|
/**
|
|
* Set one of the non-numerical rules.
|
|
* @param rule The rule to set.
|
|
*/
|
|
void setNonNumericalRule(NFRule rule) {
|
|
long baseValue = rule.getBaseValue();
|
|
if (baseValue == NFRule.NEGATIVE_NUMBER_RULE) {
|
|
nonNumericalRules[NFRuleSet.NEGATIVE_RULE_INDEX] = rule;
|
|
}
|
|
else if (baseValue == NFRule.IMPROPER_FRACTION_RULE) {
|
|
setBestFractionRule(NFRuleSet.IMPROPER_FRACTION_RULE_INDEX, rule, true);
|
|
}
|
|
else if (baseValue == NFRule.PROPER_FRACTION_RULE) {
|
|
setBestFractionRule(NFRuleSet.PROPER_FRACTION_RULE_INDEX, rule, true);
|
|
}
|
|
else if (baseValue == NFRule.DEFAULT_RULE) {
|
|
setBestFractionRule(NFRuleSet.DEFAULT_RULE_INDEX, rule, true);
|
|
}
|
|
else if (baseValue == NFRule.INFINITY_RULE) {
|
|
nonNumericalRules[NFRuleSet.INFINITY_RULE_INDEX] = rule;
|
|
}
|
|
else if (baseValue == NFRule.NAN_RULE) {
|
|
nonNumericalRules[NFRuleSet.NAN_RULE_INDEX] = rule;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determine the best fraction rule to use. Rules matching the decimal point from
|
|
* DecimalFormatSymbols become the main set of rules to use.
|
|
* @param originalIndex The index into nonNumericalRules
|
|
* @param newRule The new rule to consider
|
|
* @param rememberRule Should the new rule be added to fractionRules.
|
|
*/
|
|
private void setBestFractionRule(int originalIndex, NFRule newRule, boolean rememberRule) {
|
|
if (rememberRule) {
|
|
if (fractionRules == null) {
|
|
fractionRules = new LinkedList<>();
|
|
}
|
|
fractionRules.add(newRule);
|
|
}
|
|
NFRule bestResult = nonNumericalRules[originalIndex];
|
|
if (bestResult == null) {
|
|
nonNumericalRules[originalIndex] = newRule;
|
|
}
|
|
else {
|
|
// We have more than one. Which one is better?
|
|
DecimalFormatSymbols decimalFormatSymbols = owner.getDecimalFormatSymbols();
|
|
if (decimalFormatSymbols.getDecimalSeparator() == newRule.getDecimalPoint()) {
|
|
nonNumericalRules[originalIndex] = newRule;
|
|
}
|
|
// else leave it alone
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Flags this rule set as a fraction rule set. This function is
|
|
* called during the construction process once we know this rule
|
|
* set is a fraction rule set. We don't know a rule set is a
|
|
* fraction rule set until we see it used somewhere. This function
|
|
* is not ad must not be called at any time other than during
|
|
* construction of a RuleBasedNumberFormat.
|
|
*/
|
|
public void makeIntoFractionRuleSet() {
|
|
isFractionRuleSet = true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// boilerplate
|
|
//-----------------------------------------------------------------------
|
|
|
|
/**
|
|
* Compares two rule sets for equality.
|
|
* @param that The other rule set
|
|
* @return true if the two rule sets are functionally equivalent.
|
|
*/
|
|
@Override
|
|
public boolean equals(Object that) {
|
|
// if different classes, they're not equal
|
|
if (!(that instanceof NFRuleSet)) {
|
|
return false;
|
|
} else {
|
|
// otherwise, compare the members one by one...
|
|
NFRuleSet that2 = (NFRuleSet)that;
|
|
|
|
if (!name.equals(that2.name)
|
|
|| rules.length != that2.rules.length
|
|
|| isFractionRuleSet != that2.isFractionRuleSet)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// ...then compare the non-numerical rule lists...
|
|
for (int i = 0; i < nonNumericalRules.length; i++) {
|
|
if (!Objects.equals(nonNumericalRules[i], that2.nonNumericalRules[i])) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// ...then compare the rule lists...
|
|
for (int i = 0; i < rules.length; i++) {
|
|
if (!rules[i].equals(that2.rules[i])) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// ...and if we make it here, they're equal
|
|
return true;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
assert false : "hashCode not designed";
|
|
return 42;
|
|
}
|
|
|
|
|
|
/**
|
|
* Builds a textual representation of a rule set.
|
|
* @return A textual representation of a rule set. This won't
|
|
* necessarily be the same description that the rule set was
|
|
* constructed with, but it will produce the same results.
|
|
*/
|
|
@Override
|
|
public String toString() {
|
|
StringBuilder result = new StringBuilder();
|
|
|
|
// the rule set name goes first...
|
|
result.append(name).append(":\n");
|
|
|
|
// followed by the regular rules...
|
|
for (NFRule rule : rules) {
|
|
result.append(rule.toString()).append("\n");
|
|
}
|
|
|
|
// followed by the special rules (if they exist)
|
|
for (NFRule rule : nonNumericalRules) {
|
|
if (rule != null) {
|
|
if (rule.getBaseValue() == NFRule.IMPROPER_FRACTION_RULE
|
|
|| rule.getBaseValue() == NFRule.PROPER_FRACTION_RULE
|
|
|| rule.getBaseValue() == NFRule.DEFAULT_RULE)
|
|
{
|
|
for (NFRule fractionRule : fractionRules) {
|
|
if (fractionRule.getBaseValue() == rule.getBaseValue()) {
|
|
result.append(fractionRule.toString()).append("\n");
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
result.append(rule.toString()).append("\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
return result.toString();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// simple accessors
|
|
//-----------------------------------------------------------------------
|
|
|
|
/**
|
|
* Says whether this rule set is a fraction rule set.
|
|
* @return true if this rule is a fraction rule set; false if it isn't
|
|
*/
|
|
public boolean isFractionSet() {
|
|
return isFractionRuleSet;
|
|
}
|
|
|
|
/**
|
|
* Returns the rule set's name
|
|
* @return The rule set's name
|
|
*/
|
|
public String getName() {
|
|
return name;
|
|
}
|
|
|
|
/**
|
|
* Return true if the rule set is public.
|
|
* @return true if the rule set is public
|
|
*/
|
|
public boolean isPublic() {
|
|
return !name.startsWith("%%");
|
|
}
|
|
|
|
/**
|
|
* Return true if the rule set can be used for parsing.
|
|
* @return true if the rule set can be used for parsing.
|
|
*/
|
|
public boolean isParseable() {
|
|
return isParseable;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// formatting
|
|
//-----------------------------------------------------------------------
|
|
|
|
/**
|
|
* Formats a long. Selects an appropriate rule and dispatches
|
|
* control to it.
|
|
* @param number The number being formatted
|
|
* @param toInsertInto The string where the result is to be placed
|
|
* @param pos The position in toInsertInto where the result of
|
|
* this operation is to be inserted
|
|
*/
|
|
public void format(long number, StringBuilder toInsertInto, int pos, int recursionCount) {
|
|
if (recursionCount >= RECURSION_LIMIT) {
|
|
throw new IllegalStateException("Recursion limit exceeded when applying ruleSet " + name);
|
|
}
|
|
NFRule applicableRule = findNormalRule(number);
|
|
applicableRule.doFormat(number, toInsertInto, pos, ++recursionCount);
|
|
}
|
|
|
|
/**
|
|
* Formats a double. Selects an appropriate rule and dispatches
|
|
* control to it.
|
|
* @param number The number being formatted
|
|
* @param toInsertInto The string where the result is to be placed
|
|
* @param pos The position in toInsertInto where the result of
|
|
* this operation is to be inserted
|
|
*/
|
|
public void format(double number, StringBuilder toInsertInto, int pos, int recursionCount) {
|
|
if (recursionCount >= RECURSION_LIMIT) {
|
|
throw new IllegalStateException("Recursion limit exceeded when applying ruleSet " + name);
|
|
}
|
|
NFRule applicableRule = findRule(number);
|
|
applicableRule.doFormat(number, toInsertInto, pos, ++recursionCount);
|
|
}
|
|
|
|
/**
|
|
* Selects an appropriate rule for formatting the number.
|
|
* @param number The number being formatted.
|
|
* @return The rule that should be used to format it
|
|
*/
|
|
NFRule findRule(double number) {
|
|
// if this is a fraction rule set, use findFractionRuleSetRule()
|
|
if (isFractionRuleSet) {
|
|
return findFractionRuleSetRule(number);
|
|
}
|
|
|
|
if (Double.isNaN(number)) {
|
|
NFRule rule = nonNumericalRules[NAN_RULE_INDEX];
|
|
if (rule == null) {
|
|
rule = owner.getDefaultNaNRule();
|
|
}
|
|
return rule;
|
|
}
|
|
|
|
// if the number is negative, return the negative number rule
|
|
// (if there isn't a negative-number rule, we pretend it's a
|
|
// positive number)
|
|
if (number < 0) {
|
|
if (nonNumericalRules[NEGATIVE_RULE_INDEX] != null) {
|
|
return nonNumericalRules[NEGATIVE_RULE_INDEX];
|
|
} else {
|
|
number = -number;
|
|
}
|
|
}
|
|
|
|
if (Double.isInfinite(number)) {
|
|
NFRule rule = nonNumericalRules[INFINITY_RULE_INDEX];
|
|
if (rule == null) {
|
|
rule = owner.getDefaultInfinityRule();
|
|
}
|
|
return rule;
|
|
}
|
|
|
|
// if the number isn't an integer, we use one f the fraction rules...
|
|
if (number != Math.floor(number)) {
|
|
if (number < 1 && nonNumericalRules[PROPER_FRACTION_RULE_INDEX] != null) {
|
|
// if the number is between 0 and 1, return the proper
|
|
// fraction rule
|
|
return nonNumericalRules[PROPER_FRACTION_RULE_INDEX];
|
|
}
|
|
else if (nonNumericalRules[IMPROPER_FRACTION_RULE_INDEX] != null) {
|
|
// otherwise, return the improper fraction rule
|
|
return nonNumericalRules[IMPROPER_FRACTION_RULE_INDEX];
|
|
}
|
|
}
|
|
|
|
// if there's a default rule, use it to format the number
|
|
if (nonNumericalRules[DEFAULT_RULE_INDEX] != null) {
|
|
return nonNumericalRules[DEFAULT_RULE_INDEX];
|
|
}
|
|
else {
|
|
// and if we haven't yet returned a rule, use findNormalRule()
|
|
// to find the applicable rule
|
|
return findNormalRule(Math.round(number));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* If the value passed to findRule() is a positive integer, findRule()
|
|
* uses this function to select the appropriate rule. The result will
|
|
* generally be the rule with the highest base value less than or equal
|
|
* to the number. There is one exception to this: If that rule has
|
|
* two substitutions and a base value that is not an even multiple of
|
|
* its divisor, and the number itself IS an even multiple of the rule's
|
|
* divisor, then the result will be the rule that preceded the original
|
|
* result in the rule list. (This behavior is known as the "rollback
|
|
* rule", and is used to handle optional text: a rule with optional
|
|
* text is represented internally as two rules, and the rollback rule
|
|
* selects appropriate between them. This avoids things like "two
|
|
* hundred zero".)
|
|
* @param number The number being formatted
|
|
* @return The rule to use to format this number
|
|
*/
|
|
private NFRule findNormalRule(long number) {
|
|
// if this is a fraction rule set, use findFractionRuleSetRule()
|
|
// to find the rule (we should only go into this clause if the
|
|
// value is 0)
|
|
if (isFractionRuleSet) {
|
|
return findFractionRuleSetRule(number);
|
|
}
|
|
|
|
// if the number is negative, return the negative-number rule
|
|
// (if there isn't one, pretend the number is positive)
|
|
if (number < 0) {
|
|
if (nonNumericalRules[NEGATIVE_RULE_INDEX] != null) {
|
|
return nonNumericalRules[NEGATIVE_RULE_INDEX];
|
|
} else {
|
|
number = -number;
|
|
}
|
|
}
|
|
|
|
// we have to repeat the preceding two checks, even though we
|
|
// do them in findRule(), because the version of format() that
|
|
// takes a long bypasses findRule() and goes straight to this
|
|
// function. This function does skip the fraction rules since
|
|
// we know the value is an integer (it also skips the default
|
|
// rule, since it's considered a fraction rule. Skipping the
|
|
// default rule in this function is also how we avoid infinite
|
|
// recursion)
|
|
|
|
// binary-search the rule list for the applicable rule
|
|
// (a rule is used for all values from its base value to
|
|
// the next rule's base value)
|
|
int lo = 0;
|
|
int hi = rules.length;
|
|
if (hi > 0) {
|
|
while (lo < hi) {
|
|
int mid = (lo + hi) >>> 1;
|
|
long ruleBaseValue = rules[mid].getBaseValue();
|
|
if (ruleBaseValue == number) {
|
|
return rules[mid];
|
|
}
|
|
else if (ruleBaseValue > number) {
|
|
hi = mid;
|
|
}
|
|
else {
|
|
lo = mid + 1;
|
|
}
|
|
}
|
|
if (hi == 0) { // bad rule set
|
|
throw new IllegalStateException("The rule set " + name + " cannot format the value " + number);
|
|
}
|
|
NFRule result = rules[hi - 1];
|
|
|
|
// use shouldRollBack() to see whether we need to invoke the
|
|
// rollback rule (see shouldRollBack()'s documentation for
|
|
// an explanation of the rollback rule). If we do, roll back
|
|
// one rule and return that one instead of the one we'd normally
|
|
// return
|
|
if (result.shouldRollBack(number)) {
|
|
if (hi == 1) { // bad rule set
|
|
throw new IllegalStateException("The rule set " + name + " cannot roll back from the rule '" +
|
|
result + "'");
|
|
}
|
|
result = rules[hi - 2];
|
|
}
|
|
return result;
|
|
}
|
|
// else use the default rule
|
|
return nonNumericalRules[DEFAULT_RULE_INDEX];
|
|
}
|
|
|
|
/**
|
|
* If this rule is a fraction rule set, this function is used by
|
|
* findRule() to select the most appropriate rule for formatting
|
|
* the number. Basically, the base value of each rule in the rule
|
|
* set is treated as the denominator of a fraction. Whichever
|
|
* denominator can produce the fraction closest in value to the
|
|
* number passed in is the result. If there's a tie, the earlier
|
|
* one in the list wins. (If there are two rules in a row with the
|
|
* same base value, the first one is used when the numerator of the
|
|
* fraction would be 1, and the second rule is used the rest of the
|
|
* time.
|
|
* @param number The number being formatted (which will always be
|
|
* a number between 0 and 1)
|
|
* @return The rule to use to format this number
|
|
*/
|
|
private NFRule findFractionRuleSetRule(double number) {
|
|
// the obvious way to do this (multiply the value being formatted
|
|
// by each rule's base value until you get an integral result)
|
|
// doesn't work because of rounding error. This method is more
|
|
// accurate
|
|
|
|
// find the least common multiple of the rules' base values
|
|
// and multiply this by the number being formatted. This is
|
|
// all the precision we need, and we can do all of the rest
|
|
// of the math using integer arithmetic
|
|
long leastCommonMultiple = rules[0].getBaseValue();
|
|
for (int i = 1; i < rules.length; i++) {
|
|
leastCommonMultiple = lcm(leastCommonMultiple, rules[i].getBaseValue());
|
|
}
|
|
long numerator = Math.round(number * leastCommonMultiple);
|
|
|
|
// for each rule, do the following...
|
|
long tempDifference;
|
|
long difference = Long.MAX_VALUE;
|
|
int winner = 0;
|
|
for (int i = 0; i < rules.length; i++) {
|
|
// "numerator" is the numerator of the fraction is the
|
|
// denominator is the LCD. The numerator if the the rule's
|
|
// base value is the denominator is "numerator" times the
|
|
// base value divided by the LCD. Here we check to see if
|
|
// that's an integer, and if not, how close it is to being
|
|
// an integer.
|
|
tempDifference = numerator * rules[i].getBaseValue() % leastCommonMultiple;
|
|
|
|
// normalize the result of the above calculation: we want
|
|
// the numerator's distance from the CLOSEST multiple
|
|
// of the LCD
|
|
if (leastCommonMultiple - tempDifference < tempDifference) {
|
|
tempDifference = leastCommonMultiple - tempDifference;
|
|
}
|
|
|
|
// if this is as close as we've come, keep track of how close
|
|
// that is, and the line number of the rule that did it. If
|
|
// we've scored a direct hit, we don't have to look at any more
|
|
// rules
|
|
if (tempDifference < difference) {
|
|
difference = tempDifference;
|
|
winner = i;
|
|
if (difference == 0) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if we have two successive rules that both have the winning base
|
|
// value, then the first one (the one we found above) is used if
|
|
// the numerator of the fraction is 1 and the second one is used if
|
|
// the numerator of the fraction is anything else (this lets us
|
|
// do things like "one third"/"two thirds" without having to define
|
|
// a whole bunch of extra rule sets)
|
|
if (winner + 1 < rules.length
|
|
&& rules[winner + 1].getBaseValue() == rules[winner].getBaseValue()) {
|
|
if (Math.round(number * rules[winner].getBaseValue()) < 1
|
|
|| Math.round(number * rules[winner].getBaseValue()) >= 2) {
|
|
++winner;
|
|
}
|
|
}
|
|
|
|
// finally, return the winning rule
|
|
return rules[winner];
|
|
}
|
|
|
|
/**
|
|
* Calculates the least common multiple of x and y.
|
|
*/
|
|
private static long lcm(long x, long y) {
|
|
// binary gcd algorithm from Knuth, "The Art of Computer Programming,"
|
|
// vol. 2, 1st ed., pp. 298-299
|
|
long x1 = x;
|
|
long y1 = y;
|
|
|
|
int p2 = 0;
|
|
while ((x1 & 1) == 0 && (y1 & 1) == 0) {
|
|
++p2;
|
|
x1 >>= 1;
|
|
y1 >>= 1;
|
|
}
|
|
|
|
long t;
|
|
if ((x1 & 1) == 1) {
|
|
t = -y1;
|
|
} else {
|
|
t = x1;
|
|
}
|
|
|
|
while (t != 0) {
|
|
while ((t & 1) == 0) {
|
|
t >>= 1;
|
|
}
|
|
if (t > 0) {
|
|
x1 = t;
|
|
} else {
|
|
y1 = -t;
|
|
}
|
|
t = x1 - y1;
|
|
}
|
|
long gcd = x1 << p2;
|
|
|
|
// x * y == gcd(x, y) * lcm(x, y)
|
|
return x / gcd * y;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// parsing
|
|
//-----------------------------------------------------------------------
|
|
|
|
/**
|
|
* Parses a string. Matches the string to be parsed against each
|
|
* of its rules (with a base value less than upperBound) and returns
|
|
* the value produced by the rule that matched the most characters
|
|
* in the source string.
|
|
* @param text The string to parse
|
|
* @param parsePosition The initial position is ignored and assumed
|
|
* to be 0. On exit, this object has been updated to point to the
|
|
* first character position this rule set didn't consume.
|
|
* @param upperBound Limits the rules that can be allowed to match.
|
|
* Only rules whose base values are strictly less than upperBound
|
|
* are considered.
|
|
* @return The numerical result of parsing this string. This will
|
|
* be the matching rule's base value, composed appropriately with
|
|
* the results of matching any of its substitutions. The object
|
|
* will be an instance of Long if it's an integral value; otherwise,
|
|
* it will be an instance of Double. This function always returns
|
|
* a valid object: If nothing matched the input string at all,
|
|
* this function returns Long.valueOf(0), and the parse position is
|
|
* left unchanged.
|
|
*/
|
|
public Number parse(String text, ParsePosition parsePosition, double upperBound, int nonNumericalExecutedRuleMask) {
|
|
// try matching each rule in the rule set against the text being
|
|
// parsed. Whichever one matches the most characters is the one
|
|
// that determines the value we return.
|
|
|
|
ParsePosition highWaterMark = new ParsePosition(0);
|
|
Number result = NFRule.ZERO;
|
|
Number tempResult;
|
|
|
|
// dump out if there's no text to parse
|
|
if (text.length() == 0) {
|
|
return result;
|
|
}
|
|
|
|
// Try each of the negative rules, fraction rules, infinity rules and NaN rules
|
|
for (int nonNumericalRuleIdx = 0; nonNumericalRuleIdx < nonNumericalRules.length; nonNumericalRuleIdx++) {
|
|
NFRule nonNumericalRule = nonNumericalRules[nonNumericalRuleIdx];
|
|
if (nonNumericalRule != null && ((nonNumericalExecutedRuleMask >> nonNumericalRuleIdx) & 1) == 0) {
|
|
// Mark this rule as being executed so that we don't try to execute it again.
|
|
nonNumericalExecutedRuleMask |= 1 << nonNumericalRuleIdx;
|
|
|
|
tempResult = nonNumericalRule.doParse(text, parsePosition, false, upperBound, nonNumericalExecutedRuleMask);
|
|
if (parsePosition.getIndex() > highWaterMark.getIndex()) {
|
|
result = tempResult;
|
|
highWaterMark.setIndex(parsePosition.getIndex());
|
|
}
|
|
// commented out because the error-index API on ParsePosition isn't there in 1.1.x
|
|
// if (parsePosition.getErrorIndex() > highWaterMark.getErrorIndex()) {
|
|
// highWaterMark.setErrorIndex(parsePosition.getErrorIndex());
|
|
// }
|
|
parsePosition.setIndex(0);
|
|
}
|
|
}
|
|
|
|
// finally, go through the regular rules one at a time. We start
|
|
// at the end of the list because we want to try matching the most
|
|
// significant rule first (this helps ensure that we parse
|
|
// "five thousand three hundred six" as
|
|
// "(five thousand) (three hundred) (six)" rather than
|
|
// "((five thousand three) hundred) (six)"). Skip rules whose
|
|
// base values are higher than the upper bound (again, this helps
|
|
// limit ambiguity by making sure the rules that match a rule's
|
|
// are less significant than the rule containing the substitutions)/
|
|
for (int i = rules.length - 1; i >= 0 && highWaterMark.getIndex() < text.length(); i--) {
|
|
if (!isFractionRuleSet && rules[i].getBaseValue() >= upperBound) {
|
|
continue;
|
|
}
|
|
|
|
tempResult = rules[i].doParse(text, parsePosition, isFractionRuleSet, upperBound, nonNumericalExecutedRuleMask);
|
|
if (parsePosition.getIndex() > highWaterMark.getIndex()) {
|
|
result = tempResult;
|
|
highWaterMark.setIndex(parsePosition.getIndex());
|
|
}
|
|
// commented out because the error-index API on ParsePosition isn't there in 1.1.x
|
|
// if (parsePosition.getErrorIndex() > highWaterMark.getErrorIndex()) {
|
|
// highWaterMark.setErrorIndex(parsePosition.getErrorIndex());
|
|
// }
|
|
parsePosition.setIndex(0);
|
|
}
|
|
|
|
// finally, update the parse position we were passed to point to the
|
|
// first character we didn't use, and return the result that
|
|
// corresponds to that string of characters
|
|
parsePosition.setIndex(highWaterMark.getIndex());
|
|
// commented out because the error-index API on ParsePosition isn't there in 1.1.x
|
|
// if (parsePosition.getIndex() == 0) {
|
|
// parsePosition.setErrorIndex(highWaterMark.getErrorIndex());
|
|
// }
|
|
|
|
return result;
|
|
}
|
|
|
|
public void setDecimalFormatSymbols(DecimalFormatSymbols newSymbols) {
|
|
for (NFRule rule : rules) {
|
|
rule.setDecimalFormatSymbols(newSymbols);
|
|
}
|
|
// Switch the fraction rules to mirror the DecimalFormatSymbols.
|
|
if (fractionRules != null) {
|
|
for (int nonNumericalIdx = IMPROPER_FRACTION_RULE_INDEX; nonNumericalIdx <= DEFAULT_RULE_INDEX; nonNumericalIdx++) {
|
|
if (nonNumericalRules[nonNumericalIdx] != null) {
|
|
for (NFRule rule : fractionRules) {
|
|
if (nonNumericalRules[nonNumericalIdx].getBaseValue() == rule.getBaseValue()) {
|
|
setBestFractionRule(nonNumericalIdx, rule, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (NFRule rule : nonNumericalRules) {
|
|
if (rule != null) {
|
|
rule.setDecimalFormatSymbols(newSymbols);
|
|
}
|
|
}
|
|
}
|
|
}
|