diff --git a/ClosedXML/ClosedXML/ClosedXML/ClosedXML.csproj b/ClosedXML/ClosedXML/ClosedXML/ClosedXML.csproj index 5f7d00c..e2fcf0a 100644 --- a/ClosedXML/ClosedXML/ClosedXML/ClosedXML.csproj +++ b/ClosedXML/ClosedXML/ClosedXML/ClosedXML.csproj @@ -150,6 +150,7 @@ + diff --git a/ClosedXML/ClosedXML/ClosedXML/Excel/CalcEngine/Functions/MathTrig.cs b/ClosedXML/ClosedXML/ClosedXML/Excel/CalcEngine/Functions/MathTrig.cs index 2a13691..274bcf9 100644 --- a/ClosedXML/ClosedXML/ClosedXML/Excel/CalcEngine/Functions/MathTrig.cs +++ b/ClosedXML/ClosedXML/ClosedXML/Excel/CalcEngine/Functions/MathTrig.cs @@ -39,8 +39,8 @@ ce.RegisterFunction("LN", 1, Ln); ce.RegisterFunction("LOG", 1, 2, Log); ce.RegisterFunction("LOG10", 1, Log10); - //ce.RegisterFunction("MDETERM", 1, MDeterm); - //ce.RegisterFunction("MINVERSE", 1, MInverse); + ce.RegisterFunction("MDETERM", 1, MDeterm); + ce.RegisterFunction("MINVERSE", 1, MInverse); ce.RegisterFunction("MMULT", 2, MMult); ce.RegisterFunction("MOD", 2, Mod); ce.RegisterFunction("MROUND", 2, MRound); @@ -638,14 +638,63 @@ private static object MMult(List p) { - var oExp1 = p[0] as XObjectExpression; - var oExp2 = p[1] as XObjectExpression; + Double[,] A = GetArray(p[0]); + Double[,] B = GetArray(p[1]); - Double value1 = oExp1 == null ? p[0] : (oExp1.Value as CellRangeReference).Range.FirstCell().GetDouble(); - Double value2 = oExp2 == null ? p[1] : (oExp2.Value as CellRangeReference).Range.FirstCell().GetDouble(); + if (A.GetLength(0) != B.GetLength(0) || A.GetLength(1) != B.GetLength(1)) + throw new ArgumentException("Ranges must have the same number of rows and columns."); - //return value1*value2; - return new List {4, 5, 6, 7}; + var C = new double[A.GetLength(0), A.GetLength(1)]; + for (int i = 0; i < A.GetLength(0); i++) + { + for (int j = 0; j < B.GetLength(1); j++) + { + for (int k = 0; k < A.GetLength(1); k++) + { + C[i, j] += A[i, k] * B[k, j]; + } + } + } + + + return C; + } + + private static double[,] GetArray(Expression expression) + { + var oExp1 = expression as XObjectExpression; + if (oExp1 == null) return new [,]{{(Double)expression}}; + + var range = (oExp1.Value as CellRangeReference).Range; + var rowCount = range.RowCount(); + var columnCount = range.ColumnCount(); + var arr = new double[rowCount,columnCount]; + + for (int row = 0; row < rowCount; row++) + { + for (int column = 0; column < columnCount; column++) + { + arr[row, column] = range.Cell(row + 1, column + 1).GetDouble(); + } + } + + return arr; + } + + private static object MDeterm(List p) + { + var arr = GetArray(p[0]); + var m = new XLMatrix(arr); + + return m.Determinant(); + } + + private static object MInverse(List p) + { + var arr = GetArray(p[0]); + var m = new XLMatrix(arr); + + return m.Invert().mat; } } } \ No newline at end of file diff --git a/ClosedXML/ClosedXML/ClosedXML/Excel/CalcEngine/Functions/Tally.cs b/ClosedXML/ClosedXML/ClosedXML/Excel/CalcEngine/Functions/Tally.cs index 3cfc228..6b5cf45 100644 --- a/ClosedXML/ClosedXML/ClosedXML/Excel/CalcEngine/Functions/Tally.cs +++ b/ClosedXML/ClosedXML/ClosedXML/Excel/CalcEngine/Functions/Tally.cs @@ -53,12 +53,29 @@ Double cntA = 0; foreach (var value in _list) { - var strVal = value as String; - if (value != null && (strVal == null || !XLHelper.IsNullOrWhiteSpace(strVal))) - cntA++; + var vEnumerable = value as IEnumerable; + if (vEnumerable == null) + cntA += AddCount(value); + else + { + foreach (var v in vEnumerable) + { + cntA += AddCount(v); + break; + } + } } return cntA; } + + private static double AddCount(object value) + { + var strVal = value as String; + if (value != null && (strVal == null || !XLHelper.IsNullOrWhiteSpace(strVal))) + return 1; + return 0; + } + public List Numerics() { List retVal = new List(); diff --git a/ClosedXML/ClosedXML/ClosedXML/Excel/CalcEngine/Functions/XLMatrix.cs b/ClosedXML/ClosedXML/ClosedXML/Excel/CalcEngine/Functions/XLMatrix.cs new file mode 100644 index 0000000..000f20c --- /dev/null +++ b/ClosedXML/ClosedXML/ClosedXML/Excel/CalcEngine/Functions/XLMatrix.cs @@ -0,0 +1,594 @@ +using System; +using System.Linq; +using System.Text.RegularExpressions; + +namespace ClosedXML.Excel.CalcEngine.Functions +{ + internal class XLMatrix + { + public XLMatrix L; + public XLMatrix U; + public int cols; + private double detOfP = 1; + public double[,] mat; + private int[] pi; + public int rows; + + public XLMatrix(int iRows, int iCols) // XLMatrix Class constructor + { + rows = iRows; + cols = iCols; + mat = new double[rows,cols]; + } + public XLMatrix(Double[,] arr) + :this(arr.GetLength(0), arr.GetLength(1)) + { + var roCount = arr.GetLength(0); + var coCount = arr.GetLength(1); + for (int ro = 0; ro < roCount; ro++) + { + for (int co = 0; co < coCount; co++) + { + mat[ro, co] = arr[ro, co]; + } + } + } + + public double this[int iRow, int iCol] // Access this matrix as a 2D array + { + get { return mat[iRow, iCol]; } + set { mat[iRow, iCol] = value; } + } + + public Boolean IsSquare() + { + return (rows == cols); + } + + public XLMatrix GetCol(int k) + { + var m = new XLMatrix(rows, 1); + for (var i = 0; i < rows; i++) m[i, 0] = mat[i, k]; + return m; + } + + public void SetCol(XLMatrix v, int k) + { + for (var i = 0; i < rows; i++) mat[i, k] = v[i, 0]; + } + + public void MakeLU() // Function for LU decomposition + { + if (!IsSquare()) throw new Exception("The matrix is not square!"); + L = IdentityMatrix(rows, cols); + U = Duplicate(); + + pi = new int[rows]; + for (var i = 0; i < rows; i++) pi[i] = i; + + var k0 = 0; + + for (var k = 0; k < cols - 1; k++) + { + double p = 0; + for (var i = k; i < rows; i++) // find the row with the biggest pivot + { + if (Math.Abs(U[i, k]) > p) + { + p = Math.Abs(U[i, k]); + k0 = i; + } + } + if (p == 0) + throw new Exception("The matrix is singular!"); + + var pom1 = pi[k]; + pi[k] = pi[k0]; + pi[k0] = pom1; // switch two rows in permutation matrix + + double pom2; + for (var i = 0; i < k; i++) + { + pom2 = L[k, i]; + L[k, i] = L[k0, i]; + L[k0, i] = pom2; + } + + if (k != k0) detOfP *= -1; + + for (var i = 0; i < cols; i++) // Switch rows in U + { + pom2 = U[k, i]; + U[k, i] = U[k0, i]; + U[k0, i] = pom2; + } + + for (var i = k + 1; i < rows; i++) + { + L[i, k] = U[i, k]/U[k, k]; + for (var j = k; j < cols; j++) + U[i, j] = U[i, j] - L[i, k]*U[k, j]; + } + } + } + + + public XLMatrix SolveWith(XLMatrix v) // Function solves Ax = v in confirmity with solution vector "v" + { + if (rows != cols) throw new Exception("The matrix is not square!"); + if (rows != v.rows) throw new Exception("Wrong number of results in solution vector!"); + if (L == null) MakeLU(); + + var b = new XLMatrix(rows, 1); + for (var i = 0; i < rows; i++) b[i, 0] = v[pi[i], 0]; // switch two items in "v" due to permutation matrix + + var z = SubsForth(L, b); + var x = SubsBack(U, z); + + return x; + } + + public XLMatrix Invert() // Function returns the inverted matrix + { + if (L == null) MakeLU(); + + var inv = new XLMatrix(rows, cols); + + for (var i = 0; i < rows; i++) + { + var Ei = ZeroMatrix(rows, 1); + Ei[i, 0] = 1; + var col = SolveWith(Ei); + inv.SetCol(col, i); + } + return inv; + } + + + public double Determinant() // Function for determinant + { + if (L == null) MakeLU(); + var det = detOfP; + for (var i = 0; i < rows; i++) det *= U[i, i]; + return det; + } + + public XLMatrix GetP() // Function returns permutation matrix "P" due to permutation vector "pi" + { + if (L == null) MakeLU(); + + var matrix = ZeroMatrix(rows, cols); + for (var i = 0; i < rows; i++) matrix[pi[i], i] = 1; + return matrix; + } + + public XLMatrix Duplicate() // Function returns the copy of this matrix + { + var matrix = new XLMatrix(rows, cols); + for (var i = 0; i < rows; i++) + for (var j = 0; j < cols; j++) + matrix[i, j] = mat[i, j]; + return matrix; + } + + public static XLMatrix SubsForth(XLMatrix A, XLMatrix b) // Function solves Ax = b for A as a lower triangular matrix + { + if (A.L == null) A.MakeLU(); + var n = A.rows; + var x = new XLMatrix(n, 1); + + for (var i = 0; i < n; i++) + { + x[i, 0] = b[i, 0]; + for (var j = 0; j < i; j++) x[i, 0] -= A[i, j]*x[j, 0]; + x[i, 0] = x[i, 0]/A[i, i]; + } + return x; + } + + public static XLMatrix SubsBack(XLMatrix A, XLMatrix b) // Function solves Ax = b for A as an upper triangular matrix + { + if (A.L == null) A.MakeLU(); + var n = A.rows; + var x = new XLMatrix(n, 1); + + for (var i = n - 1; i > -1; i--) + { + x[i, 0] = b[i, 0]; + for (var j = n - 1; j > i; j--) x[i, 0] -= A[i, j]*x[j, 0]; + x[i, 0] = x[i, 0]/A[i, i]; + } + return x; + } + + public static XLMatrix ZeroMatrix(int iRows, int iCols) // Function generates the zero matrix + { + var matrix = new XLMatrix(iRows, iCols); + for (var i = 0; i < iRows; i++) + for (var j = 0; j < iCols; j++) + matrix[i, j] = 0; + return matrix; + } + + public static XLMatrix IdentityMatrix(int iRows, int iCols) // Function generates the identity matrix + { + var matrix = ZeroMatrix(iRows, iCols); + for (var i = 0; i < Math.Min(iRows, iCols); i++) + matrix[i, i] = 1; + return matrix; + } + + public static XLMatrix RandomMatrix(int iRows, int iCols, int dispersion) // Function generates the zero matrix + { + var random = new Random(); + var matrix = new XLMatrix(iRows, iCols); + for (var i = 0; i < iRows; i++) + for (var j = 0; j < iCols; j++) + matrix[i, j] = random.Next(-dispersion, dispersion); + return matrix; + } + + public static XLMatrix Parse(string ps) // Function parses the matrix from string + { + var s = NormalizeMatrixString(ps); + var rows = Regex.Split(s, "\r\n"); + var nums = rows[0].Split(' '); + var matrix = new XLMatrix(rows.Length, nums.Length); + try + { + for (var i = 0; i < rows.Length; i++) + { + nums = rows[i].Split(' '); + for (var j = 0; j < nums.Length; j++) matrix[i, j] = double.Parse(nums[j]); + } + } + catch (FormatException) + { + throw new Exception("Wrong input format!"); + } + return matrix; + } + + public override string ToString() // Function returns matrix as a string + { + var s = ""; + for (var i = 0; i < rows; i++) + { + for (var j = 0; j < cols; j++) s += String.Format("{0,5:0.00}", mat[i, j]) + " "; + s += "\r\n"; + } + return s; + } + + public static XLMatrix Transpose(XLMatrix m) // XLMatrix transpose, for any rectangular matrix + { + var t = new XLMatrix(m.cols, m.rows); + for (var i = 0; i < m.rows; i++) + for (var j = 0; j < m.cols; j++) + t[j, i] = m[i, j]; + return t; + } + + public static XLMatrix Power(XLMatrix m, int pow) // Power matrix to exponent + { + if (pow == 0) return IdentityMatrix(m.rows, m.cols); + if (pow == 1) return m.Duplicate(); + if (pow == -1) return m.Invert(); + + XLMatrix x; + if (pow < 0) + { + x = m.Invert(); + pow *= -1; + } + else x = m.Duplicate(); + + var ret = IdentityMatrix(m.rows, m.cols); + while (pow != 0) + { + if ((pow & 1) == 1) ret *= x; + x *= x; + pow >>= 1; + } + return ret; + } + + private static void SafeAplusBintoC(XLMatrix A, int xa, int ya, XLMatrix B, int xb, int yb, XLMatrix C, int size) + { + for (var i = 0; i < size; i++) // rows + for (var j = 0; j < size; j++) // cols + { + C[i, j] = 0; + if (xa + j < A.cols && ya + i < A.rows) C[i, j] += A[ya + i, xa + j]; + if (xb + j < B.cols && yb + i < B.rows) C[i, j] += B[yb + i, xb + j]; + } + } + + private static void SafeAminusBintoC(XLMatrix A, int xa, int ya, XLMatrix B, int xb, int yb, XLMatrix C, int size) + { + for (var i = 0; i < size; i++) // rows + for (var j = 0; j < size; j++) // cols + { + C[i, j] = 0; + if (xa + j < A.cols && ya + i < A.rows) C[i, j] += A[ya + i, xa + j]; + if (xb + j < B.cols && yb + i < B.rows) C[i, j] -= B[yb + i, xb + j]; + } + } + + private static void SafeACopytoC(XLMatrix A, int xa, int ya, XLMatrix C, int size) + { + for (var i = 0; i < size; i++) // rows + for (var j = 0; j < size; j++) // cols + { + C[i, j] = 0; + if (xa + j < A.cols && ya + i < A.rows) C[i, j] += A[ya + i, xa + j]; + } + } + + private static void AplusBintoC(XLMatrix A, int xa, int ya, XLMatrix B, int xb, int yb, XLMatrix C, int size) + { + for (var i = 0; i < size; i++) // rows + for (var j = 0; j < size; j++) C[i, j] = A[ya + i, xa + j] + B[yb + i, xb + j]; + } + + private static void AminusBintoC(XLMatrix A, int xa, int ya, XLMatrix B, int xb, int yb, XLMatrix C, int size) + { + for (var i = 0; i < size; i++) // rows + for (var j = 0; j < size; j++) C[i, j] = A[ya + i, xa + j] - B[yb + i, xb + j]; + } + + private static void ACopytoC(XLMatrix A, int xa, int ya, XLMatrix C, int size) + { + for (var i = 0; i < size; i++) // rows + for (var j = 0; j < size; j++) C[i, j] = A[ya + i, xa + j]; + } + + private static XLMatrix StrassenMultiply(XLMatrix A, XLMatrix B) // Smart matrix multiplication + { + if (A.cols != B.rows) throw new Exception("Wrong dimension of matrix!"); + + XLMatrix R; + + var msize = Math.Max(Math.Max(A.rows, A.cols), Math.Max(B.rows, B.cols)); + + if (msize < 32) + { + R = ZeroMatrix(A.rows, B.cols); + for (var i = 0; i < R.rows; i++) + for (var j = 0; j < R.cols; j++) + for (var k = 0; k < A.cols; k++) + R[i, j] += A[i, k]*B[k, j]; + return R; + } + + var size = 1; + var n = 0; + while (msize > size) + { + size *= 2; + n++; + } + + var h = size/2; + + + var mField = new XLMatrix[n,9]; + + /* + * 8x8, 8x8, 8x8, ... + * 4x4, 4x4, 4x4, ... + * 2x2, 2x2, 2x2, ... + * . . . + */ + + for (var i = 0; i < n - 4; i++) // rows + { + var z = (int) Math.Pow(2, n - i - 1); + for (var j = 0; j < 9; j++) mField[i, j] = new XLMatrix(z, z); + } + + SafeAplusBintoC(A, 0, 0, A, h, h, mField[0, 0], h); + SafeAplusBintoC(B, 0, 0, B, h, h, mField[0, 1], h); + StrassenMultiplyRun(mField[0, 0], mField[0, 1], mField[0, 1 + 1], 1, mField); // (A11 + A22) * (B11 + B22); + + SafeAplusBintoC(A, 0, h, A, h, h, mField[0, 0], h); + SafeACopytoC(B, 0, 0, mField[0, 1], h); + StrassenMultiplyRun(mField[0, 0], mField[0, 1], mField[0, 1 + 2], 1, mField); // (A21 + A22) * B11; + + SafeACopytoC(A, 0, 0, mField[0, 0], h); + SafeAminusBintoC(B, h, 0, B, h, h, mField[0, 1], h); + StrassenMultiplyRun(mField[0, 0], mField[0, 1], mField[0, 1 + 3], 1, mField); //A11 * (B12 - B22); + + SafeACopytoC(A, h, h, mField[0, 0], h); + SafeAminusBintoC(B, 0, h, B, 0, 0, mField[0, 1], h); + StrassenMultiplyRun(mField[0, 0], mField[0, 1], mField[0, 1 + 4], 1, mField); //A22 * (B21 - B11); + + SafeAplusBintoC(A, 0, 0, A, h, 0, mField[0, 0], h); + SafeACopytoC(B, h, h, mField[0, 1], h); + StrassenMultiplyRun(mField[0, 0], mField[0, 1], mField[0, 1 + 5], 1, mField); //(A11 + A12) * B22; + + SafeAminusBintoC(A, 0, h, A, 0, 0, mField[0, 0], h); + SafeAplusBintoC(B, 0, 0, B, h, 0, mField[0, 1], h); + StrassenMultiplyRun(mField[0, 0], mField[0, 1], mField[0, 1 + 6], 1, mField); //(A21 - A11) * (B11 + B12); + + SafeAminusBintoC(A, h, 0, A, h, h, mField[0, 0], h); + SafeAplusBintoC(B, 0, h, B, h, h, mField[0, 1], h); + StrassenMultiplyRun(mField[0, 0], mField[0, 1], mField[0, 1 + 7], 1, mField); // (A12 - A22) * (B21 + B22); + + R = new XLMatrix(A.rows, B.cols); // result + + // C11 + for (var i = 0; i < Math.Min(h, R.rows); i++) // rows + for (var j = 0; j < Math.Min(h, R.cols); j++) // cols + R[i, j] = mField[0, 1 + 1][i, j] + mField[0, 1 + 4][i, j] - mField[0, 1 + 5][i, j] + + mField[0, 1 + 7][i, j]; + + // C12 + for (var i = 0; i < Math.Min(h, R.rows); i++) // rows + for (var j = h; j < Math.Min(2*h, R.cols); j++) // cols + R[i, j] = mField[0, 1 + 3][i, j - h] + mField[0, 1 + 5][i, j - h]; + + // C21 + for (var i = h; i < Math.Min(2*h, R.rows); i++) // rows + for (var j = 0; j < Math.Min(h, R.cols); j++) // cols + R[i, j] = mField[0, 1 + 2][i - h, j] + mField[0, 1 + 4][i - h, j]; + + // C22 + for (var i = h; i < Math.Min(2*h, R.rows); i++) // rows + for (var j = h; j < Math.Min(2*h, R.cols); j++) // cols + R[i, j] = mField[0, 1 + 1][i - h, j - h] - mField[0, 1 + 2][i - h, j - h] + + mField[0, 1 + 3][i - h, j - h] + mField[0, 1 + 6][i - h, j - h]; + + return R; + } + + // function for square matrix 2^N x 2^N + + private static void StrassenMultiplyRun(XLMatrix A, XLMatrix B, XLMatrix C, int l, XLMatrix[,] f) + // A * B into C, level of recursion, matrix field + { + var size = A.rows; + var h = size/2; + + if (size < 32) + { + for (var i = 0; i < C.rows; i++) + for (var j = 0; j < C.cols; j++) + { + C[i, j] = 0; + for (var k = 0; k < A.cols; k++) C[i, j] += A[i, k]*B[k, j]; + } + return; + } + + AplusBintoC(A, 0, 0, A, h, h, f[l, 0], h); + AplusBintoC(B, 0, 0, B, h, h, f[l, 1], h); + StrassenMultiplyRun(f[l, 0], f[l, 1], f[l, 1 + 1], l + 1, f); // (A11 + A22) * (B11 + B22); + + AplusBintoC(A, 0, h, A, h, h, f[l, 0], h); + ACopytoC(B, 0, 0, f[l, 1], h); + StrassenMultiplyRun(f[l, 0], f[l, 1], f[l, 1 + 2], l + 1, f); // (A21 + A22) * B11; + + ACopytoC(A, 0, 0, f[l, 0], h); + AminusBintoC(B, h, 0, B, h, h, f[l, 1], h); + StrassenMultiplyRun(f[l, 0], f[l, 1], f[l, 1 + 3], l + 1, f); //A11 * (B12 - B22); + + ACopytoC(A, h, h, f[l, 0], h); + AminusBintoC(B, 0, h, B, 0, 0, f[l, 1], h); + StrassenMultiplyRun(f[l, 0], f[l, 1], f[l, 1 + 4], l + 1, f); //A22 * (B21 - B11); + + AplusBintoC(A, 0, 0, A, h, 0, f[l, 0], h); + ACopytoC(B, h, h, f[l, 1], h); + StrassenMultiplyRun(f[l, 0], f[l, 1], f[l, 1 + 5], l + 1, f); //(A11 + A12) * B22; + + AminusBintoC(A, 0, h, A, 0, 0, f[l, 0], h); + AplusBintoC(B, 0, 0, B, h, 0, f[l, 1], h); + StrassenMultiplyRun(f[l, 0], f[l, 1], f[l, 1 + 6], l + 1, f); //(A21 - A11) * (B11 + B12); + + AminusBintoC(A, h, 0, A, h, h, f[l, 0], h); + AplusBintoC(B, 0, h, B, h, h, f[l, 1], h); + StrassenMultiplyRun(f[l, 0], f[l, 1], f[l, 1 + 7], l + 1, f); // (A12 - A22) * (B21 + B22); + + // C11 + for (var i = 0; i < h; i++) // rows + for (var j = 0; j < h; j++) // cols + C[i, j] = f[l, 1 + 1][i, j] + f[l, 1 + 4][i, j] - f[l, 1 + 5][i, j] + f[l, 1 + 7][i, j]; + + // C12 + for (var i = 0; i < h; i++) // rows + for (var j = h; j < size; j++) // cols + C[i, j] = f[l, 1 + 3][i, j - h] + f[l, 1 + 5][i, j - h]; + + // C21 + for (var i = h; i < size; i++) // rows + for (var j = 0; j < h; j++) // cols + C[i, j] = f[l, 1 + 2][i - h, j] + f[l, 1 + 4][i - h, j]; + + // C22 + for (var i = h; i < size; i++) // rows + for (var j = h; j < size; j++) // cols + C[i, j] = f[l, 1 + 1][i - h, j - h] - f[l, 1 + 2][i - h, j - h] + f[l, 1 + 3][i - h, j - h] + + f[l, 1 + 6][i - h, j - h]; + } + + public static XLMatrix StupidMultiply(XLMatrix m1, XLMatrix m2) // Stupid matrix multiplication + { + if (m1.cols != m2.rows) throw new Exception("Wrong dimensions of matrix!"); + + var result = ZeroMatrix(m1.rows, m2.cols); + for (var i = 0; i < result.rows; i++) + for (var j = 0; j < result.cols; j++) + for (var k = 0; k < m1.cols; k++) + result[i, j] += m1[i, k]*m2[k, j]; + return result; + } + + private static XLMatrix Multiply(double n, XLMatrix m) // Multiplication by constant n + { + var r = new XLMatrix(m.rows, m.cols); + for (var i = 0; i < m.rows; i++) + for (var j = 0; j < m.cols; j++) + r[i, j] = m[i, j]*n; + return r; + } + + private static XLMatrix Add(XLMatrix m1, XLMatrix m2) + { + if (m1.rows != m2.rows || m1.cols != m2.cols) + throw new Exception("Matrices must have the same dimensions!"); + var r = new XLMatrix(m1.rows, m1.cols); + for (var i = 0; i < r.rows; i++) + for (var j = 0; j < r.cols; j++) + r[i, j] = m1[i, j] + m2[i, j]; + return r; + } + + public static string NormalizeMatrixString(string matStr) // From Andy - thank you! :) + { + // Remove any multiple spaces + while (matStr.IndexOf(" ") != -1) + matStr = matStr.Replace(" ", " "); + + // Remove any spaces before or after newlines + matStr = matStr.Replace(" \r\n", "\r\n"); + matStr = matStr.Replace("\r\n ", "\r\n"); + + // If the data ends in a newline, remove the trailing newline. + // Make it easier by first replacing \r\n’s with |’s then + // restore the |’s with \r\n’s + matStr = matStr.Replace("\r\n", "|"); + while (matStr.LastIndexOf("|") == (matStr.Length - 1)) + matStr = matStr.Substring(0, matStr.Length - 1); + + matStr = matStr.Replace("|", "\r\n"); + return matStr; + } + + // O P E R A T O R S + + public static XLMatrix operator -(XLMatrix m) + { + return Multiply(-1, m); + } + + public static XLMatrix operator +(XLMatrix m1, XLMatrix m2) + { + return Add(m1, m2); + } + + public static XLMatrix operator -(XLMatrix m1, XLMatrix m2) + { + return Add(m1, -m2); + } + + public static XLMatrix operator *(XLMatrix m1, XLMatrix m2) + { + return StrassenMultiply(m1, m2); + } + + public static XLMatrix operator *(double n, XLMatrix m) + { + return Multiply(n, m); + } + } +} \ No newline at end of file diff --git a/ClosedXML/ClosedXML/ClosedXML/Excel/Cells/XLCell.cs b/ClosedXML/ClosedXML/ClosedXML/Excel/Cells/XLCell.cs index 88c9944..5a3d424 100644 --- a/ClosedXML/ClosedXML/ClosedXML/Excel/Cells/XLCell.cs +++ b/ClosedXML/ClosedXML/ClosedXML/Excel/Cells/XLCell.cs @@ -373,7 +373,7 @@ var retVal = Worksheet.Evaluate(fA1); var retValEnumerable = retVal as IEnumerable; - if (retValEnumerable != null) + if (retValEnumerable != null && !(retVal is String)) foreach (var v in retValEnumerable) return v; diff --git a/ClosedXML/ClosedXML/ClosedXML/ExcelHelper.cs b/ClosedXML/ClosedXML/ClosedXML/ExcelHelper.cs index 31a6f9e..d0b702a 100644 --- a/ClosedXML/ClosedXML/ClosedXML/ExcelHelper.cs +++ b/ClosedXML/ClosedXML/ClosedXML/ExcelHelper.cs @@ -274,5 +274,10 @@ var newVal = A1RegexRelative.Replace(oldValue, m => Evaluator(m, row, column)); return newVal.Substring(1, newVal.Length - 2); } + + public static Boolean AreEqual(Double d1, Double d2) + { + return Math.Abs(d1 - d2) < Epsilon; + } } } \ No newline at end of file diff --git a/ClosedXML/ClosedXML/ClosedXML_Tests/Excel/CalcEngine/FunctionsTests.cs b/ClosedXML/ClosedXML/ClosedXML_Tests/Excel/CalcEngine/FunctionsTests.cs index 0edfbda..3d96791 100644 --- a/ClosedXML/ClosedXML/ClosedXML_Tests/Excel/CalcEngine/FunctionsTests.cs +++ b/ClosedXML/ClosedXML/ClosedXML_Tests/Excel/CalcEngine/FunctionsTests.cs @@ -497,20 +497,89 @@ Object actual; - // ws.Cell("A5").FormulaA1 = "MMult(A1:B2, A3:B4)"; - //actual = ws.Cell("A5").Value; + ws.Cell("A5").FormulaA1 = "MMult(A1:B2, A3:B4)"; + actual = ws.Cell("A5").Value; - //Assert.AreEqual(4.0, actual); + Assert.AreEqual(16.0, actual); - //ws.Cell("A6").FormulaA1 = "Sum(A5)"; - //actual = ws.Cell("A6").Value; + ws.Cell("A6").FormulaA1 = "Sum(A5)"; + actual = ws.Cell("A6").Value; - //Assert.AreEqual(4.0, actual); + Assert.AreEqual(16.0, actual); ws.Cell("A7").FormulaA1 = "Sum(MMult(A1:B2, A3:B4))"; actual = ws.Cell("A7").Value; Assert.AreEqual(102.0, actual); } + + [TestMethod] + public void MDetem() + { + var ws = new XLWorkbook().AddWorksheet("Sheet1"); + ws.Cell("A1").SetValue(2).CellRight().SetValue(4); + ws.Cell("A2").SetValue(3).CellRight().SetValue(5); + + + Object actual; + + ws.Cell("A5").FormulaA1 = "MDeterm(A1:B2)"; + actual = ws.Cell("A5").Value; + + Assert.IsTrue(XLHelper.AreEqual(-2.0, (double)actual)); + + ws.Cell("A6").FormulaA1 = "Sum(A5)"; + actual = ws.Cell("A6").Value; + + Assert.IsTrue(XLHelper.AreEqual(-2.0, (double)actual)); + + ws.Cell("A7").FormulaA1 = "Sum(MDeterm(A1:B2))"; + actual = ws.Cell("A7").Value; + + Assert.IsTrue(XLHelper.AreEqual(-2.0, (double)actual)); + } + + [TestMethod] + public void MInverse() + { + var ws = new XLWorkbook().AddWorksheet("Sheet1"); + ws.Cell("A1").SetValue(1).CellRight().SetValue(2).CellRight().SetValue(1); + ws.Cell("A2").SetValue(3).CellRight().SetValue(4).CellRight().SetValue(-1); + ws.Cell("A3").SetValue(0).CellRight().SetValue(2).CellRight().SetValue(0); + + + Object actual; + + ws.Cell("A5").FormulaA1 = "MInverse(A1:C3)"; + actual = ws.Cell("A5").Value; + + Assert.IsTrue(XLHelper.AreEqual(0.25, (double)actual)); + + ws.Cell("A6").FormulaA1 = "Sum(A5)"; + actual = ws.Cell("A6").Value; + + Assert.IsTrue(XLHelper.AreEqual(0.25, (double)actual)); + + ws.Cell("A7").FormulaA1 = "Sum(MInverse(A1:C3))"; + actual = ws.Cell("A7").Value; + + Assert.IsTrue(XLHelper.AreEqual(0.5, (double)actual)); + } + + [TestMethod] + public void TextConcat() + { + var wb = new XLWorkbook(); + var ws = wb.AddWorksheet("Sheet1"); + ws.Cell("A1").Value = 1; + ws.Cell("A2").Value = 1; + ws.Cell("B1").Value = 1; + ws.Cell("B2").Value = 1; + + ws.Cell("C1").FormulaA1 = "\"The total value is: \" & SUM(A1:B2)"; + + var r = ws.Cell("C1").Value; + Assert.AreEqual("The total value is: 4", r.ToString()); + } } }