forked from eXist-db/exist
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request eXist-db#4400 from evolvedbinary/feature/fn-round,#2
Implement fn:round#2
- Loading branch information
Showing
10 changed files
with
439 additions
and
155 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
124 changes: 124 additions & 0 deletions
124
exist-core/src/main/java/org/exist/xquery/functions/fn/FunRoundBase.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
/* | ||
* eXist-db Open Source Native XML Database | ||
* Copyright (C) 2001 The eXist-db Authors | ||
* | ||
* info@exist-db.org | ||
* http://www.exist-db.org | ||
* | ||
* This library is free software; you can redistribute it and/or | ||
* modify it under the terms of the GNU Lesser General Public | ||
* License as published by the Free Software Foundation; either | ||
* version 2.1 of the License, or (at your option) any later version. | ||
* | ||
* This library is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
* Lesser General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU Lesser General Public | ||
* License along with this library; if not, write to the Free Software | ||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
*/ | ||
package org.exist.xquery.functions.fn; | ||
|
||
import org.exist.xquery.BasicFunction; | ||
import org.exist.xquery.FunctionSignature; | ||
import org.exist.xquery.XPathException; | ||
import org.exist.xquery.XQueryContext; | ||
import org.exist.xquery.value.*; | ||
|
||
import java.math.RoundingMode; | ||
import java.util.Objects; | ||
|
||
/** | ||
* Base class for rounding mode functions | ||
* | ||
* Implements the eval function which knows how to round, | ||
* but defers to the subclass for the {@link RoundingMode} to use. | ||
*/ | ||
abstract class FunRoundBase extends BasicFunction { | ||
|
||
public FunRoundBase(final XQueryContext context, final FunctionSignature signature) { | ||
super(context, signature); | ||
} | ||
|
||
public int returnsType() { | ||
return Type.NUMBER; | ||
} | ||
|
||
abstract protected RoundingMode getFunctionRoundingMode(NumericValue value); | ||
|
||
@Override | ||
public Sequence eval(final Sequence[] args, final Sequence contextSequence) throws XPathException { | ||
|
||
if (args[0].isEmpty()) { | ||
return Sequence.EMPTY_SEQUENCE; | ||
} | ||
|
||
final Item item = args[0].itemAt(0); | ||
final NumericValue value; | ||
if (item instanceof NumericValue) { | ||
value = (NumericValue) item; | ||
} else { | ||
value = (NumericValue) item.convertTo(Type.NUMBER); | ||
} | ||
|
||
final RoundingMode roundingMode = getFunctionRoundingMode(value); | ||
|
||
if (args.length > 1) { | ||
final Item precisionItem = args[1].itemAt(0); | ||
if (precisionItem instanceof IntegerValue) { | ||
final IntegerValue precision = (IntegerValue) precisionItem; | ||
return convertValue(precision, value, roundingMode); | ||
} | ||
} | ||
|
||
return convertValue(IntegerValue.ZERO, value, roundingMode); | ||
} | ||
|
||
/** | ||
* Apply necessary conversions to/from decimal to perform rounding in decimal | ||
* | ||
* @param precision precision of rounding | ||
* @param value to round | ||
* @param roundingMode mode to round in | ||
* @return rounded value in decimal converted back to the input type | ||
* @throws XPathException if a conversion goes wrong (it shouldn't) | ||
*/ | ||
private static Sequence convertValue(final IntegerValue precision, final NumericValue value, final RoundingMode roundingMode) throws XPathException { | ||
|
||
if (value.isInfinite() || value.isNaN()) { | ||
return value; | ||
} | ||
|
||
final DecimalValue decimal = (DecimalValue)value.convertTo(Type.DECIMAL); | ||
// (AP) This is as much precision as we can need, and prevents overflows scaling BigInteger | ||
final IntegerValue usePrecision = truncatePrecision(decimal, precision); | ||
final DecimalValue rounded = (DecimalValue) Objects.requireNonNull(decimal).round(usePrecision, roundingMode); | ||
|
||
if (value.isNegative() && rounded.isZero()) { | ||
//Extreme care!! (AP) -0 as DecimalValue will not be negative, -0.0f and -0.0d will be negative. | ||
//So we need to test that original value to decide whether to negate a "zero" result. | ||
//DecimalValue(s) are not necessarily normalized, but the 0-test will work.. | ||
switch (value.getType()) { | ||
case Type.DOUBLE: | ||
return new DoubleValue(-0.0d); | ||
case Type.FLOAT: | ||
return new FloatValue(-0.0f); | ||
default: | ||
break; | ||
} | ||
} | ||
|
||
return rounded.convertTo(value.getType()); | ||
} | ||
|
||
private static IntegerValue truncatePrecision(final DecimalValue decimal, final IntegerValue precision) throws XPathException { | ||
|
||
final IntegerValue decimalPrecision = new IntegerValue(decimal.getValue().precision()); | ||
if (decimalPrecision.compareTo(precision) < 0) { | ||
return decimalPrecision; | ||
} | ||
return precision; | ||
} | ||
} |
Oops, something went wrong.