Bug 55742: Apply patch for Oct2Dec and refactor Hex2Dec to also use BaseNumberUtils.convertToDecimal

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1538765 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Cédric Walter 2013-11-04 21:09:40 +00:00
parent 7014abae83
commit 6cb3b01753
7 changed files with 273 additions and 47 deletions

View File

@ -136,7 +136,7 @@ public final class AnalysisToolPak implements UDFFinder {
r(m, "MULTINOMIAL", null);
r(m, "NETWORKDAYS", NetworkdaysFunction.instance);
r(m, "NOMINAL", null);
r(m, "OCT2BIN", null);
r(m, "OCT2BIN", Oct2Dec.instance);
r(m, "OCT2DEC", null);
r(m, "OCT2HEX", null);
r(m, "ODDFPRICE", null);

View File

@ -0,0 +1,78 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You 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 org.apache.poi.ss.formula.functions;
/**
* <p>Some utils for converting from and to any base<p/>
*
* @author cedric dot walter @ gmail dot com
*/
public class BaseNumberUtils {
public static double convertToDecimal(String value, int base, int maxNumberOfPlaces) throws IllegalArgumentException {
if (value.isEmpty()) {
return 0.0;
}
long stringLength = value.length();
if (stringLength > maxNumberOfPlaces) {
throw new IllegalArgumentException();
}
double decimalValue = 0.0;
long signedDigit = 0;
boolean hasSignedDigit = true;
char[] characters = value.toCharArray();
for (char character : characters) {
long digit;
if ('0' <= character && character <= '9') {
digit = character - '0';
} else if ('A' <= character && character <= 'Z') {
digit = 10 + (character - 'A');
} else if ('a' <= character && character <= 'z') {
digit = 10 + (character - 'a');
} else {
digit = base;
}
if (digit < base) {
if (hasSignedDigit) {
hasSignedDigit = false;
signedDigit = digit;
}
decimalValue = decimalValue * base + digit;
} else {
throw new IllegalArgumentException("character not allowed");
}
}
boolean isNegative = (!hasSignedDigit && stringLength == maxNumberOfPlaces && (signedDigit >= base / 2));
if (isNegative) {
decimalValue = getTwoComplement(base, maxNumberOfPlaces, decimalValue);
decimalValue = decimalValue * -1.0;
}
return decimalValue;
}
private static double getTwoComplement(double base, double maxNumberOfPlaces, double decimalValue) {
return (Math.pow(base, maxNumberOfPlaces) - decimalValue);
}
}

View File

@ -20,8 +20,6 @@ package org.apache.poi.ss.formula.functions;
import org.apache.poi.ss.formula.OperationEvaluationContext;
import org.apache.poi.ss.formula.eval.*;
import java.math.BigInteger;
/**
* Implementation for Excel HEX2DEC() function.<p/>
* <p/>
@ -41,53 +39,16 @@ public class Hex2Dec extends Fixed1ArgFunction implements FreeRefFunction {
public static final FreeRefFunction instance = new Hex2Dec();
static final int HEXADECIMAL_BASE = 16;
static final int MAX_NUMBER_OF_PLACES = 10;
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval numberVE) {
String number = OperandResolver.coerceValueToString(numberVE);
if (number.length() > 10) {
String hex = OperandResolver.coerceValueToString(numberVE);
try {
return new NumberEval(BaseNumberUtils.convertToDecimal(hex, HEXADECIMAL_BASE, MAX_NUMBER_OF_PLACES));
} catch (IllegalArgumentException e) {
return ErrorEval.NUM_ERROR;
}
String unsigned;
boolean isPositive = false;
boolean isNegative = false;
if (number.length() < 10) {
unsigned = number;
isPositive = true;
} else {
//remove sign bit
unsigned = number.substring(1);
isNegative =
number.startsWith("8") || number.startsWith("9") ||
number.startsWith("A") || number.startsWith("B") ||
number.startsWith("C") || number.startsWith("D") ||
number.startsWith("E") || number.startsWith("F");
}
long decimal;
if (isPositive) {
try {
decimal = Integer.parseInt(unsigned, 16);
} catch (NumberFormatException ee) {
// number is not a valid hexadecimal number
return ErrorEval.NUM_ERROR;
}
} else {
if (isNegative) {
BigInteger temp = new BigInteger(unsigned, 16);
BigInteger subtract = BigInteger.ONE.shiftLeft(unsigned.length() * 4);
temp = temp.subtract(subtract);
decimal = temp.longValue();
} else {
try {
decimal = Integer.parseInt(unsigned, 16);
} catch (NumberFormatException ee) {
// number is not a valid hexadecimal number
return ErrorEval.NUM_ERROR;
}
}
}
return new NumberEval(decimal);
}
public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {

View File

@ -0,0 +1,64 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You 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 org.apache.poi.ss.formula.functions;
import org.apache.poi.ss.formula.OperationEvaluationContext;
import org.apache.poi.ss.formula.eval.ErrorEval;
import org.apache.poi.ss.formula.eval.NumberEval;
import org.apache.poi.ss.formula.eval.OperandResolver;
import org.apache.poi.ss.formula.eval.ValueEval;
/**
* <p>Implementation for Excel Oct2Dec() function.<p/>
* <p>
* Converts an octal number to decimal.
* </p>
* <p>
* <b>Syntax</b>:<br/> <b>Oct2Dec </b>(<b>number</b> )
* </p>
* <p/>
* Number is the octal number you want to convert. Number may not contain more than 10 octal characters (30 bits).
* The most significant bit of number is the sign bit. The remaining 29 bits are magnitude bits.
* Negative numbers are represented using two's-complement notation..
* <p/>
* If number is not a valid octal number, OCT2DEC returns the #NUM! error value.
*
* @author cedric dot walter @ gmail dot com
*/
public class Oct2Dec extends Fixed1ArgFunction implements FreeRefFunction {
public static final FreeRefFunction instance = new Oct2Dec();
static final int MAX_NUMBER_OF_PLACES = 10;
static final int OCTAL_BASE = 8;
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval numberVE) {
String octal = OperandResolver.coerceValueToString(numberVE);
try {
return new NumberEval(BaseNumberUtils.convertToDecimal(octal, OCTAL_BASE, MAX_NUMBER_OF_PLACES));
} catch (IllegalArgumentException e) {
return ErrorEval.NUM_ERROR;
}
}
public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
if (args.length != 1) {
return ErrorEval.VALUE_INVALID;
}
return evaluate(ec.getRowIndex(), ec.getColumnIndex(), args[0]);
}
}

