public static final Value ZERO_VAL = FALSE_VAL;
public static final RoundingMode ROUND_MODE = RoundingMode.HALF_EVEN;
+ private static final int MAX_NUMERIC_SCALE = 28;
private enum CoercionType {
SIMPLE(true, true), GENERAL(false, true), COMPARE(false, false);
}
return toValue(param1.getAsDouble() / d2);
case BIG_DEC:
- return toValue(param1.getAsBigDecimal().divide(param2.getAsBigDecimal()));
+ return toValue(divide(param1.getAsBigDecimal(), param2.getAsBigDecimal()));
default:
throw new EvalException("Unexpected type " + mathType);
}
}
public static Value toValue(BigDecimal s) {
- return new BigDecimalValue(s);
+ return new BigDecimalValue(normalize(s));
}
public static Value toValue(Value.Type type, double dd, DateFormat fmt) {
- return toValue(type, new Date(ColumnImpl.fromDateDouble(dd, fmt.getCalendar())),
- fmt);
+ return toValue(type, new Date(ColumnImpl.fromDateDouble(
+ dd, fmt.getCalendar())), fmt);
}
public static Value toValue(EvalContext ctx, Value.Type type, Date d) {
if(prefType.isNumeric()) {
// re-evaluate the numeric type choice based on the type of the parsed
// number
- Value.Type numType = ((num.stripTrailingZeros().scale() > 0) ?
+ Value.Type numType = ((num.scale() > 0) ?
Value.Type.BIG_DEC : Value.Type.LONG);
prefType = getPreferredNumericType(numType, prefType);
}
return ((t1.compareTo(t2) > 0) ? t1 : t2);
}
+ static BigDecimal divide(BigDecimal num, BigDecimal denom) {
+ return num.divide(denom, MAX_NUMERIC_SCALE, ROUND_MODE);
+ }
+
static boolean isIntegral(double d) {
double id = Math.rint(d);
return ((d == id) && (d >= MIN_INT) && (d <= MAX_INT) &&
!Double.isInfinite(d) && !Double.isNaN(d));
}
+ /**
+ * Converts the given BigDecimal to the minimal scale >= 0;
+ */
+ static BigDecimal normalize(BigDecimal bd) {
+ if(bd.scale() == 0) {
+ return bd;
+ }
+ // handle a bug in the jdk which doesn't strip zero values
+ if(bd.compareTo(BigDecimal.ZERO) == 0) {
+ return BigDecimal.ZERO;
+ }
+ bd = bd.stripTrailingZeros();
+ if(bd.scale() < 0) {
+ bd = bd.setScale(0);
+ }
+ return bd;
+ }
}
package com.healthmarketscience.jackcess.impl.expr;
+import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.FieldPosition;
import java.text.ParsePosition;
*
* @author James Ahlborn
*/
-class ExpressionTokenizer
+class ExpressionTokenizer
{
private static final int EOF = -1;
private static final char QUOTED_STR_CHAR = '"';
while(buf.hasNext()) {
char c = buf.next();
-
+
byte charFlag = getCharFlag(c);
if(charFlag != 0) {
-
+
// what could it be?
switch(charFlag) {
case IS_OP_FLAG:
case IS_COMP_FLAG:
// special case for default values
- if((exprType == Type.DEFAULT_VALUE) && (c == EQUALS_CHAR) &&
+ if((exprType == Type.DEFAULT_VALUE) && (c == EQUALS_CHAR) &&
(buf.prevPos() == 0)) {
// a leading equals sign indicates how a default value should be
// evaluated
tokens.add(new Token(TokenType.OP, String.valueOf(c)));
continue;
}
-
+
tokens.add(new Token(TokenType.OP, parseCompOp(c, buf)));
break;
switch(c) {
case QUOTED_STR_CHAR:
case SINGLE_QUOTED_STR_CHAR:
- tokens.add(new Token(TokenType.LITERAL, null,
+ tokens.add(new Token(TokenType.LITERAL, null,
parseQuotedString(buf, c), Value.Type.STRING));
break;
case DATE_LIT_QUOTE_CHAR:
private static void consumeWhitespace(ExprBuf buf) {
int c = EOF;
- while(((c = buf.peekNext()) != EOF) &&
+ while(((c = buf.peekNext()) != EOF) &&
hasFlag(getCharFlag((char)c), IS_SPACE_FLAG)) {
buf.next();
}
}
-
+
private static String parseBareString(char firstChar, ExprBuf buf,
Type exprType) {
StringBuilder sb = buf.getScratchBuffer().append(firstChar);
}
sb.append(c);
}
-
+
return sb.toString();
}
return parseStringUntil(buf, DATE_LIT_QUOTE_CHAR, null, false);
}
- private static String parseStringUntil(ExprBuf buf, char endChar,
+ private static String parseStringUntil(ExprBuf buf, char endChar,
Character startChar,
- boolean allowDoubledEscape)
+ boolean allowDoubledEscape)
{
StringBuilder sb = buf.getScratchBuffer();
return sb.toString();
}
- private static Token parseDateLiteral(ExprBuf buf)
+ private static Token parseDateLiteral(ExprBuf buf)
{
TemporalConfig cfg = buf.getTemporalConfig();
String dateStr = parseDateLiteralString(buf);
-
+
boolean hasDate = (dateStr.indexOf(cfg.getDateSeparator()) >= 0);
boolean hasTime = (dateStr.indexOf(cfg.getTimeSeparator()) >= 0);
boolean hasAmPm = false;
-
+
if(hasTime) {
int strLen = dateStr.length();
hasAmPm = ((strLen >= AMPM_SUFFIX_LEN) &&
- (dateStr.regionMatches(true, strLen - AMPM_SUFFIX_LEN,
+ (dateStr.regionMatches(true, strLen - AMPM_SUFFIX_LEN,
AM_SUFFIX, 0, AMPM_SUFFIX_LEN) ||
- dateStr.regionMatches(true, strLen - AMPM_SUFFIX_LEN,
+ dateStr.regionMatches(true, strLen - AMPM_SUFFIX_LEN,
PM_SUFFIX, 0, AMPM_SUFFIX_LEN)));
}
return new Token(TokenType.LITERAL, sdf.parse(dateStr), dateStr, valType,
sdf);
} catch(java.text.ParseException pe) {
- throw new ParseException(
+ throw new ParseException(
"Invalid date time literal " + dateStr + " " + buf, pe);
}
}
String numStr = sb.toString();
try {
- // what number type to use here?
- Object num = (isFp ?
- (Number)Double.valueOf(numStr) :
- (Number)Integer.valueOf(numStr));
+ Number num = null;
+ Value.Type numType = null;
+
+ if(!isFp) {
+ try {
+ // try to parse as int. if that fails, fall back to BigDecimal
+ // (this will handle the case of int overflow)
+ num = Integer.valueOf(numStr);
+ numType = Value.Type.LONG;
+ } catch(NumberFormatException ne) {
+ // fallback to decimal
+ }
+ }
+
+ if(num == null) {
+ num = new BigDecimal(numStr);
+ numType = Value.Type.BIG_DEC;
+ }
+
foundNum = true;
- return new Token(TokenType.LITERAL, num, numStr,
- (isFp ? Value.Type.DOUBLE : Value.Type.LONG));
+ return new Token(TokenType.LITERAL, num, numStr, numType);
} catch(NumberFormatException ne) {
throw new ParseException(
"Invalid number literal " + numStr + " " + buf, ne);
}
-
+
} finally {
if(!foundNum) {
buf.reset(startPos);
private DateFormat _dateTimeFmt24;
private String _baseDate;
private final StringBuilder _scratch = new StringBuilder();
-
+
private ExprBuf(String str, ParseContext ctx) {
_str = str;
_ctx = ctx;
private final DateFormat _fmtDelegate;
private final String _baseDate;
- private TimeFormat(DateFormat parseDelegate, DateFormat fmtDelegate,
+ private TimeFormat(DateFormat parseDelegate, DateFormat fmtDelegate,
String baseDate)
{
_parseDelegate = parseDelegate;
}
for(double i : DBLS) {
- assertEquals(-i, eval("=-(" + i + ")"));
+ assertEquals(toBD(-i), eval("=-(" + i + ")"));
}
for(double i : DBLS) {
- assertEquals(i, eval("=+(" + i + ")"));
+ assertEquals(toBD(i), eval("=+(" + i + ")"));
}
for(int i = -10; i <= 10; ++i) {
for(double i : DBLS) {
for(double j : DBLS) {
- assertEquals((i + j), eval("=" + i + " + " + j));
+ assertEquals(toBD(toBD(i).add(toBD(j))), eval("=" + i + " + " + j));
}
}
for(double i : DBLS) {
for(double j : DBLS) {
- assertEquals((i - j), eval("=" + i + " - " + j));
+ assertEquals(toBD(toBD(i).subtract(toBD(j))), eval("=" + i + " - " + j));
}
}
for(double i : DBLS) {
for(double j : DBLS) {
- assertEquals((i * j), eval("=" + i + " * " + j));
+ assertEquals(toBD(toBD(i).multiply(toBD(j))), eval("=" + i + " * " + j));
}
}
if(j == 0.0d) {
evalFail("=" + i + " / " + j, ArithmeticException.class);
} else {
- assertEquals((i / j), eval("=" + i + " / " + j));
+ assertEquals(toBD(BuiltinOperators.divide(toBD(i), toBD(j))),
+ eval("=" + i + " / " + j));
}
}
}
assertEquals(-100, eval("=-(10)^2"));
assertEquals(-100, eval("=-\"10\"^2"));
- assertEquals(99d, eval("=-10E-1+10e+1"));
- assertEquals(-101d, eval("=-10E-1-10e+1"));
+ assertEquals(toBD(99d), eval("=-10E-1+10e+1"));
+ assertEquals(toBD(-101d), eval("=-10E-1-10e+1"));
}
public void testTypeCoercion() throws Exception
.intValueExact();
}
+ static BigDecimal toBD(double d) {
+ return toBD(new BigDecimal("" + d));
+ }
+
+ static BigDecimal toBD(BigDecimal bd) {
+ return BuiltinOperators.normalize(bd);
+ }
+
private static final class TestParseContext implements Expressionator.ParseContext
{
public TemporalConfig getTemporalConfig() {