Skip to content

Commit

Permalink
[bugfix] Correct the comparison of number types in eXist-db
Browse files Browse the repository at this point in the history
  • Loading branch information
adamretter committed Apr 13, 2020
1 parent 26e5472 commit 03884f6
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 57 deletions.
59 changes: 20 additions & 39 deletions exist-core/src/main/java/org/exist/xquery/value/DecimalValue.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@
import org.exist.xquery.ErrorCodes;
import org.exist.xquery.XPathException;

import javax.annotation.Nullable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.function.IntSupplier;
import java.util.regex.Pattern;

/**
Expand All @@ -52,7 +54,7 @@ public class DecimalValue extends NumericValue {
//Copied from Saxon 8.8
private static boolean stripTrailingZerosMethodUnavailable = false;
private static Method stripTrailingZerosMethod = null;
BigDecimal value;
final BigDecimal value;

public DecimalValue(BigDecimal decimal) {
this.value = stripTrailingZeros(decimal);
Expand Down Expand Up @@ -271,6 +273,23 @@ public boolean isPositive() {
return value.signum() > 0;
}

@Override
protected @Nullable IntSupplier createComparisonWith(final NumericValue other) {
final IntSupplier comparison;
if (other instanceof IntegerValue) {
comparison = () -> value.compareTo(new BigDecimal(((IntegerValue)other).value));
} else if (other instanceof DecimalValue) {
comparison = () -> value.compareTo(((DecimalValue)other).value);
} else if (other instanceof DoubleValue) {
comparison = () -> value.compareTo(BigDecimal.valueOf(((DoubleValue)other).value));
} else if (other instanceof FloatValue) {
comparison = () -> value.compareTo(BigDecimal.valueOf(((FloatValue)other).value));
} else {
return null;
}
return comparison;
}

/* (non-Javadoc)
* @see org.exist.xquery.value.NumericValue#negate()
*/
Expand Down Expand Up @@ -459,44 +478,6 @@ public AtomicValue min(Collator collator, AtomicValue other) throws XPathExcepti
}
}

@Override
public boolean compareTo(Collator collator, Comparison operator, AtomicValue other)
throws XPathException {
if (other.isEmpty()) {
//Never equal, or inequal...
return false;
}
if (Type.subTypeOf(other.getType(), Type.NUMBER)) {
if (isNaN()) {
//NaN does not equal itself.
if (((NumericValue) other).isNaN()) {
return operator == Comparison.NEQ;
}
}
if (Type.subTypeOf(other.getType(), Type.DECIMAL)) {
final DecimalValue otherValue = (DecimalValue) other.convertTo(Type.DECIMAL);
switch (operator) {
case EQ:
return compareTo(otherValue) == Constants.EQUAL;
case NEQ:
return compareTo(otherValue) != Constants.EQUAL;
case GT:
return compareTo(otherValue) == Constants.SUPERIOR;
case GTEQ:
return compareTo(otherValue) != Constants.INFERIOR;
case LT:
return compareTo(otherValue) == Constants.INFERIOR;
case LTEQ:
return compareTo(otherValue) != Constants.SUPERIOR;
default:
throw new XPathException("Type error: cannot apply operator to numeric value");
}
}
}
//Default to the standard numeric comparison
return super.compareTo(collator, operator, other);
}

