]> source.dussan.org Git - poi.git/commitdiff
Bug 55742: Apply patch for Oct2Dec and refactor Hex2Dec to also use BaseNumberUtils...
authorCédric Walter <cedricwalter@apache.org>
Mon, 4 Nov 2013 21:09:40 +0000 (21:09 +0000)
committerCédric Walter <cedricwalter@apache.org>
Mon, 4 Nov 2013 21:09:40 +0000 (21:09 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1538765 13f79535-47bb-0310-9956-ffa450edef68

src/java/org/apache/poi/ss/formula/atp/AnalysisToolPak.java
src/java/org/apache/poi/ss/formula/functions/BaseNumberUtils.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/functions/Hex2Dec.java
src/java/org/apache/poi/ss/formula/functions/Oct2Dec.java [new file with mode: 0644]
src/testcases/org/apache/poi/ss/formula/functions/TestHex2Dec.java [new file with mode: 0644]
src/testcases/org/apache/poi/ss/formula/functions/TestOct2Dec.java [new file with mode: 0644]
test-data/spreadsheet/FormulaEvalTestData.xls

index 224cbf12ee92e4d2b2f9e6e6f0d89a65f0822ca0..34e788f022ed1c4e95671450d14557a0ef3be8f3 100644 (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);
diff --git a/src/java/org/apache/poi/ss/formula/functions/BaseNumberUtils.java b/src/java/org/apache/poi/ss/formula/functions/BaseNumberUtils.java
new file mode 100644 (file)
index 0000000..110e869
--- /dev/null
@@ -0,0 +1,78 @@
+/* ====================================================================\r
+   Licensed to the Apache Software Foundation (ASF) under one or more\r
+   contributor license agreements.  See the NOTICE file distributed with\r
+   this work for additional information regarding copyright ownership.\r
+   The ASF licenses this file to You under the Apache License, Version 2.0\r
+   (the "License"); you may not use this file except in compliance with\r
+   the License.  You may obtain a copy of the License at\r
+\r
+       http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+   Unless required by applicable law or agreed to in writing, software\r
+   distributed under the License is distributed on an "AS IS" BASIS,\r
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+   See the License for the specific language governing permissions and\r
+   limitations under the License.\r
+==================================================================== */\r
+package org.apache.poi.ss.formula.functions;\r
+\r
+/**\r
+ * <p>Some utils for converting from and to any base<p/>\r
+ *\r
+ * @author cedric dot walter @ gmail dot com\r
+ */\r
+public class BaseNumberUtils {\r
+\r
+\r
+    public static double convertToDecimal(String value, int base, int maxNumberOfPlaces) throws IllegalArgumentException {\r
+        if (value.isEmpty()) {\r
+            return 0.0;\r
+        }\r
+\r
+        long stringLength = value.length();\r
+        if (stringLength > maxNumberOfPlaces) {\r
+            throw new IllegalArgumentException();\r
+        }\r
+\r
+        double decimalValue = 0.0;\r
+\r
+        long signedDigit = 0;\r
+        boolean hasSignedDigit = true;\r
+        char[] characters = value.toCharArray();\r
+        for (char character : characters) {\r
+            long digit;\r
+\r
+            if ('0' <= character && character <= '9') {\r
+                digit = character - '0';\r
+            } else if ('A' <= character && character <= 'Z') {\r
+                digit = 10 + (character - 'A');\r
+            } else if ('a' <= character && character <= 'z') {\r
+                digit = 10 + (character - 'a');\r
+            } else {\r
+                digit = base;\r
+            }\r
+\r
+            if (digit < base) {\r
+                if (hasSignedDigit) {\r
+                    hasSignedDigit = false;\r
+                    signedDigit = digit;\r
+                }\r
+                decimalValue = decimalValue * base + digit;\r
+            } else {\r
+                throw new IllegalArgumentException("character not allowed");\r
+            }\r
+        }\r
+\r
+        boolean isNegative = (!hasSignedDigit && stringLength == maxNumberOfPlaces  && (signedDigit >= base / 2));\r
+        if (isNegative) {\r
+            decimalValue = getTwoComplement(base, maxNumberOfPlaces, decimalValue);\r
+            decimalValue = decimalValue * -1.0;\r
+        }\r
+\r
+        return decimalValue;\r
+    }\r
+\r
+    private static double getTwoComplement(double base, double maxNumberOfPlaces, double decimalValue) {\r
+        return (Math.pow(base, maxNumberOfPlaces) - decimalValue);\r
+    }\r
+}\r
index 6fdebf52c2502c1b4c9a98f278954fad1a480f74..ccd641eaef825e159e00bb9ed93d046ccf6ab8d6 100644 (file)
@@ -20,8 +20,6 @@ package org.apache.poi.ss.formula.functions;
 import org.apache.poi.ss.formula.OperationEvaluationContext;\r
 import org.apache.poi.ss.formula.eval.*;\r
 \r
-import java.math.BigInteger;\r
-\r
 /**\r
  * Implementation for Excel HEX2DEC() function.<p/>\r
  * <p/>\r
@@ -41,53 +39,16 @@ public class Hex2Dec extends Fixed1ArgFunction implements FreeRefFunction {
 \r
     public static final FreeRefFunction instance = new Hex2Dec();\r
 \r
+    static final int HEXADECIMAL_BASE = 16;\r
+    static final int MAX_NUMBER_OF_PLACES = 10;\r
+\r
     public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval numberVE) {\r
-        String number = OperandResolver.coerceValueToString(numberVE);\r
-        if (number.length() > 10) {\r
+        String hex = OperandResolver.coerceValueToString(numberVE);\r
+        try {\r
+            return new NumberEval(BaseNumberUtils.convertToDecimal(hex, HEXADECIMAL_BASE, MAX_NUMBER_OF_PLACES));\r
+        }  catch (IllegalArgumentException e) {\r
             return ErrorEval.NUM_ERROR;\r
         }\r
-\r
-        String unsigned;\r
-        boolean isPositive = false;\r
-        boolean isNegative = false;\r
-        if (number.length() < 10) {\r
-            unsigned = number;\r
-            isPositive = true;\r
-        } else {\r
-            //remove sign bit\r
-            unsigned = number.substring(1);\r
-            isNegative =\r
-                    number.startsWith("8") || number.startsWith("9") ||\r
-                            number.startsWith("A") || number.startsWith("B") ||\r
-                            number.startsWith("C") || number.startsWith("D") ||\r
-                            number.startsWith("E") || number.startsWith("F");\r
-        }\r
-\r
-        long decimal;\r
-        if (isPositive) {\r
-            try {\r
-                decimal = Integer.parseInt(unsigned, 16);\r
-            } catch (NumberFormatException ee) {\r
-                // number is not a valid hexadecimal number\r
-                return ErrorEval.NUM_ERROR;\r
-            }\r
-        } else {\r
-            if (isNegative) {\r
-                BigInteger temp = new BigInteger(unsigned, 16);\r
-                BigInteger subtract = BigInteger.ONE.shiftLeft(unsigned.length() * 4);\r
-                temp = temp.subtract(subtract);\r
-                decimal = temp.longValue();\r
-            } else {\r
-                try {\r
-                    decimal = Integer.parseInt(unsigned, 16);\r
-                } catch (NumberFormatException ee) {\r
-                    // number is not a valid hexadecimal number\r
-                    return ErrorEval.NUM_ERROR;\r
-                }\r
-            }\r
-        }\r
-\r
-        return new NumberEval(decimal);\r
     }\r
 \r
     public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {\r
diff --git a/src/java/org/apache/poi/ss/formula/functions/Oct2Dec.java b/src/java/org/apache/poi/ss/formula/functions/Oct2Dec.java
new file mode 100644 (file)
index 0000000..db9447d
--- /dev/null
@@ -0,0 +1,64 @@
+/* ====================================================================\r
+   Licensed to the Apache Software Foundation (ASF) under one or more\r
+   contributor license agreements.  See the NOTICE file distributed with\r
+   this work for additional information regarding copyright ownership.\r
+   The ASF licenses this file to You under the Apache License, Version 2.0\r
+   (the "License"); you may not use this file except in compliance with\r
+   the License.  You may obtain a copy of the License at\r
+\r
+       http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+   Unless required by applicable law or agreed to in writing, software\r
+   distributed under the License is distributed on an "AS IS" BASIS,\r
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+   See the License for the specific language governing permissions and\r
+   limitations under the License.\r
+==================================================================== */\r
+package org.apache.poi.ss.formula.functions;\r
+\r
+import org.apache.poi.ss.formula.OperationEvaluationContext;\r
+import org.apache.poi.ss.formula.eval.ErrorEval;\r
+import org.apache.poi.ss.formula.eval.NumberEval;\r
+import org.apache.poi.ss.formula.eval.OperandResolver;\r
+import org.apache.poi.ss.formula.eval.ValueEval;\r
+\r
+/**\r
+ * <p>Implementation for Excel Oct2Dec() function.<p/>\r
+ * <p>\r
+ * Converts an octal number to decimal.\r
+ * </p>\r
+ * <p>\r
+ * <b>Syntax</b>:<br/> <b>Oct2Dec  </b>(<b>number</b> )\r
+ * </p>\r
+ * <p/>\r
+ * Number     is the octal number you want to convert. Number may not contain more than 10 octal characters (30 bits).\r
+ * The most significant bit of number is the sign bit. The remaining 29 bits are magnitude bits.\r
+ * Negative numbers are represented using two's-complement notation..\r
+ * <p/>\r
+ * If number is not a valid octal number, OCT2DEC returns the #NUM! error value.\r
+ *\r
+ * @author cedric dot walter @ gmail dot com\r
+ */\r
+public class Oct2Dec extends Fixed1ArgFunction implements FreeRefFunction {\r
+\r
+    public static final FreeRefFunction instance = new Oct2Dec();\r
+\r
+    static final int MAX_NUMBER_OF_PLACES = 10;\r
+    static final int OCTAL_BASE = 8;\r
+\r
+    public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval numberVE) {\r
+        String octal = OperandResolver.coerceValueToString(numberVE);\r
+        try {\r
+           return new NumberEval(BaseNumberUtils.convertToDecimal(octal, OCTAL_BASE, MAX_NUMBER_OF_PLACES));\r
+        }  catch (IllegalArgumentException e) {\r
+            return ErrorEval.NUM_ERROR;\r
+        }\r
+    }\r
+\r
+    public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {\r
+        if (args.length != 1) {\r
+            return ErrorEval.VALUE_INVALID;\r
+        }\r
+        return evaluate(ec.getRowIndex(), ec.getColumnIndex(), args[0]);\r
+    }\r
+}\r
diff --git a/src/testcases/org/apache/poi/ss/formula/functions/TestHex2Dec.java b/src/testcases/org/apache/poi/ss/formula/functions/TestHex2Dec.java
new file mode 100644 (file)
index 0000000..7c04157
--- /dev/null
@@ -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);
+    }
+}
diff --git a/src/testcases/org/apache/poi/ss/formula/functions/TestOct2Dec.java b/src/testcases/org/apache/poi/ss/formula/functions/TestOct2Dec.java
new file mode 100644 (file)
index 0000000..7599ec9
--- /dev/null
@@ -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);
+    }
+}
index 4245acb88ddeacc231355438a00267c45ba9f3f7..f06572b62525ff42cb865aff489bf25f6ee4b5de 100644 (file)
Binary files a/test-data/spreadsheet/FormulaEvalTestData.xls and b/test-data/spreadsheet/FormulaEvalTestData.xls differ