// Copyright (C) 2005 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.caja.parser.js;
import com.google.caja.lexer.FilePosition;
import com.google.javascript.jscomp.jsonml.JsonML;
import com.google.javascript.jscomp.jsonml.TagAttr;
import com.google.javascript.jscomp.jsonml.TagType;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
/**
* A literal numeric value. This class is abstract so that we can distinguish
* literals based on the format they were parsed from.
* If in doubt about which implementation to use, use {@link RealLiteral}.
*
* @author mikesamuel@gmail.com
*/
public abstract class NumberLiteral extends Literal {
private static final long serialVersionUID = -5974378121634749014L;
protected NumberLiteral(FilePosition pos) { super(pos); }
@Override
public abstract Number getValue();
/** Same as {@code getValue().doubleValue()} */
public abstract double doubleValue();
@Override
public boolean getValueInBooleanContext() {
double n = doubleValue();
return !(Double.isNaN(n) || 0D == n);
}
/**
* Convert a number to a string using javascript rules.
* <blockquote>
* 9.8.1 ToString Applied to the Number Type
* <br><br>
* The operator ToString converts a number m to string format as follows:
* <ol>
* <li>If m is NaN, return the string "NaN".
* <li>If m is +0 or -0, return the string "0".
* <li>If m is less than zero, return the string concatenation of the string
* "-" and ToString(-m).
* <li>If m is infinity, return the string "Infinity".
* <li>Otherwise, let n, k, and s be integers such that k >= 1,
* 10**k-1 <= s < 10**k, the number value for s * 10**(n-k) is m,
* and k is as small as possible. Note that k is the number of
* digits in the decimal representation of s, that s is not
* divisible by 10, and that the least significant digit of s is not
* necessarily uniquely determined by these criteria.
* <li>If k <= n <= 21, return the string consisting of the k digits
* of the decimal representation of s (in order, with no leading
* zeroes), followed by n-k occurrences of the character '0'.
* <li>If 0 < n <= 21, return the string consisting of the
* most significant n digits of the decimal representation of s,
* followed by a decimal point '.', followed by the
* remaining k-n digits of the decimal representation of s.
* <li>If -6 < n <= 0, return the string consisting of the
* character '0', followed by a decimal point '.', followed by -n
* occurrences of the character '0', followed by the k digits of
* the decimal representation of s.
* <li>Otherwise, if k = 1, return the string consisting of the
* single digit of s, followed by lowercase character 'e', followed
* by a plus sign '+' or minus sign '-' according to whether n-1 is
* positive or negative, followed by the decimal representation of
* the integer abs(n-1) (with no leading zeros).
* <li>Return the string consisting of the most significant digit
* of the decimal representation of s, followed by a decimal point
* '.', followed by the remaining k-1 digits of the decimal
* representation of s, followed by the lowercase character 'e',
* followed by a plus sign '+' or minus sign '-' according to
* whether n-1 is positive or negative, followed by the decimal
* representation of the integer abs(n-1) (with no leading zeros).
* </ol>
*/
public static String numberToString(double m) {
if (Double.isNaN(m)) { return "NaN"; }
if (m == 0) { return "0"; } // incl. -0
if (Double.isInfinite(m)) {
return m >= 0 ? "Infinity" : "-Infinity";
}
return numberToString(new BigDecimal(m, TWENTY_ONE_DIGITS_ROUNDED_TO_ZERO));
}
private static final MathContext TWENTY_ONE_DIGITS_ROUNDED_TO_ZERO
= new MathContext(21, RoundingMode.DOWN);
public static String numberToString(BigDecimal m) {
int cmp = BigDecimal.ZERO.compareTo(m);
if (cmp == 0) {
return "0";
}
if (cmp > 0) {
return "-" + numberToString(m.abs());
}
// Round to 21 digits worth and adjust n-k
if (m.precision() > 21) {
m = new BigDecimal(m.unscaledValue(), m.scale(),
TWENTY_ONE_DIGITS_ROUNDED_TO_ZERO);
assert m.precision() <= 21;
}
m = m.stripTrailingZeros();
// Choose n, k, and s such that k >= 1, 10**k-1 <= s < 10**k,
// the number value for s * 10**(n-k) is m, and k is as small as possible.
int n, k;
BigDecimal s;
s = new BigDecimal(m.unscaledValue(), 0);
// 10**k is the greatest power of 10 <= s, i.e. k = floor(log10(s))
k = s.precision() - s.scale();
// (1) s = m * 10**m.scale() by def of BigDecimal.unscaledValue()
// (2) m = s * 10**(n-k) by def of s,n,k
// (3) m = s * 10**-m.scale() from 1
// (4) n - k = -m.scale() by 2, 3
// (5) n = k - m.scale()
n = k - m.scale();
String intRep = s.unscaledValue().toString(); // independent of Locale
if (k <= n && n <= 21) {
// 6. If k <= n <= 21, return the string consisting of the k digits
// of the decimal representation of s (in order, with no leading
// zeroes), followed by n-k occurrences of the character '0'.
StringBuilder sb = new StringBuilder(n);
sb.append(intRep.substring(0, k));
while (sb.length() < n) { sb.append('0'); }
return sb.toString();
} else if (0 < n && n <= 21) {
// 7. If 0 < n <= 21, return the string consisting of the
// most significant n digits of the decimal representation of s,
// followed by a decimal point '.', followed by the
// remaining k-n digits of the decimal representation of s.
return intRep.substring(0, n) + "."
+ intRep.substring(n, Math.min(intRep.length(), 21));
} else if (-6 < n && n <= 0) {
// 8. If -6 < n <= 0, return the string consisting of the
// character '0', followed by a decimal point '.', followed by -n
// occurrences of the character '0', followed by the k digits of
// the decimal representation of s.
StringBuilder sb = new StringBuilder(2 + -n + k);
sb.append("0.");
for (int i = -n; --i >= 0;) { sb.append('0'); }
sb.append(intRep, 0, k);
return sb.toString();
} else if (k == 1) {
// 9. Otherwise, if k = 1, return the string consisting of the
// single digit of s, followed by lowercase character 'e', followed
// by a plus sign '+' or minus sign '-' according to whether n-1 is
// positive or negative, followed by the decimal representation of
// the integer abs(n-1) (with no leading zeros).
return intRep + ((n - 1) < 0 ? "e-" : "e+") + Math.abs(n - 1);
} else {
// 10. Return the string consisting of the most significant digit
// of the decimal representation of s, followed by a decimal point
// '.', folloarwed by the remaining k-1 digits of the decimal
// representation of s, followed by the lowercase character 'e',
// followed by a plus sign '+' or minus sign '-' according to
// whether n-1 is positive or negative, followed by the decimal
// representation of the integer abs(n-1) (with no leading zeros).
return intRep.substring(0, 1) + "." + intRep.substring(1, k)
+ ((n - 1) < 0 ? "e-" : "e+") + Math.abs(n - 1);
}
}
public String typeOf() { return "number"; }
public JsonML toJsonML() {
FilePosition pos = getFilePosition();
Number value = getValue();
if (value instanceof Double) {
double d = value.doubleValue();
if (Double.isNaN(d) || Double.isInfinite(d)) {
NumberLiteral num = new IntegerLiteral(
FilePosition.startOf(pos), Double.isNaN(d) ? 0 : d < 0 ? -1 : 1);
NumberLiteral den = new IntegerLiteral(FilePosition.endOf(pos), 0);
return Operation.createInfix(Operator.DIVISION, num, den).toJsonML();
}
}
return JsonMLBuilder.builder(TagType.LiteralExpr, pos)
.setAttribute(TagAttr.TYPE, "number")
.setAttribute(TagAttr.VALUE, value)
.build();
}
}