public int compareTo(Object o) {
final AtomicValue other = (AtomicValue) o;
if (Type.subTypeOf(other.getType(), Type.DECIMAL)) {
Expand Down
21 changes: 20 additions & 1 deletion exist-core/src/main/java/org/exist/xquery/value/DoubleValue.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@
import org.exist.xquery.ErrorCodes;
import org.exist.xquery.XPathException;

import javax.annotation.Nullable;
import java.math.BigDecimal;
import java.util.function.IntSupplier;

public class DoubleValue extends NumericValue {
// m × 2^e, where m is an integer whose absolute value is less than 2^53,
Expand All @@ -39,7 +41,7 @@ public class DoubleValue extends NumericValue {
public static final DoubleValue NEGATIVE_INFINITY = new DoubleValue(Double.NEGATIVE_INFINITY);
public static final DoubleValue NaN = new DoubleValue(Double.NaN);

private final double value;
final double value;

public DoubleValue(final double value) {
this.value = value;
Expand Down Expand Up @@ -124,6 +126,23 @@ public boolean isPositive() {
return (Double.compare(value, 0.0) > Constants.EQUAL);
}

@Override
protected @Nullable IntSupplier createComparisonWith(final NumericValue other) {
final IntSupplier comparison;
if (other instanceof IntegerValue) {
comparison = () -> BigDecimal.valueOf(value).compareTo(new BigDecimal(((IntegerValue)other).value));
} else if (other instanceof DecimalValue) {
comparison = () -> BigDecimal.valueOf(value).compareTo(((DecimalValue)other).value);
} else if (other instanceof DoubleValue) {
comparison = () -> Double.compare(value, ((DoubleValue)other).value);
} else if (other instanceof FloatValue) {
comparison = () -> Double.compare(value, ((FloatValue)other).value);
} else {
return null;
}
return comparison;
}

@Override
public AtomicValue convertTo(final int requiredType) throws XPathException {
switch (requiredType) {
Expand Down
21 changes: 20 additions & 1 deletion exist-core/src/main/java/org/exist/xquery/value/FloatValue.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@
import org.exist.xquery.ErrorCodes;
import org.exist.xquery.XPathException;

import javax.annotation.Nullable;
import java.math.BigDecimal;
import java.util.function.IntSupplier;

/**
* @author wolf
Expand All @@ -42,7 +44,7 @@ public class FloatValue extends NumericValue {
public final static FloatValue NEGATIVE_INFINITY = new FloatValue(Float.NEGATIVE_INFINITY);
public final static FloatValue ZERO = new FloatValue(0.0E0f);

protected float value;
final float value;

public FloatValue(float value) {
this.value = value;
Expand Down Expand Up @@ -128,6 +130,23 @@ public boolean isPositive() {
return (Float.compare(value, 0f) > Constants.EQUAL);
}

@Override
protected @Nullable IntSupplier createComparisonWith(final NumericValue other) {
final IntSupplier comparison;
if (other instanceof IntegerValue) {
comparison = () -> BigDecimal.valueOf(value).compareTo(new BigDecimal(((IntegerValue)other).value));
} else if (other instanceof DecimalValue) {
comparison = () -> BigDecimal.valueOf(value).compareTo(((DecimalValue)other).value);
} else if (other instanceof DoubleValue) {
comparison = () -> BigDecimal.valueOf(value).compareTo(BigDecimal.valueOf(((DoubleValue)other).value));
} else if (other instanceof FloatValue) {
comparison = () -> Float.compare(value, ((FloatValue)other).value);
} else {
return null;
}
return comparison;
}

public boolean hasFractionalPart() {
if (isNaN()) {
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,14 @@
package org.exist.xquery.value;

import com.ibm.icu.text.Collator;
import org.exist.xquery.Constants;
import org.exist.xquery.ErrorCodes;
import org.exist.xquery.XPathException;

import javax.annotation.Nullable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.function.IntSupplier;

/**
* Definition: integer is derived from decimal by fixing the value of fractionDigits to be 0.
Expand Down Expand Up @@ -66,7 +69,7 @@ public class IntegerValue extends NumericValue {

private static final BigInteger LARGEST_UNSIGNED_BYTE = new BigInteger("255");

private final BigInteger value;
final BigInteger value;
private final int type;

public IntegerValue(final long value) {
Expand Down Expand Up @@ -220,6 +223,23 @@ public boolean isPositive() {
return value.signum() > 0;
}

@Override
protected @Nullable IntSupplier createComparisonWith(final NumericValue other) {
final IntSupplier comparison;
if (other instanceof IntegerValue) {
comparison = () -> value.compareTo(((IntegerValue)other).value);
} else if (other instanceof DecimalValue) {
comparison = () -> new BigDecimal(value).compareTo(((DecimalValue)other).value);
} else if (other instanceof DoubleValue) {
comparison = () -> new BigDecimal(value).compareTo(BigDecimal.valueOf(((DoubleValue)other).value));
} else if (other instanceof FloatValue) {
comparison = () -> new BigDecimal(value).compareTo(BigDecimal.valueOf(((FloatValue)other).value));
} else {
return null;
}
return comparison;
}

@Override
public AtomicValue convertTo(final int requiredType) throws XPathException {
if (this.type == requiredType) {
Expand Down
57 changes: 42 additions & 15 deletions exist-core/src/main/java/org/exist/xquery/value/NumericValue.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
import org.exist.xquery.ErrorCodes;
import org.exist.xquery.XPathException;

import javax.annotation.Nullable;
import java.util.function.IntSupplier;

public abstract class NumericValue extends ComputableValue {

public double getDouble() throws XPathException {
Expand Down Expand Up @@ -41,7 +44,7 @@ public boolean effectiveBooleanValue() {
}

@Override
public boolean compareTo(final Collator collator, final Comparison operator, final AtomicValue other)
public final boolean compareTo(final Collator collator, final Comparison operator, final AtomicValue other)
throws XPathException {
if (other.isEmpty()) {
//Never equal, or inequal...
Expand All @@ -55,44 +58,68 @@ public boolean compareTo(final Collator collator, final Comparison operator, fin
return operator == Comparison.NEQ;
}
}
final double otherVal = ((NumericValue) other).getDouble();
final double val = getDouble();

final IntSupplier comparison = createComparisonWith((NumericValue) other);
if (comparison == null) {
throw new XPathException(ErrorCodes.XPTY0004, "Type error: cannot apply operator to numeric value");
}

switch (operator) {
case EQ:
return val == otherVal;
return comparison.getAsInt() == 0;
case NEQ:
return val != otherVal;
return comparison.getAsInt() != 0;
case GT:
return val > otherVal;
return comparison.getAsInt() > 0;
case GTEQ:
return val >= otherVal;
return comparison.getAsInt() >= 0;
case LT:
return val < otherVal;
return comparison.getAsInt() < 0;
case LTEQ:
return val <= otherVal;
return comparison.getAsInt() <= 0;
default:
throw new XPathException("Type error: cannot apply operator to numeric value");
throw new XPathException(ErrorCodes.XPTY0004, "Type error: cannot apply operator to numeric value");
}
}

throw new XPathException(ErrorCodes.XPTY0004, "Type error: cannot compare operands: " +
Type.getTypeName(getType()) + " and " +
Type.getTypeName(other.getType()));
}

/**
* Creates a function which when called performs a comparison between this NumericValue
* and the {@code other} NumericValue.
*
* @param other the other numberic value to compare this against.
*
* @return the comparison function or null.
*/
protected abstract @Nullable IntSupplier createComparisonWith(final NumericValue other);

@Override
public int compareTo(final Collator collator, final AtomicValue other) throws XPathException {
public final int compareTo(final Collator collator, final AtomicValue other) throws XPathException {
if (other.isEmpty()) {
//Never equal, or inequal...
return Constants.INFERIOR;
}

if (Type.subTypeOfUnion(other.getType(), Type.NUMBER)) {
if (isNaN()) {
//NaN does not equal itself.
if (((NumericValue) other).isNaN()) {
return Constants.INFERIOR;
}
}
final double otherVal = ((NumericValue) other).getDouble();
final double val = getDouble();
return Double.compare(val, otherVal);

final IntSupplier comparison = createComparisonWith((NumericValue) other);
if (comparison == null) {
throw new XPathException(ErrorCodes.XPTY0004, "Type error: cannot apply operator to numeric value");
}

return comparison.getAsInt();
} else {
throw new XPathException("cannot compare numeric value to non-numeric value");
throw new XPathException(ErrorCodes.XPTY0004, "cannot compare numeric value to non-numeric value");
}
}

Expand Down

0 comments on commit 03884f6

Please sign in to comment.