View File

@ -0,0 +1,60 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You 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 org.apache.poi.ss.formula.functions;
import junit.framework.TestCase;
import org.apache.poi.ss.formula.eval.ErrorEval;
import org.apache.poi.ss.formula.eval.NumberEval;
import org.apache.poi.ss.formula.eval.StringEval;
import org.apache.poi.ss.formula.eval.ValueEval;
/**
* Tests for {@link Hex2Dec}
*
* @author cedric dot walter @ gmail dot com
*/
public final class TestHex2Dec extends TestCase {
private static ValueEval invokeValue(String number1) {
ValueEval[] args = new ValueEval[] { new StringEval(number1) };
return new Hex2Dec().evaluate(args, -1, -1);
}
private static void confirmValue(String msg, String number1, String expected) {
ValueEval result = invokeValue(number1);
assertEquals(NumberEval.class, result.getClass());
assertEquals(msg, expected, ((NumberEval) result).getStringValue());
}
private static void confirmValueError(String msg, String number1, ErrorEval numError) {
ValueEval result = invokeValue(number1);
assertEquals(ErrorEval.class, result.getClass());
assertEquals(msg, numError, result);
}
public void testBasic() {
confirmValue("Converts octal 'A5' to decimal (165)", "A5", "165");
confirmValue("Converts octal FFFFFFFF5B to decimal (-165)", "FFFFFFFF5B", "-165");
confirmValue("Converts octal 3DA408B9 to decimal (-165)", "3DA408B9", "1034160313");
}
public void testErrors() {
confirmValueError("not a valid octal number","GGGGGGG", ErrorEval.NUM_ERROR);
confirmValueError("not a valid octal number","3.14159", ErrorEval.NUM_ERROR);
}
}

View File

@ -0,0 +1,63 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You 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 org.apache.poi.ss.formula.functions;
import junit.framework.TestCase;
import org.apache.poi.ss.formula.eval.ErrorEval;
import org.apache.poi.ss.formula.eval.NumberEval;
import org.apache.poi.ss.formula.eval.StringEval;
import org.apache.poi.ss.formula.eval.ValueEval;
/**
* Tests for {@link org.apache.poi.ss.formula.functions.Oct2Dec}
*
* @author cedric dot walter @ gmail dot com
*/
public final class TestOct2Dec extends TestCase {
private static ValueEval invokeValue(String number1) {
ValueEval[] args = new ValueEval[] { new StringEval(number1) };
return new Oct2Dec().evaluate(args, -1, -1);
}
private static void confirmValue(String msg, String number1, String expected) {
ValueEval result = invokeValue(number1);
assertEquals(NumberEval.class, result.getClass());
assertEquals(msg, expected, ((NumberEval) result).getStringValue());
}
private static void confirmValueError(String msg, String number1, ErrorEval numError) {
ValueEval result = invokeValue(number1);
assertEquals(ErrorEval.class, result.getClass());
assertEquals(msg, numError, result);
}
public void testBasic() {
confirmValue("Converts octal '' to decimal (0)", "", "0");
confirmValue("Converts octal 54 to decimal (44)", "54", "44");
confirmValue("Converts octal 7777777533 to decimal (-165)", "7777777533", "-165");
confirmValue("Converts octal 7000000000 to decimal (-134217728)", "7000000000", "-134217728");
confirmValue("Converts octal 7776667533 to decimal (-299173)", "7776667533", "-299173");
}
public void testErrors() {
confirmValueError("not a valid octal number","ABCDEFGH", ErrorEval.NUM_ERROR);
confirmValueError("not a valid octal number","99999999", ErrorEval.NUM_ERROR);
confirmValueError("not a valid octal number","3.14159", ErrorEval.NUM_ERROR);
}
}