diff --git a/sheets/PointStorage.h b/sheets/PointStorage.h index 386d5ac268f..cb994cb7a58 100644 --- a/sheets/PointStorage.h +++ b/sheets/PointStorage.h @@ -1,882 +1,882 @@ /* This file is part of the KDE project Copyright 2007 Stefan Nikolaus This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef CALLIGRA_SHEETS_POINT_STORAGE #define CALLIGRA_SHEETS_POINT_STORAGE #include #include #include #include "Region.h" #include "calligra_sheets_limits.h" #include // #define KSPREAD_POINT_STORAGE_HASH namespace Calligra { namespace Sheets { /** * \ingroup Storage * A custom pointwise storage. * Based on a compressed sparse matrix data structure. * Usable for any kind of data attached to 2D coordinates. * * Only non-default data with its coordinate is stored. Hence, the storage * has a small memory footprint nearly regardless of the data's location. * Each empty row before a location occupy an integer, which is not the case * for columns. Iterating over the data becomes fast compared to dense * matrix/array, where each location has to be traversed irrespective of * default or non-default data. * * The actual data is stored in the list m_data. It is grouped by rows in * ascending order. The rows' beginnings and ends are stored in the list * m_rows. Its index corresponds to the row index. The values denote the * starting index of a row in m_data. The row's end is determined by * the starting position of the next row. The entries in each row are ordered * by column. The corresponding column indices are stored in m_cols. Hence, * m_cols has the same amount of entries as m_data. * * \author Stefan Nikolaus * * \note If you fill the storage, do it row-wise. That's more performant. * \note For data assigned to rectangular regions use RectStorage. * \note It's QVector based. To boost performance a lot, declare the stored * data type as movable. */ template class PointStorage { friend class PointStorageBenchmark; friend class PointStorageTest; public: /** * Constructor. * Creates an empty storage. Actually, does nothing. */ PointStorage() {} /** * Destructor. */ ~PointStorage() {} /** * Clears the storage. */ void clear() { m_cols.clear(); m_rows.clear(); m_data.clear(); } /** * Returns the number of items in the storage. * Usable to iterate over all non-default data. * \return number of items * \see col() * \see row() * \see data() */ int count() const { return m_data.count(); } /** * Inserts \p data at \p col , \p row . * \return the overridden data (default data, if no overwrite) */ T insert(int col, int row, const T& data) { Q_ASSERT(1 <= col && col <= KS_colMax); Q_ASSERT(1 <= row && row <= KS_rowMax); // row's missing? if (row > m_rows.count()) { // insert missing rows m_rows.insert(m_rows.count(), row - m_rows.count(), m_data.count()); // append the actual data #ifdef KSPREAD_POINT_STORAGE_HASH m_data.append(*m_usedData.insert(data)); #else m_data.append(data); #endif // append the column index m_cols.append(col); } // the row exists else { const QVector::const_iterator cstart(m_cols.begin() + m_rows.value(row - 1)); const QVector::const_iterator cend((row < m_rows.count()) ? (m_cols.begin() + m_rows.value(row)) : m_cols.end()); const QVector::const_iterator cit = std::lower_bound(cstart, cend, col); // column's missing? if (cit == cend || *cit != col) { // determine the index where the data and column has to be inserted const int index = m_rows.value(row - 1) + (cit - cstart); // insert the actual data #ifdef KSPREAD_POINT_STORAGE_HASH m_data.insert(index, *m_usedData.insert(data)); #else m_data.insert(index, data); #endif // insert the column index m_cols.insert(index, col); // adjust the offsets of the following rows for (int r = row; r < m_rows.count(); ++r) ++m_rows[r]; } // column exists else { const int index = m_rows.value(row - 1) + (cit - cstart); const T oldData = m_data[ index ]; #ifdef KSPREAD_POINT_STORAGE_HASH m_data[ index ] = *m_usedData.insert(data); #else m_data[ index ] = data; #endif return oldData; } } squeezeRows(); return T(); } /** * Looks up the data at \p col , \p row . If no data was found returns a * default object. * \return the data at the given coordinate */ T lookup(int col, int row, const T& defaultVal = T()) const { Q_ASSERT(1 <= col && col <= KS_colMax); Q_ASSERT(1 <= row && row <= KS_rowMax); // is the row not present? if (row > m_rows.count()) return defaultVal; const QVector::const_iterator cstart(m_cols.begin() + m_rows.value(row - 1)); const QVector::const_iterator cend((row < m_rows.count()) ? (m_cols.begin() + m_rows.value(row)) : m_cols.end()); const QVector::const_iterator cit = std::lower_bound(cstart, cend, col); // is the col not present? if (cit == cend) return defaultVal; return m_data.value(m_rows.value(row - 1) + (cit - cstart)); } /** * Removes data at \p col , \p row . * \return the removed data (default data, if none) */ T take(int col, int row, const T& defaultVal = T()) { Q_ASSERT(1 <= col && col <= KS_colMax); Q_ASSERT(1 <= row && row <= KS_rowMax); // row's missing? if (row > m_rows.count()) return defaultVal; const int rowStart = (row - 1 < m_rows.count()) ? m_rows.value(row - 1) : m_data.count(); const int rowLength = (row < m_rows.count()) ? m_rows.value(row) - rowStart : -1; const QVector cols = m_cols.mid(rowStart, rowLength); QVector::const_iterator cit = std::lower_bound(cols.begin(), cols.end(), col); // column's missing? if (cit == cols.constEnd()) return defaultVal; const int index = rowStart + (cit - cols.constBegin()); // save the old data const T oldData = m_data[ index ]; // remove the actual data m_data.remove(index); // remove the column index m_cols.remove(index); // adjust the offsets of the following rows for (int r = row; r < m_rows.count(); ++r) --m_rows[r]; squeezeRows(); return oldData; } /** * Insert \p number columns at \p position . * \return the data, that became out of range (shifted over the end) */ QVector< QPair > insertColumns(int position, int number) { Q_ASSERT(1 <= position && position <= KS_colMax); QVector< QPair > oldData; for (int row = m_rows.count(); row >= 1; --row) { const int rowStart = m_rows.value(row - 1); const int rowLength = (row < m_rows.count()) ? m_rows.value(row) - rowStart : -1; const QVector cols = m_cols.mid(rowStart, rowLength); for (int col = cols.count(); col >= 0; --col) { if (cols.value(col) + number > KS_colMax) { oldData.append(qMakePair(QPoint(cols.value(col), row), m_data.value(rowStart + col))); m_cols.remove(rowStart + col); m_data.remove(rowStart + col); // adjust the offsets of the following rows for (int r = row; r < m_rows.count(); ++r) --m_rows[r]; } else if (cols.value(col) >= position) m_cols[rowStart + col] += number; } } squeezeRows(); return oldData; } /** * Removes \p number columns at \p position . * \return the removed data */ QVector< QPair > removeColumns(int position, int number) { Q_ASSERT(1 <= position && position <= KS_colMax); QVector< QPair > oldData; for (int row = m_rows.count(); row >= 1; --row) { const int rowStart = m_rows.value(row - 1); const int rowLength = (row < m_rows.count()) ? m_rows.value(row) - rowStart : -1; const QVector cols = m_cols.mid(rowStart, rowLength); for (int col = cols.count() - 1; col >= 0; --col) { if (cols.value(col) >= position) { if (cols.value(col) < position + number) { oldData.append(qMakePair(QPoint(cols.value(col), row), m_data.value(rowStart + col))); m_cols.remove(rowStart + col); m_data.remove(rowStart + col); for (int r = row; r < m_rows.count(); ++r) --m_rows[r]; } else m_cols[rowStart + col] -= number; } } } squeezeRows(); return oldData; } /** * Insert \p number rows at \p position . * \return the data, that became out of range (shifted over the end) */ QVector< QPair > insertRows(int position, int number) { Q_ASSERT(1 <= position && position <= KS_rowMax); // row's missing? if (position > m_rows.count()) return QVector< QPair >(); QVector< QPair > oldData; int dataCount = 0; int rowCount = 0; // save the old data for (int row = KS_rowMax - number + 1; row <= m_rows.count() && row <= KS_rowMax; ++row) { const QVector::const_iterator cstart(m_cols.begin() + m_rows.value(row - 1)); const QVector::const_iterator cend((row < m_rows.count()) ? (m_cols.begin() + m_rows.value(row)) : m_cols.end()); for (QVector::const_iterator cit = cstart; cit != cend; ++cit) oldData.append(qMakePair(QPoint(*cit, row), m_data.value(cit - m_cols.constBegin()))); dataCount += (cend - cstart); ++rowCount; } // remove the out of range data while (dataCount-- > 0) { m_data.remove(m_data.count() - 1); m_cols.remove(m_cols.count() - 1); } while (rowCount-- > 0) m_rows.remove(m_rows.count() - 1); // insert the new rows const int index = m_rows.value(position - 1); for (int r = 0; r < number; ++r) m_rows.insert(position, index); squeezeRows(); return oldData; } /** * Removes \p number rows at \p position . * \return the removed data */ QVector< QPair > removeRows(int position, int number) { Q_ASSERT(1 <= position && position <= KS_rowMax); // row's missing? if (position > m_rows.count()) return QVector< QPair >(); QVector< QPair > oldData; int dataCount = 0; int rowCount = 0; // save the old data for (int row = position; row <= m_rows.count() && row <= position + number - 1; ++row) { const int rowStart = m_rows.value(row - 1); const int rowLength = (row < m_rows.count()) ? m_rows.value(row) - rowStart : -1; const QVector cols = m_cols.mid(rowStart, rowLength); const QVector data = m_data.mid(rowStart, rowLength); for (int col = 0; col < cols.count(); ++col) oldData.append(qMakePair(QPoint(cols.value(col), row), data.value(col))); dataCount += data.count(); ++rowCount; } // adjust the offsets of the following rows for (int r = position + number - 1; r < m_rows.count(); ++r) m_rows[r] -= dataCount; // remove the out of range data while (dataCount-- > 0) { m_data.remove(m_rows.value(position - 1)); m_cols.remove(m_rows.value(position - 1)); } while (rowCount-- > 0) m_rows.remove(position - 1); squeezeRows(); return oldData; } /** * Shifts the data right of \p rect to the left by the width of \p rect . * The data formerly contained in \p rect becomes overridden. * \return the removed data */ QVector< QPair > removeShiftLeft(const QRect& rect) { Q_ASSERT(1 <= rect.left() && rect.left() <= KS_colMax); QVector< QPair > oldData; for (int row = qMin(rect.bottom(), m_rows.count()); row >= rect.top(); --row) { const int rowStart = m_rows.value(row - 1); const int rowLength = (row < m_rows.count()) ? m_rows.value(row) - rowStart : -1; const QVector cols = m_cols.mid(rowStart, rowLength); for (int col = cols.count() - 1; col >= 0; --col) { if (cols.value(col) >= rect.left()) { if (cols.value(col) <= rect.right()) { oldData.append(qMakePair(QPoint(cols.value(col), row), m_data.value(rowStart + col))); m_cols.remove(rowStart + col); m_data.remove(rowStart + col); for (int r = row; r < m_rows.count(); ++r) --m_rows[r]; } else m_cols[rowStart + col] -= rect.width(); } } } squeezeRows(); return oldData; } /** * Shifts the data in and right of \p rect to the right by the width of \p rect . * \return the data, that became out of range (shifted over the end) */ QVector< QPair > insertShiftRight(const QRect& rect) { Q_ASSERT(1 <= rect.left() && rect.left() <= KS_colMax); QVector< QPair > oldData; for (int row = rect.top(); row <= rect.bottom() && row <= m_rows.count(); ++row) { const int rowStart = m_rows.value(row - 1); const int rowLength = (row < m_rows.count()) ? m_rows.value(row) - rowStart : -1; const QVector cols = m_cols.mid(rowStart, rowLength); for (int col = cols.count(); col >= 0; --col) { if (cols.value(col) + rect.width() > KS_colMax) { oldData.append(qMakePair(QPoint(cols.value(col), row), m_data.value(rowStart + col))); m_cols.remove(rowStart + col); m_data.remove(rowStart + col); // adjust the offsets of the following rows for (int r = row; r < m_rows.count(); ++r) --m_rows[r]; } else if (cols.value(col) >= rect.left()) m_cols[rowStart + col] += rect.width(); } } squeezeRows(); return oldData; } /** * Shifts the data below \p rect to the top by the height of \p rect . * The data formerly contained in \p rect becomes overridden. * \return the removed data */ QVector< QPair > removeShiftUp(const QRect& rect) { Q_ASSERT(1 <= rect.top() && rect.top() <= KS_rowMax); // row's missing? if (rect.top() > m_rows.count()) return QVector< QPair >(); QVector< QPair > oldData; for (int row = rect.top(); row <= m_rows.count() && row <= KS_rowMax - rect.height(); ++row) { const int rowStart = m_rows.value(row - 1); const int rowLength = (row < m_rows.count()) ? m_rows.value(row) - rowStart : -1; const QVector cols = m_cols.mid(rowStart, rowLength); const QVector data = m_data.mid(rowStart, rowLength); // first, iterate over the destination row for (int col = cols.count() - 1; col >= 0; --col) { const int column = cols.value(col); // real column value (1...KS_colMax) if (column >= rect.left() && column <= rect.right()) { // save the old data if (row <= rect.bottom()) oldData.append(qMakePair(QPoint(column, row), data.value(col))); // search const int srcRow = row + rect.height(); const QVector::const_iterator cstart2((srcRow - 1 < m_rows.count()) ? m_cols.begin() + m_rows.value(srcRow - 1) : m_cols.end()); const QVector::const_iterator cend2((srcRow < m_rows.count()) ? (m_cols.begin() + m_rows.value(srcRow)) : m_cols.end()); const QVector::const_iterator cit2 = std::lower_bound(cstart2, cend2, column); // column's missing? if (cit2 == cend2) { m_cols.remove(rowStart + col); m_data.remove(rowStart + col); // adjust the offsets of the following rows for (int r = row; r < m_rows.count(); ++r) --m_rows[r]; } // column exists else { // copy m_data[rowStart + col] = m_data.value(cit2 - m_cols.constBegin()); // remove m_cols.remove(cit2 - m_cols.constBegin()); m_data.remove(cit2 - m_cols.constBegin()); // adjust the offsets of the following rows for (int r = row + rect.height(); r < m_rows.count(); ++r) --m_rows[r]; } } } // last, iterate over the source row const int srcRow = row + rect.height(); const int rowStart2 = (srcRow - 1 < m_rows.count()) ? m_rows.value(srcRow - 1) : m_data.count(); const int rowLength2 = (srcRow < m_rows.count()) ? m_rows.value(srcRow) - rowStart2 : -1; const QVector cols2 = m_cols.mid(rowStart2, rowLength2); const QVector data2 = m_data.mid(rowStart2, rowLength2); int offset = 0; for (int col = cols2.count() - 1; col >= 0; --col) { const int column = cols2.value(col); // real column value (1...KS_colMax) if (column >= rect.left() && column <= rect.right()) { // find the insertion position const QVector::const_iterator cstart((row - 1 < m_rows.count()) ? m_cols.begin() + m_rows.value(row - 1) : m_cols.end()); const QVector::const_iterator cend(((row < m_rows.count())) ? (m_cols.begin() + m_rows.value(row)) : m_cols.end()); - const QVector::const_iterator cit = qUpperBound(cstart, cend, cols2.value(col)); + const QVector::const_iterator cit = std::upper_bound(cstart, cend, cols2.value(col)); // Destination column: const QVector::const_iterator dstcit = std::lower_bound(cols.begin(), cols.end(), column); if (dstcit != cols.end()) { // destination column exists // replace the existing destination value const int dstCol = (dstcit - cols.constBegin()); m_data[rowStart + dstCol] = m_data.value(rowStart2 + col); // remove it from its old position m_data.remove(rowStart2 + col + 1); m_cols.remove(rowStart2 + col + 1); // The amount of values in the range from the // destination row to the source row have not changed. // adjust the offsets of the following rows for (int r = srcRow; r < m_rows.count(); ++r) { ++m_rows[r]; } } else { // destination column does not exist yet // copy it to its new position const int dstCol = cit - m_cols.constBegin(); m_data.insert(dstCol, data2.value(col)); m_cols.insert(dstCol, cols2.value(col)); // remove it from its old position m_data.remove(rowStart2 + col + 1 + offset); m_cols.remove(rowStart2 + col + 1 + offset); ++offset; // adjust the offsets of the following rows for (int r = row; r < srcRow; ++r) { ++m_rows[r]; } } } } } squeezeRows(); return oldData; } /** * Shifts the data in and below \p rect to the bottom by the height of \p rect . * \return the data, that became out of range (shifted over the end) */ QVector< QPair > insertShiftDown(const QRect& rect) { Q_ASSERT(1 <= rect.top() && rect.top() <= KS_rowMax); // row's missing? if (rect.top() > m_rows.count()) return QVector< QPair >(); QVector< QPair > oldData; for (int row = m_rows.count(); row >= rect.top(); --row) { const int rowStart = m_rows.value(row - 1); const int rowLength = (row < m_rows.count()) ? m_rows.value(row) - rowStart : -1; const QVector cols = m_cols.mid(rowStart, rowLength); const QVector data = m_data.mid(rowStart, rowLength); for (int col = cols.count() - 1; col >= 0; --col) { if (cols.value(col) >= rect.left() && cols.value(col) <= rect.right()) { if (row + rect.height() > KS_rowMax) { // save old data oldData.append(qMakePair(QPoint(cols.value(col), row), data.value(col))); } else { // insert missing rows if (row + rect.height() > m_rows.count()) m_rows.insert(m_rows.count(), row + rect.height() - m_rows.count(), m_data.count()); // copy the data down const int row2 = row + rect.height(); const QVector::const_iterator cstart2(m_cols.begin() + m_rows.value(row2 - 1)); const QVector::const_iterator cend2((row2 < m_rows.count()) ? (m_cols.begin() + m_rows.value(row2)) : m_cols.end()); const QVector::const_iterator cit2 = std::lower_bound(cstart2, cend2, cols.value(col)); // column's missing? if (cit2 == cend2 || *cit2 != cols.value(col)) { // determine the index where the data and column has to be inserted const int index = m_rows.value(row2 - 1) + (cit2 - cstart2); // insert the actual data m_data.insert(index, data.value(col)); // insert the column index m_cols.insert(index, cols.value(col)); // adjust the offsets of the following rows for (int r = row2; r < m_rows.count(); ++r) ++m_rows[r]; } // column exists else { const int index = m_rows.value(row2 - 1) + (cit2 - cstart2); m_data[ index ] = data.value(col); } } // remove the data m_cols.remove(rowStart + col); m_data.remove(rowStart + col); // adjust the offsets of the following rows for (int r = row; r < m_rows.count(); ++r) --m_rows[r]; } } } squeezeRows(); return oldData; } /** * Retrieve the first used data in \p col . * Can be used in conjunction with nextInColumn() to loop through a column. * \return the first used data in \p col or the default data, if the column is empty. */ T firstInColumn(int col, int* newRow = 0) const { Q_ASSERT(1 <= col && col <= KS_colMax); const int index = m_cols.indexOf(col); if (newRow) { if (index == -1) // not found *newRow = 0; else - *newRow = qUpperBound(m_rows, index) - m_rows.begin(); + *newRow = std::upper_bound(m_rows.begin(), m_rows.end(), index) - m_rows.begin(); } return m_data.value(index); } /** * Retrieve the first used data in \p row . * Can be used in conjunction with nextInRow() to loop through a row. * \return the first used data in \p row or the default data, if the row is empty. */ T firstInRow(int row, int* newCol = 0) const { Q_ASSERT(1 <= row && row <= KS_rowMax); // row's empty? if (row > m_rows.count() || ((row < m_rows.count()) && m_rows.value(row - 1) == m_rows.value(row))) { if (newCol) *newCol = 0; return T(); } if (newCol) *newCol = m_cols.value(m_rows.value(row - 1)); return m_data.value(m_rows.value(row - 1)); } /** * Retrieve the last used data in \p col . * Can be used in conjunction with prevInColumn() to loop through a column. * \return the last used data in \p col or the default data, if the column is empty. */ T lastInColumn(int col, int* newRow = 0) const { Q_ASSERT(1 <= col && col <= KS_colMax); const int index = m_cols.lastIndexOf(col); if (newRow) { if (index == -1) // not found *newRow = 0; else - *newRow = qUpperBound(m_rows, index) - m_rows.begin(); + *newRow = std::upper_bound(m_rows.begin(), m_rows.end(), index) - m_rows.begin(); } return m_data.value(index); } /** * Retrieve the last used data in \p row . * Can be used in conjunction with prevInRow() to loop through a row. * \return the last used data in \p row or the default data, if the row is empty. */ T lastInRow(int row, int* newCol = 0) const { Q_ASSERT(1 <= row && row <= KS_rowMax); // row's empty? if (m_rows.value(row - 1) == m_rows.value(row) || m_rows.value(row - 1) == m_data.count()) { if (newCol) *newCol = 0; return T(); } // last row ends on data vector end if (row == m_rows.count()) { if (newCol) *newCol = m_cols.value(m_data.count() - 1); return m_data.value(m_data.count() - 1); } if (newCol) *newCol = m_cols.value(m_rows.value(row) - 1); return m_data.value(m_rows.value(row) - 1); } /** * Retrieve the next used data in \p col after \p row . * Can be used in conjunction with firstInColumn() to loop through a column. * \return the next used data in \p col or the default data, there is no further data. */ T nextInColumn(int col, int row, int* newRow = 0) const { Q_ASSERT(1 <= col && col <= KS_colMax); Q_ASSERT(1 <= row && row <= KS_rowMax); // no next row? if (row + 1 > m_rows.count()) { if (newRow) *newRow = 0; return T(); } // search beginning in rows after the specified row const int index = m_cols.indexOf(col, m_rows.value(row)); if (newRow) { if (index == -1) // not found *newRow = 0; else - *newRow = qUpperBound(m_rows, index) - m_rows.begin(); + *newRow = std::upper_bound(m_rows.begin(), m_rows.end(), index) - m_rows.begin(); } return m_data.value(index); } /** * Retrieve the next used data in \p row after \p col . * Can be used in conjunction with firstInRow() to loop through a row. * \return the next used data in \p row or the default data, if there is no further data. */ T nextInRow(int col, int row, int* newCol = 0) const { Q_ASSERT(1 <= col && col <= KS_colMax); Q_ASSERT(1 <= row && row <= KS_rowMax); // is the row not present? if (row > m_rows.count()) { if (newCol) *newCol = 0; return T(); } const QVector::const_iterator cstart(m_cols.begin() + m_rows.value(row - 1)); const QVector::const_iterator cend((row < m_rows.count()) ? (m_cols.begin() + m_rows.value(row)) : m_cols.end()); - const QVector::const_iterator cit = qUpperBound(cstart, cend, col); + const QVector::const_iterator cit = std::upper_bound(cstart, cend, col); if (cit == cend || *cit <= col) { if (newCol) *newCol = 0; return T(); } if (newCol) *newCol = m_cols.value(m_rows.value(row - 1) + (cit - cstart)); return m_data.value(m_rows.value(row - 1) + (cit - cstart)); } /** * Retrieve the previous used data in \p col after \p row . * Can be used in conjunction with lastInColumn() to loop through a column. * \return the previous used data in \p col or the default data, there is no further data. */ T prevInColumn(int col, int row, int* newRow = 0) const { Q_ASSERT(1 <= col && col <= KS_colMax); Q_ASSERT(1 <= row && row <= KS_rowMax); // first row? if (row <= m_rows.count() && m_rows.value(row - 1) == 0) { if (newRow) *newRow = 0; return T(); } const int index = m_cols.lastIndexOf(col, m_rows.value(row - 1) - 1); if (newRow) { if (index == -1) // not found *newRow = 0; else - *newRow = qUpperBound(m_rows, index) - m_rows.begin(); + *newRow = std::upper_bound(m_rows.begin(), m_rows.end(), index) - m_rows.begin(); } return m_data.value(index); } /** * Retrieve the previous used data in \p row after \p col . * Can be used in conjunction with lastInRow() to loop through a row. * \return the previous used data in \p row or the default data, if there is no further data. */ T prevInRow(int col, int row, int* newCol = 0) const { Q_ASSERT(1 <= col && col <= KS_colMax); Q_ASSERT(1 <= row && row <= KS_rowMax); const QVector::const_iterator cstart((row - 1 < m_rows.count()) ? m_cols.begin() + m_rows.value(row - 1) : m_cols.end()); const QVector::const_iterator cend((row < m_rows.count()) ? (m_cols.begin() + m_rows.value(row)) : m_cols.end()); const QVector::const_iterator cit = std::lower_bound(cstart, cend, col); if (cit == cstart) { if (newCol) *newCol = 0; return T(); } if (newCol) *newCol = m_cols.value(cit - 1 - m_cols.begin()); return m_data.value(cit - 1 - m_cols.begin()); } /** * For debugging/testing purposes. * \note only works with primitive/printable data */ QString dump() const { QString str; // determine the dimension of the matrix (the missing column number) int maxCols = 0; for (int row = 0; row < m_rows.count(); ++row) { const int rowStart = m_rows.value(row); const int rowLength = (row + 1 < m_rows.count()) ? m_rows.value(row + 1) - rowStart : -1; const QVector cols = m_cols.mid(rowStart, rowLength); maxCols = qMax(maxCols, cols.value(cols.count() - 1)); } for (int row = 0; row < m_rows.count(); ++row) { str += '('; const int rowStart = m_rows.value(row); const int rowLength = (row + 1 < m_rows.count()) ? m_rows.value(row + 1) - rowStart : -1; const QVector cols = m_cols.mid(rowStart, rowLength); const QVector data = m_data.mid(rowStart, rowLength); int lastCol = 0; for (int col = 0; col < cols.count(); ++col) { int counter = cols.value(col) - lastCol; while (counter-- > 1) str += " ,"; str += QString("%1,").arg(data.value(col), 2); // str += QString( "%1," ).arg( (data.value( col ) == T()) ? "" : "_", 2 ); lastCol = cols.value(col); } // fill the column up to the max int counter = maxCols - lastCol; while (counter-- > 0) str += " ,"; // replace the last comma str[str.length()-1] = ')'; str += '\n'; } return str.isEmpty() ? QString("()") : str.mid(0, str.length() - 1); } /** * Returns the column of the non-default data at \p index . * \return the data's column at \p index . * \see count() * \see row() * \see data() */ int col(int index) const { return m_cols.value(index); } /** * Returns the row of the non-default data at \p index . * \return the data's row at \p index . * \see count() * \see col() * \see data() */ int row(int index) const { - return qUpperBound(m_rows, index) - m_rows.begin(); + return std::upper_bound(m_rows.begin(), m_rows.end(), index) - m_rows.begin(); } /** * Returns the non-default data at \p index . * \return the data at \p index . * \see count() * \see col() * \see row() */ T data(int index) const { return m_data.value(index); } /** * The maximum occupied column, i.e. the horizontal storage dimension. * \return the maximum column */ int columns() const { int columns = 0; for (int c = 0; c < m_cols.count(); ++c) columns = qMax(m_cols.value(c), columns); return columns; } /** * The maximum occupied row, i.e. the vertical storage dimension. * \return the maximum row */ int rows() const { return m_rows.count(); } /** * Creates a substorage consisting of the values in \p region. * If \p keepOffset is \c true, the values' positions are not altered. * Otherwise, the upper left of \p region's bounding rect is used as new origin, * and all positions are adjusted. * \return a subset of the storage stripped down to the values in \p region */ PointStorage subStorage(const Region& region, bool keepOffset = true) const { // Determine the offset. const QPoint offset = keepOffset ? QPoint(0, 0) : region.boundingRect().topLeft() - QPoint(1, 1); // this generates an array of values PointStorage subStorage; Region::ConstIterator end(region.constEnd()); for (Region::ConstIterator it(region.constBegin()); it != end; ++it) { const QRect rect = (*it)->rect(); for (int row = rect.top(); row <= rect.bottom() && row <= m_rows.count(); ++row) { const QVector::const_iterator cstart(m_cols.begin() + m_rows.value(row - 1)); const QVector::const_iterator cend((row < m_rows.count()) ? (m_cols.begin() + m_rows.value(row)) : m_cols.end()); for (QVector::const_iterator cit = cstart; cit != cend; ++cit) { if (*cit >= rect.left() && *cit <= rect.right()) { if (keepOffset) subStorage.insert(*cit, row, m_data.value(cit - m_cols.begin())); else subStorage.insert(*cit - offset.x(), row - offset.y(), m_data.value(cit - m_cols.begin())); } } } } return subStorage; } /** * Equality operator. */ bool operator==(const PointStorage& o) const { return (m_rows == o.m_rows && m_cols == o.m_cols && m_data == o.m_data); } private: void squeezeRows() { int row = m_rows.count() - 1; while (m_rows.value(row) == m_data.count() && row >= 0) m_rows.remove(row--); } private: QVector m_cols; // stores the column indices (beginning with one) QVector m_rows; // stores the row offsets in m_data QVector m_data; // stores the actual non-default data #ifdef KSPREAD_POINT_STORAGE_HASH QSet m_usedData; #endif }; } // namespace Sheets } // namespace Calligra #endif // CALLIGRA_SHEETS_POINT_STORAGE diff --git a/sheets/StyleStorage.cpp b/sheets/StyleStorage.cpp index 743f28eee72..7e794eb9bed 100644 --- a/sheets/StyleStorage.cpp +++ b/sheets/StyleStorage.cpp @@ -1,986 +1,986 @@ /* This file is part of the KDE project Copyright 2010 Marijn Kruisselbrink Copyright 2006,2007 Stefan Nikolaus This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Local #include "StyleStorage.h" #include #include #include #include #ifdef CALLIGRA_SHEETS_MT #include #include #endif #include "Global.h" #include "Map.h" #include "RTree.h" #include "Style.h" #include "StyleManager.h" #include "RectStorage.h" static const int g_maximumCachedStyles = 10000; using namespace Calligra::Sheets; class Q_DECL_HIDDEN StyleStorage::Private { public: Private() #ifdef CALLIGRA_SHEETS_MT : cacheMutex(QMutex::Recursive) #endif {} Map* map; RTree tree; - QMap usedColumns; // FIXME Stefan: Use QList and qUpperBound() for insertion. + QMap usedColumns; // FIXME Stefan: Use QList and std::upper_bound() for insertion. QMap usedRows; QRegion usedArea; QHash > subStyles; QMap > possibleGarbage; QCache cache; QRegion cachedArea; StyleStorageLoaderJob* loader; #ifdef CALLIGRA_SHEETS_MT QMutex cacheMutex; #endif void ensureLoaded(); }; class Calligra::Sheets::StyleStorageLoaderJob : public QRunnable { public: StyleStorageLoaderJob(StyleStorage* storage, const QList >& styles); void run() override; void waitForFinished(); bool isFinished(); QList > data() const { return m_styles; } private: StyleStorage* m_storage; QList > m_styles; }; StyleStorageLoaderJob::StyleStorageLoaderJob(StyleStorage *storage, const QList > &styles) : m_storage(storage), m_styles(styles) { } void StyleStorageLoaderJob::waitForFinished() { run(); } bool StyleStorageLoaderJob::isFinished() { return false; } void StyleStorageLoaderJob::run() { static int total = 0; debugSheetsStyle << "Loading styles:" << endl << m_styles; QTime t; t.start(); StyleStorage::Private* d = m_storage->d; QList > subStyles; d->usedArea = QRegion(); d->usedColumns.clear(); d->usedRows.clear(); { #ifdef CALLIGRA_SHEETS_MT QMutexLocker(&d->cacheMutex); #endif d->cachedArea = QRegion(); d->cache.clear(); } typedef QPair StyleRegion; foreach (const StyleRegion& styleArea, m_styles) { const QRegion& reg = styleArea.first; const Style& style = styleArea.second; if (style.isEmpty()) continue; // update used areas QRect bound = reg.boundingRect(); if ((bound.top() == 1 && bound.bottom() >= KS_rowMax) || (bound.left() == 1 && bound.right() >= KS_colMax)) { foreach (const QRect& rect, reg.rects()) { if (rect.top() == 1 && rect.bottom() >= KS_rowMax) { for (int i = rect.left(); i <= rect.right(); ++i) { d->usedColumns.insert(i, true); } } else if (rect.left() == 1 && rect.right() >= KS_colMax) { for (int i = rect.top(); i <= rect.bottom(); ++i) { d->usedRows.insert(i, true); } } else { d->usedArea += rect; } } } else { d->usedArea += reg; } // find substyles foreach(const SharedSubStyle& subStyle, style.subStyles()) { bool foundShared = false; typedef const QList< SharedSubStyle> StoredSubStyleList; StoredSubStyleList& storedSubStyles(d->subStyles.value(subStyle->type())); StoredSubStyleList::ConstIterator end(storedSubStyles.end()); for (StoredSubStyleList::ConstIterator it(storedSubStyles.begin()); it != end; ++it) { if (Style::compare(subStyle.data(), (*it).data())) { // debugSheetsStyle <<"[REUSING EXISTING SUBSTYLE]"; subStyles.append(qMakePair(reg, *it)); foundShared = true; break; } } if (!foundShared) { // insert substyle and add to the used substyle list //if (reg.contains(QPoint(1,1))) {debugSheetsStyle<<"load:"<dump();} subStyles.append(qMakePair(reg, subStyle)); } } } d->tree.load(subStyles); int e = t.elapsed(); total += e; debugSheetsStyle << "Time: " << e << total; } void StyleStorage::Private::ensureLoaded() { if (loader) { loader->waitForFinished(); delete loader; loader = 0; } } StyleStorage::StyleStorage(Map* map) : QObject(map) , d(new Private) { d->map = map; d->cache.setMaxCost(g_maximumCachedStyles); d->loader = 0; } StyleStorage::StyleStorage(const StyleStorage& other) : QObject(other.d->map) , d(new Private) { d->map = other.d->map; d->tree = other.d->tree; d->usedColumns = other.d->usedColumns; d->usedRows = other.d->usedRows; d->usedArea = other.d->usedArea; d->subStyles = other.d->subStyles; if (other.d->loader) { d->loader = new StyleStorageLoaderJob(this, other.d->loader->data()); } else { d->loader = 0; } // the other member variables are temporary stuff } StyleStorage::~StyleStorage() { delete d->loader; // in a multi-threaded approach this needs more care delete d; } Style StyleStorage::contains(const QPoint& point) const { d->ensureLoaded(); if (!d->usedArea.contains(point) && !d->usedColumns.contains(point.x()) && !d->usedRows.contains(point.y())) return *styleManager()->defaultStyle(); { #ifdef CALLIGRA_SHEETS_MT QMutexLocker ml(&d->cacheMutex); #endif // first, lookup point in the cache if (d->cache.contains(point)) { Style st = *d->cache.object(point); //if (point.x() == 1 && point.y() == 1) {debugSheetsStyle <<"StyleStorage: cached style:"< subStyles = d->tree.contains(point); //if (point.x() == 1 && point.y() == 1) {debugSheetsStyle <<"StyleStorage: substyles:"<debugData();}} if (subStyles.isEmpty()) { Style *style = styleManager()->defaultStyle(); // let's try caching empty styles too, the lookup is rather expensive still { #ifdef CALLIGRA_SHEETS_MT QMutexLocker ml(&d->cacheMutex); #endif // insert style into the cache d->cache.insert(point, style); d->cachedArea += QRect(point, point); } return *style; } Style* style = new Style(); (*style) = composeStyle(subStyles); { #ifdef CALLIGRA_SHEETS_MT QMutexLocker ml(&d->cacheMutex); #endif // insert style into the cache d->cache.insert(point, style); d->cachedArea += QRect(point, point); } //if (point.x() == 1 && point.y() == 1) {debugSheetsStyle <<"StyleStorage: style:"<dump();} return *style; } Style StyleStorage::contains(const QRect& rect) const { d->ensureLoaded(); QList subStyles = d->tree.contains(rect); return composeStyle(subStyles); } Style StyleStorage::intersects(const QRect& rect) const { d->ensureLoaded(); QList subStyles = d->tree.intersects(rect); return composeStyle(subStyles); } QList< QPair > StyleStorage::undoData(const Region& region) const { d->ensureLoaded(); QList< QPair > result; Region::ConstIterator end = region.constEnd(); for (Region::ConstIterator it = region.constBegin(); it != end; ++it) { const QRect rect = (*it)->rect(); QList< QPair > pairs = d->tree.intersectingPairs(rect).values(); for (int i = 0; i < pairs.count(); ++i) { // trim the rects pairs[i].first = pairs[i].first.intersected(rect); } // Always a default subStyle first, even if there are no pairs. result << qMakePair(QRectF(rect), SharedSubStyle()) << pairs; } return result; } QRect StyleStorage::usedArea() const { d->ensureLoaded(); if (d->usedArea.isEmpty()) return QRect(1, 1, 0, 0); return QRect(QPoint(1, 1), d->usedArea.boundingRect().bottomRight()); } // craete default styles in the style tables - used in Odf saving void StyleStorage::saveCreateDefaultStyles(int& maxCols, int& maxRows, QMap &columnDefaultStyles, QMap &rowDefaultStyles) const { d->ensureLoaded(); #if 0 // TODO // If we have both, column and row styles, we can take the short route. if (!d->usedColumns.isEmpty() && !d->usedRows.isEmpty()) { for (int i = 0; i < d->usedColumns.count(); ++i) { const int col = d->usedColumns[i]; columnDefaultStyles[col].insertSubStyle(contains(QRect(col, 1, 1, KS_rowMax))); } for (int i = 0; i < d->usedRow.count(); ++i) { const int row = d->usedRow[i]; rowDefaultStyles[row].insertSubStyle(contains(QRect(1, row, KS_colMax, 1))); } return; } #endif const QRect sheetRect(QPoint(1, 1), QPoint(KS_colMax, KS_rowMax)); if (d->usedColumns.count() != 0) { maxCols = qMax(maxCols, (--d->usedColumns.constEnd()).key()); maxRows = KS_rowMax; } if (d->usedRows.count() != 0) { maxCols = KS_colMax; maxRows = qMax(maxRows, (--d->usedRows.constEnd()).key()); } const QList< QPair > pairs = d->tree.intersectingPairs(sheetRect).values(); for (int i = 0; i < pairs.count(); ++i) { const QRect rect = pairs[i].first.toRect(); // column default cell styles // Columns have no content. Prefer them over rows for the default cell styles. if (rect.top() == 1 && rect.bottom() == maxRows) { for (int col = rect.left(); col <= rect.right(); ++col) { if (pairs[i].second.data()->type() == Style::DefaultStyleKey) columnDefaultStyles.remove(col); else columnDefaultStyles[col].insertSubStyle(pairs[i].second); } } // row default cell styles else if (rect.left() == 1 && rect.right() == maxCols) { for (int row = rect.top(); row <= rect.bottom(); ++row) { if (pairs[i].second.data()->type() == Style::DefaultStyleKey) rowDefaultStyles.remove(row); else rowDefaultStyles[row].insertSubStyle(pairs[i].second); } } } } int StyleStorage::nextColumnStyleIndex(int column) const { d->ensureLoaded(); QMap::iterator it = d->usedColumns.upperBound(column + 1); return (it == d->usedColumns.end()) ? 0 : it.key(); } int StyleStorage::nextRowStyleIndex(int row) const { d->ensureLoaded(); QMap::iterator it = d->usedRows.upperBound(row + 1); return (it == d->usedRows.end()) ? 0 : it.key(); } int StyleStorage::firstColumnIndexInRow(int row) const { d->ensureLoaded(); const QRect rect = (d->usedArea & QRect(QPoint(1, row), QPoint(KS_colMax, row))).boundingRect(); return rect.isNull() ? 0 : rect.left(); } int StyleStorage::nextColumnIndexInRow(int column, int row) const { d->ensureLoaded(); const QRect rect = (d->usedArea & QRect(QPoint(column + 1, row), QPoint(KS_colMax, row))).boundingRect(); return rect.isNull() ? 0 : rect.left(); } void StyleStorage::insert(const QRect& rect, const SharedSubStyle& subStyle, bool markRegionChanged) { d->ensureLoaded(); // debugSheetsStyle <<"StyleStorage: inserting" << SubStyle::name(subStyle->type()) <<" into" << rect; // keep track of the used area const bool isDefault = subStyle->type() == Style::DefaultStyleKey; if (rect.top() == 1 && rect.bottom() >= KS_rowMax) { for (int i = rect.left(); i <= rect.right(); ++i) { if (isDefault) d->usedColumns.remove(i); else d->usedColumns.insert(i, true); } if (isDefault) d->usedArea -= rect; } else if (rect.left() == 1 && rect.right() >= KS_colMax) { for (int i = rect.top(); i <= rect.bottom(); ++i) { if (isDefault) d->usedRows.remove(i); else d->usedRows.insert(i, true); } if (isDefault) d->usedArea -= rect; } else { if (isDefault) d->usedArea -= rect; else d->usedArea += rect; } // lookup already used substyles typedef const QList< SharedSubStyle> StoredSubStyleList; StoredSubStyleList& storedSubStyles(d->subStyles.value(subStyle->type())); StoredSubStyleList::ConstIterator end(storedSubStyles.end()); for (StoredSubStyleList::ConstIterator it(storedSubStyles.begin()); it != end; ++it) { if (Style::compare(subStyle.data(), (*it).data())) { // debugSheetsStyle <<"[REUSING EXISTING SUBSTYLE]"; d->tree.insert(rect, *it); if (markRegionChanged) { regionChanged(rect); } return; } } // insert substyle and add to the used substyle list d->tree.insert(rect, subStyle); d->subStyles[subStyle->type()].append(subStyle); if (markRegionChanged) { regionChanged(rect); } } void StyleStorage::insert(const Region& region, const Style& style) { d->ensureLoaded(); if (style.isEmpty()) return; foreach(const SharedSubStyle& subStyle, style.subStyles()) { Region::ConstIterator end(region.constEnd()); for (Region::ConstIterator it(region.constBegin()); it != end; ++it) { // insert substyle insert((*it)->rect(), subStyle, false); } } for (Region::ConstIterator it(region.constBegin()), end(region.constEnd()); it != end; ++it) { regionChanged((*it)->rect()); } } void StyleStorage::load(const QList >& styles) { Q_ASSERT(!d->loader); d->loader = new StyleStorageLoaderJob(this, styles); } QList< QPair > StyleStorage::insertRows(int position, int number) { d->ensureLoaded(); const QRect invalidRect(1, position, KS_colMax, KS_rowMax); // invalidate the affected, cached styles invalidateCache(invalidRect); // update the used area const QRegion usedArea = d->usedArea & invalidRect; d->usedArea -= invalidRect; d->usedArea += usedArea.translated(0, number); const QVector rects = (d->usedArea & QRect(1, position - 1, KS_colMax, 1)).rects(); for (int i = 0; i < rects.count(); ++i) d->usedArea += rects[i].adjusted(0, 1, 0, number + 1); // update the used rows QMap map; QMap::iterator begin = d->usedRows.lowerBound(position); QMap::iterator end = d->usedRows.end(); for (QMap::iterator it = begin; it != end; ++it) { if (it.key() + number <= KS_rowMax) map.insert(it.key() + number, true); } for (QMap::iterator it = begin; it != d->usedRows.end(); ) it = d->usedRows.erase(it); d->usedRows.unite(map); // process the tree QList< QPair > undoData; undoData << qMakePair(QRectF(1, KS_rowMax - number + 1, KS_colMax, number), SharedSubStyle()); undoData << d->tree.insertRows(position, number); return undoData; } QList< QPair > StyleStorage::insertColumns(int position, int number) { d->ensureLoaded(); const QRect invalidRect(position, 1, KS_colMax, KS_rowMax); // invalidate the affected, cached styles invalidateCache(invalidRect); // update the used area const QRegion usedArea = d->usedArea & invalidRect; d->usedArea -= invalidRect; d->usedArea += usedArea.translated(number, 0); const QVector rects = (d->usedArea & QRect(position - 1, 0, 1, KS_rowMax)).rects(); for (int i = 0; i < rects.count(); ++i) d->usedArea += rects[i].adjusted(1, 0, number + 1, 0); // update the used columns QMap map; QMap::iterator begin = d->usedColumns.upperBound(position); QMap::iterator end = d->usedColumns.end(); for (QMap::iterator it = begin; it != end; ++it) { if (it.key() + number <= KS_colMax) map.insert(it.key() + number, true); } for (QMap::iterator it = begin; it != d->usedColumns.end(); ) it = d->usedColumns.erase(it); d->usedColumns.unite(map); // process the tree QList< QPair > undoData; undoData << qMakePair(QRectF(KS_colMax - number + 1, 1, number, KS_rowMax), SharedSubStyle()); undoData << d->tree.insertColumns(position, number); return undoData; } QList< QPair > StyleStorage::removeRows(int position, int number) { d->ensureLoaded(); const QRect invalidRect(1, position, KS_colMax, KS_rowMax); // invalidate the affected, cached styles invalidateCache(invalidRect); // update the used area const QRegion usedArea = d->usedArea & QRect(1, position + number, KS_colMax, KS_rowMax); d->usedArea -= invalidRect; d->usedArea += usedArea.translated(0, -number); // update the used rows QMap map; QMap::iterator begin = d->usedRows.upperBound(position); QMap::iterator end = d->usedRows.end(); for (QMap::iterator it = begin; it != end; ++it) { if (it.key() - number >= position) map.insert(it.key() - number, true); } for (QMap::iterator it = begin; it != d->usedRows.end(); ) it = d->usedRows.erase(it); d->usedRows.unite(map); // process the tree QList< QPair > undoData; undoData << qMakePair(QRectF(1, position, KS_colMax, number), SharedSubStyle()); undoData << d->tree.removeRows(position, number); return undoData; } QList< QPair > StyleStorage::removeColumns(int position, int number) { d->ensureLoaded(); const QRect invalidRect(position, 1, KS_colMax, KS_rowMax); // invalidate the affected, cached styles invalidateCache(invalidRect); // update the used area const QRegion usedArea = d->usedArea & QRect(position + number, 1, KS_colMax, KS_rowMax); d->usedArea -= invalidRect; d->usedArea += usedArea.translated(-number, 0); // update the used columns QMap map; QMap::iterator begin = d->usedColumns.upperBound(position); QMap::iterator end = d->usedColumns.end(); for (QMap::iterator it = begin; it != end; ++it) { if (it.key() - number >= position) map.insert(it.key() - number, true); } for (QMap::iterator it = begin; it != d->usedColumns.end(); ) it = d->usedColumns.erase(it); d->usedColumns.unite(map); // process the tree QList< QPair > undoData; undoData << qMakePair(QRectF(position, 1, number, KS_rowMax), SharedSubStyle()); undoData << d->tree.removeColumns(position, number); return undoData; } QList< QPair > StyleStorage::insertShiftRight(const QRect& rect) { d->ensureLoaded(); const QRect invalidRect(rect.topLeft(), QPoint(KS_colMax, rect.bottom())); QList< QPair > undoData; undoData << qMakePair(QRectF(rect), SharedSubStyle()); undoData << d->tree.insertShiftRight(rect); regionChanged(invalidRect); // update the used area const QRegion usedArea = d->usedArea & invalidRect; d->usedArea -= invalidRect; d->usedArea += usedArea.translated(rect.width(), 0); const QVector rects = (d->usedArea & QRect(rect.left() - 1, rect.top(), 1, rect.height())).rects(); for (int i = 0; i < rects.count(); ++i) d->usedArea += rects[i].adjusted(1, 0, rect.width() + 1, 0); // update the used columns QMap::iterator begin = d->usedColumns.upperBound(rect.left()); QMap::iterator end = d->usedColumns.end(); for (QMap::iterator it = begin; it != end; ++it) { if (it.key() + rect.width() <= KS_colMax) d->usedArea += QRect(it.key() + rect.width(), rect.top(), rect.width(), rect.height()); } if (d->usedColumns.contains(rect.left() - 1)) d->usedArea += rect; return undoData; } QList< QPair > StyleStorage::insertShiftDown(const QRect& rect) { d->ensureLoaded(); const QRect invalidRect(rect.topLeft(), QPoint(rect.right(), KS_rowMax)); QList< QPair > undoData; undoData << qMakePair(QRectF(rect), SharedSubStyle()); undoData << d->tree.insertShiftDown(rect); regionChanged(invalidRect); // update the used area const QRegion usedArea = d->usedArea & invalidRect; d->usedArea -= invalidRect; d->usedArea += usedArea.translated(0, rect.height()); const QVector rects = (d->usedArea & QRect(rect.left(), rect.top() - 1, rect.width(), 1)).rects(); for (int i = 0; i < rects.count(); ++i) d->usedArea += rects[i].adjusted(0, 1, 0, rect.height() + 1); // update the used rows QMap::iterator begin = d->usedRows.upperBound(rect.top()); QMap::iterator end = d->usedRows.end(); for (QMap::iterator it = begin; it != end; ++it) { if (it.key() + rect.height() <= KS_rowMax) d->usedArea += QRect(rect.left(), it.key() + rect.height(), rect.width(), rect.height()); } if (d->usedRows.contains(rect.top() - 1)) d->usedArea += rect; return undoData; } QList< QPair > StyleStorage::removeShiftLeft(const QRect& rect) { d->ensureLoaded(); const QRect invalidRect(rect.topLeft(), QPoint(KS_colMax, rect.bottom())); QList< QPair > undoData; undoData << qMakePair(QRectF(rect), SharedSubStyle()); undoData << d->tree.removeShiftLeft(rect); regionChanged(invalidRect); // update the used area const QRegion usedArea = d->usedArea & QRect(rect.right() + 1, rect.top(), KS_colMax, rect.height()); d->usedArea -= invalidRect; d->usedArea += usedArea.translated(-rect.width(), 0); // update the used columns QMap::iterator begin = d->usedColumns.upperBound(rect.right() + 1); QMap::iterator end = d->usedColumns.end(); for (QMap::iterator it = begin; it != end; ++it) { if (it.key() - rect.width() >= rect.left()) d->usedArea += QRect(it.key() - rect.width(), rect.top(), rect.width(), rect.height()); } return undoData; } QList< QPair > StyleStorage::removeShiftUp(const QRect& rect) { d->ensureLoaded(); const QRect invalidRect(rect.topLeft(), QPoint(rect.right(), KS_rowMax)); QList< QPair > undoData; undoData << qMakePair(QRectF(rect), SharedSubStyle()); undoData << d->tree.removeShiftUp(rect); regionChanged(invalidRect); // update the used area const QRegion usedArea = d->usedArea & QRect(rect.left(), rect.bottom() + 1, rect.width(), KS_rowMax); d->usedArea -= invalidRect; d->usedArea += usedArea.translated(0, -rect.height()); // update the used rows QMap::iterator begin = d->usedRows.upperBound(rect.bottom() + 1); QMap::iterator end = d->usedRows.end(); for (QMap::iterator it = begin; it != end; ++it) { if (it.key() - rect.height() >= rect.top()) d->usedArea += QRect(rect.left(), it.key() - rect.height(), rect.width(), rect.height()); } return undoData; } void StyleStorage::invalidateCache() { // still busy loading? no cache to invalidate if (d->loader && !d->loader->isFinished()) return; #ifdef CALLIGRA_SHEETS_MT QMutexLocker ml(&d->cacheMutex); #endif d->cache.clear(); d->cachedArea = QRegion(); } void StyleStorage::garbageCollection() { // still busy loading? no garbage to collect if (d->loader && !d->loader->isFinished()) return; // any possible garbage left? if (d->possibleGarbage.isEmpty()) return; const int currentZIndex = d->possibleGarbage.constBegin().key(); const QPair currentPair = d->possibleGarbage.take(currentZIndex); // check whether the named style still exists if (currentPair.second->type() == Style::NamedStyleKey && !styleManager()->style(static_cast(currentPair.second.data())->name)) { debugSheetsStyle << "removing" << currentPair.second->debugData() << "at" << Region(currentPair.first.toRect()).name() << "used" << currentPair.second->ref << "times" << endl; d->tree.remove(currentPair.first.toRect(), currentPair.second); d->subStyles[currentPair.second->type()].removeAll(currentPair.second); QTimer::singleShot(g_garbageCollectionTimeOut, this, SLOT(garbageCollection())); return; // already done } typedef QPair SharedSubStylePair; QMap pairs = d->tree.intersectingPairs(currentPair.first.toRect()); if (pairs.isEmpty()) // actually never true, just for sanity return; int zIndex = pairs.constBegin().key(); SharedSubStylePair pair = pairs[zIndex]; // check whether the default style is placed first if (zIndex == currentZIndex && currentPair.second->type() == Style::DefaultStyleKey && pair.second->type() == Style::DefaultStyleKey && pair.first == currentPair.first) { debugSheetsStyle << "removing default style" << "at" << Region(currentPair.first.toRect()).name() << "used" << currentPair.second->ref << "times" << endl; d->tree.remove(currentPair.first.toRect(), currentPair.second); QTimer::singleShot(g_garbageCollectionTimeOut, this, SLOT(garbageCollection())); return; // already done } // special handling for indentation: // check whether the default indentation is placed first if (zIndex == currentZIndex && currentPair.second->type() == Style::Indentation && static_cast*>(currentPair.second.data())->value1 == 0 && pair.first == currentPair.first) { debugSheetsStyle << "removing default indentation" << "at" << Region(currentPair.first.toRect()).name() << "used" << currentPair.second->ref << "times" << endl; d->tree.remove(currentPair.first.toRect(), currentPair.second); QTimer::singleShot(g_garbageCollectionTimeOut, this, SLOT(garbageCollection())); return; // already done } // special handling for precision: // check whether the storage default precision is placed first if (zIndex == currentZIndex && currentPair.second->type() == Style::Precision && static_cast*>(currentPair.second.data())->value1 == 0 && pair.first == currentPair.first) { debugSheetsStyle << "removing default precision" << "at" << Region(currentPair.first.toRect()).name() << "used" << currentPair.second->ref << "times" << endl; d->tree.remove(currentPair.first.toRect(), currentPair.second); QTimer::singleShot(g_garbageCollectionTimeOut, this, SLOT(garbageCollection())); return; // already done } // check, if the current substyle is covered by others added after it bool found = false; QMap::ConstIterator end = pairs.constEnd(); for (QMap::ConstIterator it = pairs.constFind(currentZIndex); it != end; ++it) { zIndex = it.key(); pair = it.value(); // as long as the substyle in question was not found, skip the substyle if (!found) { if (pair.first == currentPair.first && Style::compare(pair.second.data(), currentPair.second.data()) && zIndex == currentZIndex) { found = true; } continue; } // remove the current pair, if another substyle of the same type, // the default style or a named style follows and the rectangle // is completely covered if (zIndex != currentZIndex && (pair.second->type() == currentPair.second->type() || pair.second->type() == Style::DefaultStyleKey || pair.second->type() == Style::NamedStyleKey) && pair.first.toRect().contains(currentPair.first.toRect())) { // special handling for indentation // only remove, if covered by default if (pair.second->type() == Style::Indentation && static_cast*>(pair.second.data())->value1 != 0) { continue; } // special handling for precision // only remove, if covered by default if (pair.second->type() == Style::Precision && static_cast*>(pair.second.data())->value1 != 0) { continue; } debugSheetsStyle << "removing" << currentPair.second->debugData() << "at" << Region(currentPair.first.toRect()).name() << "used" << currentPair.second->ref << "times" << endl; d->tree.remove(currentPair.first.toRect(), currentPair.second, currentZIndex); #if 0 debugSheetsStyle << "StyleStorage: usage of" << currentPair.second->debugData() << " is" << currentPair.second->ref; // FIXME Stefan: The usage of substyles used once should be // two (?) here, not more. Why is this not the case? // The shared pointers are used by: // a) the tree // b) the reusage list (where it should be removed) // c) the cached styles (!) // d) the undo data of operations (!) if (currentPair.second->ref == 2) { debugSheetsStyle << "StyleStorage: removing" << currentPair.second << " from the used subStyles"; d->subStyles[currentPair.second->type()].removeAll(currentPair.second); } #endif break; } } QTimer::singleShot(g_garbageCollectionTimeOut, this, SLOT(garbageCollection())); } void StyleStorage::regionChanged(const QRect& rect) { // still busy loading? no garbage to collect if (d->loader && !d->loader->isFinished()) return; if (d->map->isLoading()) return; // mark the possible garbage // NOTE Stefan: The map may contain multiple indices. The already existing possible garbage has // has to be inserted most recently, because it should be accessed first. d->possibleGarbage = d->tree.intersectingPairs(rect).unite(d->possibleGarbage); QTimer::singleShot(g_garbageCollectionTimeOut, this, SLOT(garbageCollection())); // invalidate cache invalidateCache(rect); } void StyleStorage::invalidateCache(const QRect& rect) { // still busy loading? no cache to invalidate if (d->loader && !d->loader->isFinished()) return; #ifdef CALLIGRA_SHEETS_MT QMutexLocker ml(&d->cacheMutex); #endif // debugSheetsStyle <<"StyleStorage: Invalidating" << rect; const QRegion region = d->cachedArea.intersected(rect); d->cachedArea = d->cachedArea.subtracted(rect); foreach(const QRect& rect, region.rects()) { for (int col = rect.left(); col <= rect.right(); ++col) { for (int row = rect.top(); row <= rect.bottom(); ++row) { // debugSheetsStyle <<"StyleStorage: Removing cached style for" << Cell::name( col, row ); d->cache.remove(QPoint(col, row)); // also deletes it } } } } Style StyleStorage::composeStyle(const QList& subStyles) const { d->ensureLoaded(); if (subStyles.isEmpty()) { // debugSheetsStyle <<"StyleStorage:" << "nothing to merge, return the default style"; return *styleManager()->defaultStyle(); } // From OpenDocument-v1.2-os-part1 16.2 // // The element represents styles. // // Styles defined by the element use a hierarchical style model. // The element supports inheritance of formatting properties by a style from its parent style. // A parent style is specified by the style:parent-style-name attribute on a element. // // The determination of the value of a formatting property begins with any style that is specified by an element. // If the formatting property is present in that style, its value is used. // // If that style does not specify a value for that formatting property and it has a parent style, // the value of the formatting element is taken from the parent style, if present. // // If the parent style does not have a value for the formatting property, the search for the formatting property value continues up parent styles // until either the formatting property has been found or a style is found with no parent style. // // If a search of the parent styles of a style does not result in a value for a formatting property, // the determination of its value depends on the style family and the element to which a style is applied. // TODO review loading of libreOffice generated files: // It seems libreOffice saves parent also when parent is the Default style. // Sheets do not do this, it is handled implicitly. // According to the spec, both ways should be ok, // but the result is that when loading lo files, it may exist multiple (two) NamedStyleKey substyles: // One loaded explicitly (first in the list), and one generated by our loading code (later in the list). // We use the last one in the list here, this should be our generated one. CustomStyle *namedStyle = 0; for (int i = subStyles.count() - 1; i >= 0; --i) { if (subStyles[i]->type() == Style::NamedStyleKey) { namedStyle = styleManager()->style(static_cast(subStyles[i].data())->name); if (namedStyle) { debugSheetsStyle<<"Compose found namedstyle:"<(subStyles[i].data())->name<parentName();namedStyle->dump(); break; } } } Style style; // get attributes from parent styles if (namedStyle) { // first, load the attributes of the parent style(s) QList parentStyles; CustomStyle *parentStyle = styleManager()->style(namedStyle->parentName()); // debugSheetsStyle <<"StyleStorage:" << namedStyle->name() <<"'s parent =" << namedStyle->parentName(); while (parentStyle) { // debugSheetsStyle <<"StyleStorage:" << parentStyle->name() <<"'s parent =" << parentStyle->parentName(); parentStyles.prepend(parentStyle); parentStyle = styleManager()->style(parentStyle->parentName()); } Style tmpStyle; for (int i = 0; i < parentStyles.count(); ++i) { // debugSheetsStyle <<"StyleStorage: merging" << parentStyles[i]->name() <<" in."; tmpStyle = *parentStyles[i]; tmpStyle.merge(style); // insert/replace substyles in tmpStyle with substyles from style style = tmpStyle; } // second, merge the other attributes in // debugSheetsStyle <<"StyleStorage: merging" << namedStyle->name() <<" in."; tmpStyle = *namedStyle; tmpStyle.merge(style); // insert/replace substyles in tmpStyle with substyles from style style = tmpStyle; // not the default anymore style.clearAttribute(Style::DefaultStyleKey); // reset the parent name style.setParentName(namedStyle->name()); // debugSheetsStyle <<"StyleStorage: merging done"; } for (int i = 0; i < subStyles.count(); ++i) { if (subStyles[i]->type() == Style::DefaultStyleKey) { // skip } else if (subStyles[i]->type() == Style::NamedStyleKey) { // treated above } else if (subStyles[i]->type() == Style::Indentation) { // special handling for indentation const int indentation = static_cast*>(subStyles[i].data())->value1; if (indentation == 0 || (style.indentation() + indentation <= 0)) style.clearAttribute(Style::Indentation); // reset else style.setIndentation(style.indentation() + indentation); // increase/decrease } else if (subStyles[i]->type() == Style::Precision) { // special handling for precision // The Style default (-1) and the storage default (0) differ. const int precision = static_cast*>(subStyles[i].data())->value1; if (precision == 0) // storage default style.clearAttribute(Style::Precision); // reset else { if (style.precision() == -1) // Style default style.setPrecision(qMax(0, precision)); // positive initial value else if (style.precision() + precision <= 0) style.setPrecision(0); else if (style.precision() + precision >= 10) style.setPrecision(10); else style.setPrecision(style.precision() + precision); // increase/decrease } } else { // insert the substyle // debugSheetsStyle <<"StyleStorage: inserting" << subStyles[i]->debugData(); style.insertSubStyle(subStyles[i]); // not the default anymore style.clearAttribute(Style::DefaultStyleKey); } } // Implicitly merge in any missing attributes from the family (table-cell) default style // It might have been merged in via parent styles above, but we cannot rely on that. if (!styleManager()->defaultStyle()->isEmpty()) { // debugSheetsStyle << "StyleStorage: merging family default in"; Style tmpStyle = *styleManager()->defaultStyle(); tmpStyle.clearAttribute(Style::DefaultStyleKey); tmpStyle.merge(style); // insert/replace substyles in tmpStyle with substyles from style style = tmpStyle; } return style; } StyleManager* StyleStorage::styleManager() const { return d->map->styleManager(); } diff --git a/sheets/functions/statistical.cpp b/sheets/functions/statistical.cpp index 84ddb352564..542597df7bf 100644 --- a/sheets/functions/statistical.cpp +++ b/sheets/functions/statistical.cpp @@ -1,3063 +1,3063 @@ /* This file is part of the KDE project Copyright (C) 1998-2002 The KSpread Team Copyright (C) 2005 Tomas Mecir Copyright (C) 2007 Sascha Pfau This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; only version 2 of the License. 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // built-in statistical functions #include "StatisticalModule.h" #include "Function.h" #include "FunctionModuleRegistry.h" #include "ValueCalc.h" #include "ValueConverter.h" #include "SheetsDebug.h" #include // needed for MODE #include #include #include using namespace Calligra::Sheets; // prototypes (sorted!) Value func_arrang(valVector args, ValueCalc *calc, FuncExtra *); Value func_average(valVector args, ValueCalc *calc, FuncExtra *); Value func_averagea(valVector args, ValueCalc *calc, FuncExtra *); Value func_averageif(valVector args, ValueCalc *calc, FuncExtra *); Value func_averageifs(valVector args, ValueCalc *calc, FuncExtra *e); Value func_avedev(valVector args, ValueCalc *calc, FuncExtra *); Value func_betadist(valVector args, ValueCalc *calc, FuncExtra *); Value func_betainv(valVector args, ValueCalc *calc, FuncExtra *); Value func_bino(valVector args, ValueCalc *calc, FuncExtra *); Value func_binomdist(valVector args, ValueCalc *calc, FuncExtra *); Value func_chidist(valVector args, ValueCalc *calc, FuncExtra *); Value func_combin(valVector args, ValueCalc *calc, FuncExtra *); Value func_combina(valVector args, ValueCalc *calc, FuncExtra *); Value func_confidence(valVector args, ValueCalc *calc, FuncExtra *); Value func_correl_pop(valVector args, ValueCalc *calc, FuncExtra *); Value func_covar(valVector args, ValueCalc *calc, FuncExtra *); Value func_devsq(valVector args, ValueCalc *calc, FuncExtra *); Value func_devsqa(valVector args, ValueCalc *calc, FuncExtra *); Value func_expondist(valVector args, ValueCalc *calc, FuncExtra *); Value func_fdist(valVector args, ValueCalc *calc, FuncExtra *); Value func_finv(valVector args, ValueCalc *calc, FuncExtra *); Value func_fisher(valVector args, ValueCalc *calc, FuncExtra *); Value func_fisherinv(valVector args, ValueCalc *calc, FuncExtra *); Value func_frequency(valVector args, ValueCalc *calc, FuncExtra *); Value func_ftest(valVector args, ValueCalc *calc, FuncExtra *); Value func_gammadist(valVector args, ValueCalc *calc, FuncExtra *); Value func_gammainv(valVector args, ValueCalc *calc, FuncExtra *); Value func_gammaln(valVector args, ValueCalc *calc, FuncExtra *); Value func_gauss(valVector args, ValueCalc *calc, FuncExtra *); Value func_growth(valVector args, ValueCalc *calc, FuncExtra *); Value func_geomean(valVector args, ValueCalc *calc, FuncExtra *); Value func_harmean(valVector args, ValueCalc *calc, FuncExtra *); Value func_hypgeomdist(valVector args, ValueCalc *calc, FuncExtra *); Value func_intercept(valVector args, ValueCalc *calc, FuncExtra *); Value func_kurtosis_est(valVector args, ValueCalc *calc, FuncExtra *); Value func_kurtosis_pop(valVector args, ValueCalc *calc, FuncExtra *); Value func_large(valVector args, ValueCalc *calc, FuncExtra *); Value func_legacychidist(valVector args, ValueCalc *calc, FuncExtra *); Value func_legacychiinv(valVector args, ValueCalc *calc, FuncExtra *); Value func_legacyfdist(valVector args, ValueCalc *calc, FuncExtra *); Value func_legacyfinv(valVector args, ValueCalc *calc, FuncExtra *); Value func_loginv(valVector args, ValueCalc *calc, FuncExtra *); Value func_lognormdist(valVector args, ValueCalc *calc, FuncExtra *); Value func_median(valVector args, ValueCalc *calc, FuncExtra *); Value func_mode(valVector args, ValueCalc *calc, FuncExtra *); Value func_negbinomdist(valVector args, ValueCalc *calc, FuncExtra *); Value func_normdist(valVector args, ValueCalc *calc, FuncExtra *); Value func_norminv(valVector args, ValueCalc *calc, FuncExtra *); Value func_normsinv(valVector args, ValueCalc *calc, FuncExtra *); Value func_percentile(valVector args, ValueCalc *calc, FuncExtra *); Value func_permutationa(valVector args, ValueCalc *calc, FuncExtra *); Value func_phi(valVector args, ValueCalc *calc, FuncExtra *); Value func_poisson(valVector args, ValueCalc *calc, FuncExtra *); Value func_rank(valVector args, ValueCalc *calc, FuncExtra *); Value func_rsq(valVector args, ValueCalc *calc, FuncExtra *); Value func_quartile(valVector args, ValueCalc *calc, FuncExtra *); Value func_skew_est(valVector args, ValueCalc *calc, FuncExtra *); Value func_skew_pop(valVector args, ValueCalc *calc, FuncExtra *); Value func_slope(valVector args, ValueCalc *calc, FuncExtra *); Value func_small(valVector args, ValueCalc *calc, FuncExtra *); Value func_standardize(valVector args, ValueCalc *calc, FuncExtra *); Value func_stddev(valVector args, ValueCalc *calc, FuncExtra *); Value func_stddeva(valVector args, ValueCalc *calc, FuncExtra *); Value func_stddevp(valVector args, ValueCalc *calc, FuncExtra *); Value func_stddevpa(valVector args, ValueCalc *calc, FuncExtra *); Value func_stdnormdist(valVector args, ValueCalc *calc, FuncExtra *); Value func_steyx(valVector args, ValueCalc *calc, FuncExtra *); Value func_sumproduct(valVector args, ValueCalc *calc, FuncExtra *); Value func_sumx2py2(valVector args, ValueCalc *calc, FuncExtra *); Value func_sumx2my2(valVector args, ValueCalc *calc, FuncExtra *); Value func_sumxmy2(valVector args, ValueCalc *calc, FuncExtra *); Value func_tdist(valVector args, ValueCalc *calc, FuncExtra *); Value func_tinv(valVector args, ValueCalc *calc, FuncExtra *); Value func_trend(valVector args, ValueCalc *calc, FuncExtra *); Value func_trimmean(valVector args, ValueCalc *calc, FuncExtra *); Value func_ttest(valVector args, ValueCalc *calc, FuncExtra *); Value func_variance(valVector args, ValueCalc *calc, FuncExtra *); Value func_variancea(valVector args, ValueCalc *calc, FuncExtra *); Value func_variancep(valVector args, ValueCalc *calc, FuncExtra *); Value func_variancepa(valVector args, ValueCalc *calc, FuncExtra *); Value func_weibull(valVector args, ValueCalc *calc, FuncExtra *); Value func_ztest(valVector args, ValueCalc *calc, FuncExtra *); typedef QList List; CALLIGRA_SHEETS_EXPORT_FUNCTION_MODULE("kspreadstatisticalmodule.json", StatisticalModule) StatisticalModule::StatisticalModule(QObject* parent, const QVariantList&) : FunctionModule(parent) { Function *f; f = new Function("AVEDEV", func_avedev); f->setParamCount(1, -1); f->setAcceptArray(); add(f); f = new Function("AVERAGE", func_average); f->setParamCount(1, -1); f->setAcceptArray(); add(f); f = new Function("AVERAGEA", func_averagea); f->setParamCount(1, -1); f->setAcceptArray(); add(f); f = new Function("AVERAGEIF", func_averageif); f->setParamCount(2, 3); f->setAcceptArray(); f->setNeedsExtra(true); add(f); f = new Function("AVERAGEIFS", func_averageifs); f->setParamCount(3, -1); f->setAcceptArray(); f->setNeedsExtra(true); add(f); f = new Function("BETADIST", func_betadist); f->setParamCount(3, 6); add(f); f = new Function("BETAINV", func_betainv); f->setParamCount(3, 5); add(f); f = new Function("BINO", func_bino); f->setParamCount(3); add(f); f = new Function("BINOMDIST", func_binomdist); f->setParamCount(4); add(f); f = new Function("CHIDIST", func_chidist); f->setParamCount(2); add(f); f = new Function("COMBIN", func_combin); f->setParamCount(2); add(f); f = new Function("COMBINA", func_combina); f->setParamCount(2); add(f); f = new Function("CONFIDENCE", func_confidence); f->setParamCount(3); add(f); f = new Function("CORREL", func_correl_pop); f->setParamCount(2); f->setAcceptArray(); add(f); f = new Function("COVAR", func_covar); f->setParamCount(2); f->setAcceptArray(); add(f); f = new Function("DEVSQ", func_devsq); f->setParamCount(1, -1); f->setAcceptArray(); add(f); f = new Function("DEVSQA", func_devsqa); f->setParamCount(1, -1); f->setAcceptArray(); add(f); f = new Function("EXPONDIST", func_expondist); f->setParamCount(3); add(f); f = new Function("FDIST", func_fdist); f->setParamCount(3, 4); add(f); f = new Function("FINV", func_finv); f->setParamCount(3); add(f); f = new Function("FISHER", func_fisher); add(f); f = new Function("FISHERINV", func_fisherinv); add(f); f = new Function("FREQUENCY", func_frequency); f->setParamCount(2); f->setAcceptArray(); add(f); f = new Function("FTEST", func_ftest); f->setParamCount(2); f->setAcceptArray(); add(f); f = new Function("GAMMADIST", func_gammadist); f->setParamCount(4); add(f); f = new Function("GAMMAINV", func_gammainv); f->setParamCount(3); add(f); f = new Function("GAMMALN", func_gammaln); add(f); f = new Function("GAUSS", func_gauss); add(f); f = new Function("GROWTH", func_growth); f->setParamCount(1, 4); f->setAcceptArray(); add(f); f = new Function("GEOMEAN", func_geomean); f->setParamCount(1, -1); f->setAcceptArray(); add(f); f = new Function("HARMEAN", func_harmean); f->setParamCount(1, -1); f->setAcceptArray(); add(f); f = new Function("HYPGEOMDIST", func_hypgeomdist); f->setParamCount(4, 5); add(f); f = new Function("INTERCEPT", func_intercept); f->setParamCount(2); f->setAcceptArray(); add(f); f = new Function("INVBINO", func_bino); // same as BINO, for 1.4 compat add(f); f = new Function("KURT", func_kurtosis_est); f->setParamCount(1, -1); f->setAcceptArray(); add(f); f = new Function("KURTP", func_kurtosis_pop); f->setParamCount(1, -1); f->setAcceptArray(); add(f); f = new Function("LARGE", func_large); f->setParamCount(2); f->setAcceptArray(); add(f); f = new Function("LEGACYCHIDIST", func_legacychidist); f->setParamCount(2); add(f); f = new Function("LEGACYCHIINV", func_legacychiinv); f->setParamCount(2); add(f); f = new Function("LEGACYFDIST", func_legacyfdist); f->setParamCount(3); add(f); f = new Function("LEGACYFINV", func_legacyfinv); f->setParamCount(3); add(f); // this is meant to be a copy of the function NORMSDIST. // required for OpenFormula compliance. f = new Function("LEGACYNORMSDIST", func_stdnormdist); add(f); // this is meant to be a copy of the function NORMSINV. // required for OpenFormula compliance. f = new Function("LEGACYNORMSINV", func_normsinv); add(f); f = new Function("LOGINV", func_loginv); f->setParamCount(1, 3); add(f); f = new Function("LOGNORMDIST", func_lognormdist); f->setParamCount(1, 4); add(f); f = new Function("MEDIAN", func_median); f->setParamCount(1, -1); f->setAcceptArray(); add(f); f = new Function("MODE", func_mode); f->setParamCount(1, -1); f->setAcceptArray(); add(f); f = new Function("NEGBINOMDIST", func_negbinomdist); f->setParamCount(3); add(f); f = new Function("NORMDIST", func_normdist); f->setParamCount(4); add(f); f = new Function("NORMINV", func_norminv); f->setParamCount(3); add(f); f = new Function("NORMSDIST", func_stdnormdist); add(f); f = new Function("NORMSINV", func_normsinv); add(f); f = new Function("PEARSON", func_correl_pop); f->setParamCount(2); f->setAcceptArray(); add(f); f = new Function("PERCENTILE", func_percentile); f->setParamCount(2); f->setAcceptArray(); add(f); f = new Function("PERMUT", func_arrang); f->setParamCount(2); add(f); f = new Function("PERMUTATIONA", func_permutationa); f->setParamCount(2); add(f); f = new Function("PHI", func_phi); add(f); f = new Function("POISSON", func_poisson); f->setParamCount(3); add(f); f = new Function("RANK", func_rank); f->setParamCount(2, 3); f->setAcceptArray(); add(f); f = new Function("RSQ", func_rsq); f->setParamCount(2); f->setAcceptArray(); add(f); f = new Function("QUARTILE", func_quartile); f->setParamCount(2); f->setAcceptArray(); add(f); f = new Function("SKEW", func_skew_est); f->setParamCount(1, -1); f->setAcceptArray(); add(f); f = new Function("SKEWP", func_skew_pop); f->setParamCount(1, -1); f->setAcceptArray(); add(f); f = new Function("SLOPE", func_slope); f->setParamCount(2); f->setAcceptArray(); add(f); f = new Function("SMALL", func_small); f->setParamCount(2); f->setAcceptArray(); add(f); f = new Function("STANDARDIZE", func_standardize); f->setParamCount(3); add(f); f = new Function("STDEV", func_stddev); f->setParamCount(1, -1); f->setAcceptArray(); add(f); f = new Function("STDEVA", func_stddeva); f->setParamCount(1, -1); f->setAcceptArray(); add(f); f = new Function("STDEVP", func_stddevp); f->setParamCount(1, -1); f->setAcceptArray(); add(f); f = new Function("STDEVPA", func_stddevpa); f->setParamCount(1, -1); f->setAcceptArray(); add(f); f = new Function("STEYX", func_steyx); f->setParamCount(2); f->setAcceptArray(); add(f); f = new Function("SUM2XMY", func_sumxmy2); // deprecated, use SUMXMY2 f->setParamCount(2); f->setAcceptArray(); add(f); f = new Function("SUMXMY2", func_sumxmy2); f->setParamCount(2); f->setAcceptArray(); add(f); f = new Function("SUMPRODUCT", func_sumproduct); f->setParamCount(2); f->setAcceptArray(); add(f); f = new Function("SUMX2PY2", func_sumx2py2); f->setParamCount(2); f->setAcceptArray(); add(f); f = new Function("SUMX2MY2", func_sumx2my2); f->setParamCount(2); f->setAcceptArray(); add(f); f = new Function("TDIST", func_tdist); f->setParamCount(3); add(f); f = new Function("TINV", func_tinv); f->setParamCount(2); add(f); f = new Function("TREND", func_trend); f->setParamCount(1, 4); f->setAcceptArray(); add(f); f = new Function("TRIMMEAN", func_trimmean); f->setParamCount(2); f->setAcceptArray(); add(f); f = new Function("TTEST", func_ttest); f->setParamCount(4); f->setAcceptArray(); add(f); f = new Function("VARIANCE", func_variance); f->setParamCount(1, -1); f->setAcceptArray(); add(f); f = new Function("VAR", func_variance); f->setParamCount(1, -1); f->setAcceptArray(); add(f); f = new Function("VARP", func_variancep); f->setParamCount(1, -1); f->setAcceptArray(); add(f); f = new Function("VARA", func_variancea); f->setParamCount(1, -1); f->setAcceptArray(); add(f); f = new Function("VARPA", func_variancepa); f->setParamCount(1, -1); f->setAcceptArray(); add(f); f = new Function("WEIBULL", func_weibull); f->setParamCount(4); add(f); f = new Function("ZTEST", func_ztest); f->setParamCount(2, 3); f->setAcceptArray(); add(f); } QString StatisticalModule::descriptionFileName() const { return QString("statistical.xml"); } /////////////////////////////////////////////////////////// // // helper functions // /////////////////////////////////////////////////////////// // // helper: array_helper // void func_array_helper(Value range, ValueCalc *calc, List &array, int &number) { if (!range.isArray()) { array << numToDouble(calc->conv()->toFloat(range)); ++number; return; } for (unsigned int row = 0; row < range.rows(); ++row) for (unsigned int col = 0; col < range.columns(); ++col) { Value v = range.element(col, row); if (v.isArray()) func_array_helper(v, calc, array, number); else { array << numToDouble(calc->conv()->toFloat(v)); ++number; } } } // // helper: covar_helper // Value func_covar_helper(Value range1, Value range2, ValueCalc *calc, Value avg1, Value avg2) { // two arrays -> cannot use arrayWalk if ((!range1.isArray()) && (!range2.isArray())) // (v1-E1)*(v2-E2) return calc->mul(calc->sub(range1, avg1), calc->sub(range2, avg2)); int rows = range1.rows(); int cols = range1.columns(); int rows2 = range2.rows(); int cols2 = range2.columns(); if ((rows != rows2) || (cols != cols2)) return Value::errorVALUE(); Value result(0.0); for (int row = 0; row < rows; ++row) for (int col = 0; col < cols; ++col) { Value v1 = range1.element(col, row); Value v2 = range2.element(col, row); if (v1.isArray() || v2.isArray()) result = calc->add(result, func_covar_helper(v1, v2, calc, avg1, avg2)); else // result += (v1-E1)*(v2-E2) result = calc->add(result, calc->mul(calc->sub(v1, avg1), calc->sub(v2, avg2))); } return result; } /* // helper: GetValue - Returns result of a formula. static double GetValue(const QString& formula, const double x) { Formula f; QString expr = formula; if (expr[0] != '=') expr.prepend('='); expr.replace(QString("x"), QString::number(x, 'g', 12)); //debugSheets<<"expression"< 0.0; ++i) { factor *= (n - i) / (i + 1) * q / p; res -= factor; } if (res < 0.0) res = 0.0; } } else { res = factor; unsigned long max = (unsigned long) x; for (unsigned long i = 0; i < max && factor > 0.0; ++i) { factor *= (n - i) / (i + 1) * p / q; res += factor; } } } } else { // density debugSheets << "calc density"; q = 1.0 - p; factor = pow(q, n); if (factor == 0.0) { factor = pow(p, n); if (factor == 0.0) return Value::errorNA(); //SetNoValue(); else { unsigned long max = (unsigned long)(n - x); for (unsigned long i = 0; i < max && factor > 0.0; ++i) factor *= (n - i) / (i + 1) * q / p; res = factor; } } else { unsigned long max = (unsigned long) x; for (unsigned long i = 0; i < max && factor > 0.0; ++i) factor *= (n - i) / (i + 1) * p / q; res = factor; } } return Value(res); } // // Function: chidist // // returns the chi-distribution // Value func_chidist(valVector args, ValueCalc *calc, FuncExtra *) { Value fChi = args[0]; Value fDF = args[1]; // fDF < 1 || fDF >= 1.0E5 if (calc->lower(fDF, Value(1)) || (!calc->lower(fDF, Value(1.0E5)))) return Value::errorVALUE(); // fChi <= 0.0 if (calc->lower(fChi, Value(0.0)) || (!calc->greater(fChi, Value(0.0)))) return Value(1.0); // 1.0 - GetGammaDist (fChi / 2.0, fDF / 2.0, 1.0) return calc->sub(Value(1.0), calc->GetGammaDist(calc->div(fChi, 2.0), calc->div(fDF, 2.0), Value(1.0))); } // // Function: combin // Value func_combin(valVector args, ValueCalc *calc, FuncExtra *) { if (calc->lower(args[1], Value(0.0)) || calc->lower(args[1], Value(0.0)) || calc->greater(args[1], args[0])) return Value::errorNUM(); return calc->combin(args[0], args[1]); } // // Function: combina // Value func_combina(valVector args, ValueCalc *calc, FuncExtra *) { if (calc->lower(args[1], Value(0.0)) || calc->lower(args[1], Value(0.0)) || calc->greater(args[1], args[0])) return Value::errorNUM(); return calc->combin(calc->sub(calc->add(args[0], args[1]), Value(1.0)), args[1]); } // // Function: confidence // // returns the confidence interval for a population mean // Value func_confidence(valVector args, ValueCalc *calc, FuncExtra *) { Value alpha = args[0]; Value sigma = args[1]; Value n = args[2]; // sigma <= 0.0 || alpha <= 0.0 || alpha >= 1.0 || n < 1 if ((!calc->greater(sigma, Value(0.0))) || (!calc->greater(alpha, Value(0.0))) || (!calc->lower(alpha, Value(1.0))) || calc->lower(n, Value(1))) return Value::errorVALUE(); // g = gaussinv (1.0 - alpha / 2.0) Value g = calc->gaussinv(calc->sub(Value(1.0), calc->div(alpha, 2.0))); // g * sigma / sqrt (n) return calc->div(calc->mul(g, sigma), calc->sqrt(n)); } // // function: correl_pop // Value func_correl_pop(valVector args, ValueCalc *calc, FuncExtra *) { Value covar = func_covar(args, calc, 0); Value stdevp1 = calc->stddevP(args[0]); Value stdevp2 = calc->stddevP(args[1]); if (calc->isZero(stdevp1) || calc->isZero(stdevp2)) return Value::errorDIV0(); // covar / (stdevp1 * stdevp2) return calc->div(covar, calc->mul(stdevp1, stdevp2)); } // // function: covar // Value func_covar(valVector args, ValueCalc *calc, FuncExtra *) { Value avg1 = calc->avg(args[0]); Value avg2 = calc->avg(args[1]); int number = calc->count(args[0]); int number2 = calc->count(args[1]); if (number2 <= 0 || number2 != number) return Value::errorVALUE(); Value covar = func_covar_helper(args[0], args[1], calc, avg1, avg2); return calc->div(covar, number); } // // function: devsq // Value func_devsq(valVector args, ValueCalc *calc, FuncExtra *) { Value res; calc->arrayWalk(args, res, calc->awFunc("devsq"), calc->avg(args, false)); return res; } // // function: devsqa // Value func_devsqa(valVector args, ValueCalc *calc, FuncExtra *) { Value res; calc->arrayWalk(args, res, calc->awFunc("devsqa"), calc->avg(args)); return res; } // // Function: expondist // // returns the exponential distribution // Value func_expondist(valVector args, ValueCalc *calc, FuncExtra *) { Value x = args[0]; Value lambda = args[1]; Value kum = args[2]; Value result(0.0); if (!calc->greater(lambda, 0.0)) return Value::errorVALUE(); // ex = exp (-lambda * x) Value ex = calc->exp(calc->mul(calc->mul(lambda, -1), x)); if (calc->isZero(kum)) { //density if (!calc->lower(x, 0.0)) // lambda * ex result = calc->mul(lambda, ex); } else { //distribution if (calc->greater(x, 0.0)) // 1.0 - ex result = calc->sub(1.0, ex); } return result; } // // Function: fdist // // returns the f-distribution // Value func_fdist(valVector args, ValueCalc *calc, FuncExtra *) { Value x = args[0]; Value fF1 = args[1]; Value fF2 = args[2]; bool kum = true; if (args.count() > 3) kum = calc->conv()->asInteger(args[3]).asInteger(); // check constraints // x < 0.0 || fF1 < 1 || fF2 < 1 || fF1 >= 1.0E10 || fF2 >= 1.0E10 if (calc->lower(x, Value(0.0)) || calc->lower(fF1, Value(1)) || calc->lower(fF2, Value(1)) || (!calc->lower(fF1, Value(1.0E10))) || (!calc->lower(fF2, Value(1.0E10)))) return Value::errorVALUE(); if (kum) { // arg = fF2 / (fF2 + fF1 * x) Value arg = calc->div(fF2, calc->add(fF2, calc->mul(fF1, x))); // alpha = fF2/2.0 Value alpha = calc->div(fF2, 2.0); // beta = fF1/2.0 Value beta = calc->div(fF1, 2.0); return calc->sub(Value(1), calc->GetBeta(arg, alpha, beta)); } else { // non-cumulative calculation if (calc->lower(x, Value(0.0))) return Value(0); else { double res = 0.0; double f1 = calc->conv()->asFloat(args[1]).asFloat(); double f2 = calc->conv()->asFloat(args[2]).asFloat(); double xx = calc->conv()->asFloat(args[0]).asFloat(); double tmp1 = (f1 / 2) * log(f1) + (f2 / 2) * log(f2); double tmp2 = calc->GetLogGamma(Value((f1 + f2) / 2)).asFloat(); double tmp3 = calc->GetLogGamma(Value(f1 / 2)).asFloat(); double tmp4 = calc->GetLogGamma(Value(f2 / 2)).asFloat(); res = exp(tmp1 + tmp2 - tmp3 - tmp4) * pow(xx, (f1 / 2) - 1) * pow(f2 + f1 * xx, (-f1 / 2) - (f2 / 2)); return Value(res); } } } // // Function: finv // // returns the inverse f-distribution // Value func_finv(valVector args, ValueCalc *calc, FuncExtra *) { Value p = args[0]; Value f1 = args[1]; Value f2 = args[2]; Value result; //TODO constraints // if ( calc->lower(DF, 1.0) || calc->greater(DF, 1.0E5) || // calc->greater(p, 1.0) || calc->lower(p,0.0) ) // return Value::errorVALUE(); bool convergenceError; result = InverseIterator(func_fdist, valVector() << f1 << f2 << Value(1), calc).exec(p.asFloat(), f1.asFloat() * 0.5, f1.asFloat(), convergenceError); if (convergenceError) return Value::errorVALUE(); return result; } // // Function: fisher // // returns the Fisher transformation for x // Value func_fisher(valVector args, ValueCalc *calc, FuncExtra *) { // 0.5 * ln ((1.0 + fVal) / (1.0 - fVal)) Value fVal = args[0]; Value num = calc->div(calc->add(fVal, 1.0), calc->sub(1.0, fVal)); return calc->mul(calc->ln(num), 0.5); } // // Function: fisherinv // // returns the inverse of the Fisher transformation for x // Value func_fisherinv(valVector args, ValueCalc *calc, FuncExtra *) { Value fVal = args[0]; // (exp (2.0 * fVal) - 1.0) / (exp (2.0 * fVal) + 1.0) Value ex = calc->exp(calc->mul(fVal, 2.0)); return calc->div(calc->sub(ex, 1.0), calc->add(ex, 1.0)); } // // Function: FREQUENCY // Value func_frequency(valVector args, ValueCalc*, FuncExtra*) { const Value bins = args[1]; if (bins.columns() != 1) return Value::errorVALUE(); // create a data vector QVector data; for (uint v = 0; v < args[0].count(); ++v) { if (args[0].element(v).isNumber()) data.append(numToDouble(args[0].element(v).asFloat())); } // no intervals given? if (bins.isEmpty()) return Value(data.count()); // sort the data std::stable_sort(data.begin(), data.end()); Value result(Value::Array); QVector::ConstIterator begin = data.constBegin(); QVector::ConstIterator it = data.constBegin(); for (uint v = 0; v < bins.count(); ++v) { if (!bins.element(v).isNumber()) continue; - it = qUpperBound(begin, data.constEnd(), bins.element(v).asFloat()); + it = std::upper_bound(begin, data.constEnd(), bins.element(v).asFloat()); // exact match? if (*it == numToDouble(bins.element(v).asFloat())) ++it; // add the number of values in this interval to the result result.setElement(0, v, Value(static_cast(it - begin))); begin = it; } // the remaining values result.setElement(0, bins.count(), Value(static_cast(data.constEnd() - begin))); return result; } // // Function: FTEST // // TODO - check if parameters are arrays Value func_ftest(valVector args, ValueCalc *calc, FuncExtra*) { const Value matrixA = args[0]; const Value matrixB = args[1]; double val = 0.0; // stores current array value double countA = 0.0; // double countB = 0.0; // double sumA = 0.0, sumSqrA = 0.0; double sumB = 0.0, sumSqrB = 0.0; // matrixA for (uint v = 0; v < matrixA.count(); ++v) { if (matrixA.element(v).isNumber()) { ++countA; val = numToDouble(matrixA.element(v).asFloat()); sumA += val; // add sum sumSqrA += val * val; // add square } } // matrixB for (uint v = 0; v < matrixB.count(); ++v) { if (matrixB.element(v).isNumber()) { ++countB; val = numToDouble(matrixB.element(v).asFloat()); sumB += val; // add sum sumSqrB += val * val; // add square } } // check constraints if (countA < 2 || countB < 2) return Value::errorNA(); double sA = (sumSqrA - sumA * sumA / countA) / (countA - 1.0); double sB = (sumSqrB - sumB * sumB / countB) / (countB - 1.0); if (sA == 0.0 || sB == 0.0) return Value::errorNA(); double x, r1, r2; if (sA > sB) { x = sA / sB; r1 = countA - 1.0; r2 = countB - 1.0; } else { x = sB / sA; r1 = countB - 1.0; r2 = countA - 1.0; } valVector param; param.append(Value(x)); param.append(Value(r1)); param.append(Value(r2)); return calc->mul(Value(2.0), func_legacyfdist(param, calc, 0)); } // // Function: gammadist // Value func_gammadist(valVector args, ValueCalc *calc, FuncExtra *) { Value x = args[0]; Value alpha = args[1]; Value beta = args[2]; int kum = calc->conv()->asInteger(args[3]).asInteger(); // 0 or 1 Value result; if (calc->lower(x, 0.0) || (!calc->greater(alpha, 0.0)) || (!calc->greater(beta, 0.0))) return Value::errorVALUE(); if (kum == 0) { //density Value G = calc->GetGamma(alpha); // result = pow (x, alpha - 1.0) / exp (x / beta) / pow (beta, alpha) / G Value pow1 = calc->pow(x, calc->sub(alpha, 1.0)); Value pow2 = calc->exp(calc->div(x, beta)); Value pow3 = calc->pow(beta, alpha); result = calc->div(calc->div(calc->div(pow1, pow2), pow3), G); } else result = calc->GetGammaDist(x, alpha, beta); return Value(result); } // // Function: gammainv // Value func_gammainv(valVector args, ValueCalc *calc, FuncExtra *) { Value p = args[0]; Value alpha = args[1]; Value beta = args[2]; Value result; // constraints if (calc->lower(alpha, 0.0) || calc->lower(beta, 0.0) || calc->lower(p, 0.0) || !calc->lower(p, 1.0)) return Value::errorVALUE(); bool convergenceError; Value start = calc->mul(alpha, beta); result = InverseIterator(func_gammadist, valVector() << alpha << beta << Value(1), calc).exec(p.asFloat(), start.asFloat() * 0.5, start.asFloat(), convergenceError); if (convergenceError) return Value::errorVALUE(); return result; } // // Function: gammaln // // returns the natural logarithm of the gamma function // Value func_gammaln(valVector args, ValueCalc *calc, FuncExtra *) { if (calc->greater(args[0], Value(0.0))) return calc->GetLogGamma(args[0]); return Value::errorVALUE(); } // // Function: gauss // Value func_gauss(valVector args, ValueCalc *calc, FuncExtra *) { //returns the integral values of the standard normal cumulative distribution return calc->gauss(args[0]); } // // Function: growth // // GROWTH ( knownY [; [knownX] [; [newX] [; allowOsset = TRUE() ] ] ] ) // Value func_growth(valVector args, ValueCalc *calc, FuncExtra *) { debugSheets << "GROWTH"; // Debug Value known_Y = args[0]; // default bool withOffset = true; if (args.count() > 3) withOffset = calc->conv()->asInteger(args[3]).asInteger(); // check constraints if (known_Y.isEmpty()) { debugSheets << "known_Y is empty"; return Value::errorNA(); } // check if array known_Y contains only numbers for (uint i = 0; i < known_Y.count(); ++i) { if (!known_Y.element(i).isNumber()) { debugSheets << "count_Y (" << i << ") is non Value"; return Value::errorNA(); } } uint cols_X, cols_Y; // columns in X and Y-Matrix uint rows_X, rows_Y; // rows in X and Y-Matrix // stores count of elements in array // int count_Y = 0; // int count_X = 0; // int nCase = 0; // get size Y-Matrix rows_Y = known_Y.rows(); cols_Y = known_Y.columns(); debugSheets << "Y has " << rows_Y << " rows"; debugSheets << "Y has " << cols_Y << " cols"; // convert all Value in known_Y into log for (uint r = 0; r < rows_Y; ++r) for (uint c = 0; c < cols_Y; ++c) { debugSheets << "col " << c << " row " << r << " log of Y(" << known_Y.element(c, r) << ") Value=" << calc->log(known_Y.element(c, r)); // Debug known_Y.setElement(c, r, calc->ln(known_Y.element(c, r))); } Value known_X(Value::Array); // // knownX is given ... // if (args.count() > 1) { // // get X-Matrix and print size // known_X = args[1]; rows_X = known_Y.rows(); cols_X = known_X.columns(); debugSheets << "X has " << rows_X << " rows"; debugSheets << "X has " << cols_X << " cols"; // // check if array known_X contains only numbers // for (uint i = 0; i < known_X.count(); ++i) { if (!known_X.element(i).isNumber()) { debugSheets << "count_X (" << i << ") is non Value"; return Value::errorNA(); } } // // check for simple regression // if (cols_X == cols_Y && rows_X == rows_Y) nCase = 1; else if (cols_Y != 1 && rows_Y != 1) { debugSheets << "Y-Matrix only has one row or column"; return Value::errorNA(); // TODO which errortype VALUE? } else if (cols_Y == 1) { // // row alignment // if (rows_X != rows_Y) { debugSheets << "--> row aligned"; debugSheets << "row sizes not equal"; return Value::errorNA(); } else { debugSheets << "--> row aligned"; nCase = 2; // row alignment } } // // only column alignment left // else if (cols_X != cols_Y) { debugSheets << "--> col aligned"; debugSheets << "col sizes not equal"; return Value::errorNA(); } else { debugSheets << "--> col aligned"; nCase = 3; // column alignment } } else // // if known_X is empty it has to be set to // the sequence 1,2,3... n (n number of counts knownY) in one row. // { debugSheets << "fill X-Matrix with 0,1,2,3 .. sequence"; const int known_Y_count = known_Y.count(); for (int i = 0; i < known_Y_count; ++i) known_X.setElement(i, 0, Value(i)); cols_X = cols_Y; rows_X = rows_Y; // simple regression nCase = 1; } Value newX(Value::Array); uint cols_newX, rows_newX; if (args.count() < 3) { debugSheets << "no newX-Matrix --> copy X-Matrix"; cols_newX = cols_X; rows_newX = rows_X; newX = known_X; } else { newX = args[2]; // get dimensions cols_newX = newX.columns(); rows_newX = newX.rows(); if ((nCase == 2 && cols_X != cols_newX) || (nCase == 3 && rows_X != rows_newX)) { debugSheets << "newX does not fit..."; return Value::errorNA(); } // check if array newX contains only numbers for (uint i = 0; i < newX.count(); ++i) { if (!newX.element(i).isNumber()) { debugSheets << "newX (" << i << ") is non Value"; return Value::errorNA(); } } } debugSheets << "known_X = " << known_X; debugSheets << "newX = " << newX; debugSheets << "newX has " << rows_newX << " rows"; debugSheets << "newX has " << cols_newX << " cols"; // create the resulting matrix Value res(Value::Array); // // simple regression // if (nCase == 1) { debugSheets << "Simple regression detected"; // Debug double count = 0.0; double sumX = 0.0; double sumSqrX = 0.0; double sumY = 0.0; double sumSqrY = 0.0; double sumXY = 0.0; double valX, valY; // // Gehe �ber Matrix Reihen/Spaltenweise // for (uint c = 0; c < cols_Y; ++c) { for (uint r = 0; r < rows_Y; ++r) { valX = known_X.element(c, r).asFloat(); valY = known_Y.element(c, r).asFloat(); sumX += valX; sumSqrX += valX * valX; sumY += valY; sumSqrY += valY * valY; sumXY += valX * valY; ++count; } } if (count < 1.0) { debugSheets << "count less than 1.0"; return Value::errorNA(); } else { double f1 = count * sumXY - sumX * sumY; double X = count * sumSqrX - sumX * sumX; double b, m; if (withOffset) { // with offset b = sumY / count - f1 / X * sumX / count; m = f1 / X; } else { // without offset b = 0.0; m = sumXY / sumSqrX; } // // Fill result matrix // for (uint c = 0; c < cols_newX; ++c) { for (uint r = 0; r < rows_newX; ++r) { double result = 0.0; result = exp(newX.element(c, r).asFloat() * m + b); debugSheets << "res(" << c << "," << r << ") = " << result; res.setElement(c, r, Value(result)); } } } debugSheets << res; } else { if (nCase == 2) { debugSheets << "column alignment"; } else { debugSheets << "row alignment"; } } return (res); // return array } // // function: geomean // Value func_geomean(valVector args, ValueCalc *calc, FuncExtra *) { Value count = Value(calc->count(args)); Value prod = calc->product(args, Value(1.0)); if (calc->isZero(count)) return Value::errorDIV0(); return calc->pow(prod, calc->div(Value(1.0), count)); } // // function: harmean // Value func_harmean(valVector args, ValueCalc *calc, FuncExtra *) { Value count(calc->count(args)); if (calc->isZero(count)) return Value::errorDIV0(); Value suminv; calc->arrayWalk(args, suminv, awSumInv, Value(0)); return calc->div(count, suminv); } // // function: hypgeomdist // Value func_hypgeomdist(valVector args, ValueCalc *calc, FuncExtra *) { int x = calc->conv()->asInteger(args[0]).asInteger(); int n = calc->conv()->asInteger(args[1]).asInteger(); int M = calc->conv()->asInteger(args[2]).asInteger(); int N = calc->conv()->asInteger(args[3]).asInteger(); double res = 0.0; bool kum = false; if (args.count() > 4) kum = calc->conv()->asInteger(args[4]).asInteger(); if (x < 0 || n < 0 || M < 0 || N < 0) return Value::errorVALUE(); if (x > M || n > N) return Value::errorVALUE(); if (kum) { for (int i = 0; i < x + 1; ++i) { Value d1 = calc->combin(M, i); Value d2 = calc->combin(N - M, n - i); Value d3 = calc->combin(N, n); // d1 * d2 / d3 res += calc->div(calc->mul(d1, d2), d3).asFloat(); } } else { Value d1 = calc->combin(M, x); Value d2 = calc->combin(N - M, n - x); Value d3 = calc->combin(N, n); // d1 * d2 / d3 res = calc->div(calc->mul(d1, d2), d3).asFloat(); } return Value(res); } // // Function: INTERCEPT // Value func_intercept(valVector args, ValueCalc* calc, FuncExtra*) { int numberY = calc->count(args[0]); int numberX = calc->count(args[1]); if (numberY < 1 || numberX < 1 || numberY != numberX) return Value::errorVALUE(); Value denominator; Value avgY = calc->avg(args[0]); Value avgX = calc->avg(args[1]); Value nominator = func_covar_helper(args[0], args[1], calc, avgY, avgX); calc->arrayWalk(args[1], denominator, calc->awFunc("devsq"), avgX); // result = Ey - SLOPE * Ex return calc->sub(avgY, calc->mul(calc->div(nominator, denominator), avgX)); } // // function: kurtosis_est // Value func_kurtosis_est(valVector args, ValueCalc *calc, FuncExtra *) { int count = calc->count(args); if (count < 4) return Value::errorVALUE(); Value avg = calc->avg(args); Value stdev = calc->stddev(args, false); if (stdev.isZero()) return Value::errorDIV0(); Value params(Value::Array); params.setElement(0, 0, avg); params.setElement(1, 0, stdev); Value x4; calc->arrayWalk(args, x4, awKurtosis, params); // res = ( n*(n+1)*x4 - 3*(n-1)^3) / ( (n-3)*(n-2)*(n-1) ) int v1 = count * (count + 1); int v2 = 3 * (count - 1) * (count - 1) * (count - 1); int v3 = (count - 3) * (count - 2) * (count - 1); return calc->div(calc->sub(calc->mul(x4, v1), v2), v3); } // // function: kurtosis_pop // Value func_kurtosis_pop(valVector args, ValueCalc *calc, FuncExtra *) { int count = calc->count(args); if (count < 4) return Value::errorVALUE(); Value avg = calc->avg(args); Value stdev = calc->stddev(args, false); if (stdev.isZero()) return Value::errorDIV0(); Value params(Value::Array); params.setElement(0, 0, avg); params.setElement(1, 0, stdev); Value x4; calc->arrayWalk(args, x4, awKurtosis, params); // x4 / count - 3 return calc->sub(calc->div(x4, count), 3); } // // function: large // Value func_large(valVector args, ValueCalc *calc, FuncExtra *) { // does NOT support anything other than doubles !!! int k = calc->conv()->asInteger(args[1]).asInteger(); if (k < 1) return Value::errorVALUE(); List array; int number = 1; func_array_helper(args[0], calc, array, number); if (k >= number || number - k - 1 >= array.count()) return Value::errorVALUE(); std::sort(array.begin(), array.end()); double d = array.at(number - k - 1); return Value(d); } // // Function: legacaychidist // // returns the chi-distribution // Value func_legacychidist(valVector args, ValueCalc *calc, FuncExtra *) { Value fChi = args[0]; Value fDF = args[1]; // fDF < 1 || fDF >= 1.0E5 if (calc->lower(fDF, Value(1)) || (!calc->lower(fDF, Value(1.0E5)))) return Value::errorVALUE(); // fChi <= 0.0 if (calc->lower(fChi, Value(0.0)) || (!calc->greater(fChi, Value(0.0)))) return Value(1.0); // 1.0 - GetGammaDist (fChi / 2.0, fDF / 2.0, 1.0) return calc->sub(Value(1.0), calc->GetGammaDist(calc->div(fChi, 2.0), calc->div(fDF, 2.0), Value(1.0))); } // // Function: legacaychiinv // // returns the inverse chi-distribution // Value func_legacychiinv(valVector args, ValueCalc *calc, FuncExtra *) { Value p = args[0]; Value DF = args[1]; Value result; // constraints if (calc->lower(DF, 1.0) || calc->greater(DF, 1.0E5) || calc->greater(p, 1.0) || calc->lower(p, 0.0)) return Value::errorVALUE(); bool convergenceError; result = InverseIterator(func_legacychidist, valVector() << DF, calc).exec(p.asFloat(), DF.asFloat() * 0.5, DF.asFloat(), convergenceError); if (convergenceError) return Value::errorVALUE(); return result; } // // Function: legacy.fdist // // returns the f-distribution // Value func_legacyfdist(valVector args, ValueCalc *calc, FuncExtra *) { Value x = args[0]; Value fF1 = args[1]; Value fF2 = args[2]; // x < 0.0 || fF1 < 1 || fF2 < 1 || fF1 >= 1.0E10 || fF2 >= 1.0E10 if (calc->lower(x, Value(0.0)) || calc->lower(fF1, Value(1)) || calc->lower(fF2, Value(1)) || (!calc->lower(fF1, Value(1.0E10))) || (!calc->lower(fF2, Value(1.0E10)))) return Value::errorVALUE(); // arg = fF2 / (fF2 + fF1 * x) Value arg = calc->div(fF2, calc->add(fF2, calc->mul(fF1, x))); // alpha = fF2/2.0 Value alpha = calc->div(fF2, 2.0); // beta = fF1/2.0 Value beta = calc->div(fF1, 2.0); return calc->GetBeta(arg, alpha, beta); } // // Function: legacyfinv // // returns the inverse legacy f-distribution // Value func_legacyfinv(valVector args, ValueCalc *calc, FuncExtra *) { Value p = args[0]; Value f1 = args[1]; Value f2 = args[2]; Value result; //TODO constraints // if ( calc->lower(DF, 1.0) || calc->greater(DF, 1.0E5) || // calc->greater(p, 1.0) || calc->lower(p,0.0) ) // return Value::errorVALUE(); bool convergenceError; result = InverseIterator(func_legacyfdist, valVector() << f1 << f2, calc).exec(p.asFloat(), f1.asFloat() * 0.5, f1.asFloat(), convergenceError); if (convergenceError) return Value::errorVALUE(); return result; } // // function: loginv // Value func_loginv(valVector args, ValueCalc *calc, FuncExtra *) { Value p = args[0]; // defaults Value m = Value(0.0); Value s = Value(1.0); if (args.count() > 1) m = args[1]; if (args.count() > 2) s = args[2]; if (calc->lower(p, Value(0)) || calc->greater(p, Value(1))) return Value::errorVALUE(); if (!calc->greater(s, Value(0))) return Value::errorVALUE(); Value result(0.0); if (calc->equal(p, Value(1))) //p==1 result = Value::errorVALUE(); else if (calc->greater(p, Value(0))) { //p>0 Value gaussInv = calc->gaussinv(p); // exp (gaussInv * s + m) result = calc->exp(calc->add(calc->mul(s, gaussInv), m)); } return result; } // // Function: lognormdist // // returns the cumulative lognormal distribution // Value func_lognormdist(valVector args, ValueCalc *calc, FuncExtra *) { // defaults Value mue = Value(0); Value sigma = Value(1); bool kum = true; Value x = args[0]; if (args.count() > 1) mue = args[1]; if (args.count() > 2) sigma = args[2]; if (args.count() > 3) kum = calc->conv()->asInteger(args[3]).asInteger(); if (!kum) { // TODO implement me !!! return Value::errorVALUE(); // check constraints if (!calc->greater(sigma, 0.0) || (!calc->greater(x, 0.0))) return Value::errorVALUE(); } // non-cumulative // check constraints if (calc->lower(x, Value(0.0))) return Value(0.0); // (ln(x) - mue) / sigma Value Y = calc->div(calc->sub(calc->ln(x), mue), sigma); return calc->add(calc->gauss(Y), 0.5); } // // Function: MEDIAN // Value func_median(valVector args, ValueCalc *calc, FuncExtra *) { // does NOT support anything other than doubles !!! List array; int number = 0; for (int i = 0; i < args.count(); ++i) func_array_helper(args[i], calc, array, number); if (number == 0) return Value::errorVALUE(); std::sort(array.begin(), array.end()); double d; if (number % 2) // odd d = array.at((number - 1) / 2); else // even d = 0.5 * (array.at(number / 2 - 1) + array.at(number / 2)); return Value(d); } // // function: mode // Value func_mode(valVector args, ValueCalc *calc, FuncExtra *) { // does NOT support anything other than doubles !!! ContentSheet sh; for (int i = 0; i < args.count(); ++i) func_mode_helper(args[i], calc, sh); // retrieve value with max.count int maxcount = 0; double max = 0.0; // check if there is a difference in frequency ContentSheet::ConstIterator it = sh.constBegin(); double last = it.value(); // init last with 1st value bool nodiff = true; // reset flag for ( ; it != sh.constEnd(); ++it) { if (it.value() > maxcount) { max = it.key(); maxcount = it.value(); } if (last != it.value()) nodiff = false; // set flag } // if no diff return error if (nodiff) return Value::errorNUM(); else return Value(max); } // // function: negbinomdist // Value func_negbinomdist(valVector args, ValueCalc *calc, FuncExtra *) { double x = calc->conv()->asFloat(args[0]).asFloat(); double r = calc->conv()->asFloat(args[1]).asFloat(); double p = calc->conv()->asFloat(args[2]).asFloat(); if (r < 0.0 || x < 0.0 || p < 0.0 || p > 1.0) return Value::errorVALUE(); double q = 1.0 - p; double res = pow(p, r); for (double i = 0.0; i < x; ++i) res *= (i + r) / (i + 1.0) * q; return Value(res); // int x = calc->conv()->asInteger (args[0]).asInteger(); // int r = calc->conv()->asInteger (args[1]).asInteger(); // Value p = args[2]; // // if ((x + r - 1) <= 0) // return Value::errorVALUE(); // if (calc->lower (p, Value(0)) || calc->greater (p, Value(1))) // return Value::errorVALUE(); // // Value d1 = calc->combin (x + r - 1, r - 1); // // d2 = pow (p, r) * pow (1 - p, x) // Value d2 = calc->mul (calc->pow (p, r), // calc->pow (calc->sub (Value(1), p), x)); // // return calc->mul (d1, d2); } // // Function: normdist // // returns the normal cumulative distribution // Value func_normdist(valVector args, ValueCalc *calc, FuncExtra *) { Value x = args[0]; Value mue = args[1]; Value sigma = args[2]; Value k = args[3]; if (!calc->greater(sigma, 0.0)) return Value::errorVALUE(); // (x - mue) / sigma Value Y = calc->div(calc->sub(x, mue), sigma); if (calc->isZero(k)) // density return calc->div(calc->phi(Y), sigma); else // distribution return calc->add(calc->gauss(Y), 0.5); } // // Function: norminv // // returns the inverse of the normal cumulative distribution // Value func_norminv(valVector args, ValueCalc *calc, FuncExtra *) { Value x = args[0]; Value mue = args[1]; Value sigma = args[2]; if (!calc->greater(sigma, 0.0)) return Value::errorVALUE(); if (!(calc->greater(x, 0.0) && calc->lower(x, 1.0))) return Value::errorVALUE(); // gaussinv (x)*sigma + mue return calc->add(calc->mul(calc->gaussinv(x), sigma), mue); } // // Function: normsinv // // returns the inverse of the standard normal cumulative distribution // Value func_normsinv(valVector args, ValueCalc *calc, FuncExtra *) { Value x = args[0]; if (!(calc->greater(x, 0.0) && calc->lower(x, 1.0))) return Value::errorVALUE(); return calc->gaussinv(x); } // // Function: percentile // // PERCENTILE( data set; alpha ) // Value func_percentile(valVector args, ValueCalc *calc, FuncExtra*) { double alpha = numToDouble(calc->conv()->toFloat(args[1])); // create array - does NOT support anything other than doubles !!! List array; int number = 0; func_array_helper(args[0], calc, array, number); // check constraints - number of values must be > 0 and flag >0 <=4 if (number == 0) return Value::errorNA(); // or VALUE? if (alpha < -1e-9 || alpha > 1 + 1e-9) return Value::errorVALUE(); // sort values std::sort(array.begin(), array.end()); if (number == 1) return Value(array[0]); // only one value else { double r = alpha * (number - 1); int index = ::floor(r); double d = r - index; return Value(array[index] + d * (array[index+1] - array[index])); } } // // Function: permutationa // // returns the number of ordered permutations (with repetition) Value func_permutationa(valVector args, ValueCalc *calc, FuncExtra *) { int n = calc->conv()->toInteger(args[0]); int k = calc->conv()->toInteger(args[1]); if (n < 0 || k < 0) return Value::errorVALUE(); return calc->pow(Value(n), k); } // // Function: phi // // distribution function for a standard normal distribution // Value func_phi(valVector args, ValueCalc *calc, FuncExtra *) { return calc->phi(args[0]); } // // Function: poisson // // returns the Poisson distribution // Value func_poisson(valVector args, ValueCalc *calc, FuncExtra *) { Value x = args[0]; Value lambda = args[1]; Value kum = args[2]; // lambda < 0.0 || x < 0.0 if (calc->lower(lambda, Value(0.0)) || calc->lower(x, Value(0.0))) return Value::errorVALUE(); Value result; // ex = exp (-lambda) Value ex = calc->exp(calc->mul(lambda, -1)); if (calc->isZero(kum)) { // density if (calc->isZero(lambda)) result = Value(0); else // ex * pow (lambda, x) / fact (x) result = calc->div(calc->mul(ex, calc->pow(lambda, x)), calc->fact(x)); } else { // distribution if (calc->isZero(lambda)) result = Value(1); else { result = Value(1.0); Value fFak(1.0); qint64 nEnd = calc->conv()->asInteger(x).asInteger(); for (qint64 i = 1; i <= nEnd; ++i) { // fFak *= i fFak = calc->mul(fFak, (int)i); // result += pow (lambda, i) / fFak result = calc->add(result, calc->div(calc->pow(lambda, (int)i), fFak)); } result = calc->mul(result, ex); } } return result; } // // Function: rank // // rank(rank; ref.;sort order) Value func_rank(valVector args, ValueCalc *calc, FuncExtra*) { double x = calc->conv()->asFloat(args[0]).asFloat(); // default bool descending = true; double count = 1.0; double val = 0.0; bool valid = false; // flag // opt. parameter if (args.count() > 2) descending = !calc->conv()->asInteger(args[2]).asInteger(); // does NOT support anything other than doubles !!! List array; int number = 0; func_array_helper(args[1], calc, array, number); // sort array std::sort(array.begin(), array.end()); for (int i = 0; i < array.count(); ++i) { if (descending) val = array[array.count()-count]; else val = array[i]; //debugSheets<<"count ="<div(nominator, denominator); } // // function: small // Value func_small(valVector args, ValueCalc *calc, FuncExtra *) { // does NOT support anything other than doubles !!! int k = calc->conv()->asInteger(args[1]).asInteger(); if (k < 1) return Value::errorVALUE(); List array; int number = 1; func_array_helper(args[0], calc, array, number); if (k > number || k - 1 >= array.count()) return Value::errorVALUE(); std::sort(array.begin(), array.end()); double d = array.at(k - 1); return Value(d); } // // function: standardize // Value func_standardize(valVector args, ValueCalc *calc, FuncExtra *) { Value x = args[0]; Value m = args[1]; Value s = args[2]; if (!calc->greater(s, Value(0))) // s must be >0 return Value::errorVALUE(); // (x - m) / s return calc->div(calc->sub(x, m), s); } // // Function: stddev // Value func_stddev(valVector args, ValueCalc *calc, FuncExtra *) { return calc->stddev(args, false); } // // Function: stddeva // Value func_stddeva(valVector args, ValueCalc *calc, FuncExtra *) { return calc->stddev(args); } // // Function: stddevp // Value func_stddevp(valVector args, ValueCalc *calc, FuncExtra *) { return calc->stddevP(args, false); } // // Function: stddevpa // Value func_stddevpa(valVector args, ValueCalc *calc, FuncExtra *) { return calc->stddevP(args); } // // Function: normsdist // Value func_stdnormdist(valVector args, ValueCalc *calc, FuncExtra *) { //returns the cumulative lognormal distribution, mue=0, sigma=1 return calc->add(calc->gauss(args[0]), 0.5); } // // Function: STEYX // Value func_steyx(valVector args, ValueCalc* calc, FuncExtra*) { int number = calc->count(args[0]); if (number < 1 || number != calc->count(args[1])) return Value::errorVALUE(); Value varX, varY; Value avgY = calc->avg(args[0]); Value avgX = calc->avg(args[1]); Value cov = func_covar_helper(args[0], args[1], calc, avgY, avgX); calc->arrayWalk(args[0], varY, calc->awFunc("devsq"), avgY); calc->arrayWalk(args[1], varX, calc->awFunc("devsq"), avgX); Value numerator = calc->sub(varY, calc->div(calc->sqr(cov), varX)); Value denominator = calc->sub(Value(number), 2); return calc->sqrt(calc->div(numerator, denominator)); } // // Function: sumproduct // Value func_sumproduct(valVector args, ValueCalc *calc, FuncExtra *) { Value result; calc->twoArrayWalk(args[0], args[1], result, tawSumproduct); return result; } // // Function: sumx2py2 // Value func_sumx2py2(valVector args, ValueCalc *calc, FuncExtra *) { Value result; calc->twoArrayWalk(args[0], args[1], result, tawSumx2py2); return result; } // // Function: sumx2my2 // Value func_sumx2my2(valVector args, ValueCalc *calc, FuncExtra *) { Value result; calc->twoArrayWalk(args[0], args[1], result, tawSumx2my2); return result; } // // Function: SUMXMY2 // Value func_sumxmy2(valVector args, ValueCalc *calc, FuncExtra *) { Value result; calc->twoArrayWalk(args[0], args[1], result, tawSumxmy2); return result; } // // Function: tdist // // returns the t-distribution // Value func_tdist(valVector args, ValueCalc *calc, FuncExtra *) { Value T = args[0]; Value fDF = args[1]; int flag = calc->conv()->asInteger(args[2]).asInteger(); if (calc->lower(fDF, Value(1)) || (flag != 1 && flag != 2)) return Value::errorVALUE(); // arg = fDF / (fDF + T * T) Value arg = calc->div(fDF, calc->add(fDF, calc->sqr(T))); Value R; R = calc->mul(calc->GetBeta(arg, calc->div(fDF, 2.0), Value(0.5)), 0.5); if (flag == 1) return R; return calc->mul(R, 2); // flag is 2 here } // // Function: tinv // // returns the inverse t-distribution // Value func_tinv(valVector args, ValueCalc *calc, FuncExtra *) { Value p = args[0]; Value DF = args[1]; Value result; // constraints if (calc->lower(DF, 1.0) || calc->greater(DF, 1.0E5) || calc->greater(p, 1.0) || calc->lower(p, 0.0)) return Value::errorVALUE(); bool convergenceError; result = InverseIterator(func_tdist, valVector() << DF << Value(2), calc).exec(p.asFloat(), DF.asFloat() * 0.5, DF.asFloat(), convergenceError); if (convergenceError) return Value::errorVALUE(); return result; } // // Function: trend // // TREND ( knownY [; [knownX] [; [newX] [; allowOsset = TRUE() ] ] ] ) // // TODO - check do we need 2d arrays? // Value func_trend(valVector args, ValueCalc *calc, FuncExtra *) { // default bool withOffset = true; if (args.count() > 3) withOffset = calc->conv()->asInteger(args[3]).asInteger(); List knownY, knownX, newX; int knownXcount = 0, newXcount = 0; // // knownX // if (args[1].isEmpty()) { // if knownX is empty it has to be set to the sequence 1,2,3... n (n number of counts knownY) for (uint i = 1; i < args[0].count() + 1; ++i) knownX.append(i); } else { // check constraints / TODO if 2d array, then we must check dimension row&col? if (args[0].count() != args[1].count()) return Value::errorNUM(); // copy array to list func_array_helper(args[1], calc, knownX, knownXcount); } // // newX // if (args[2].isEmpty()) { for (uint i = 1; i < args[0].count() + 1; ++i) newX.append(i); } else { // copy array to list func_array_helper(args[2], calc, newX, newXcount); } // create the resulting matrix Value res(Value::Array); // arrays for slope und intercept Value Y(Value::Array); Value X(Value::Array); Value sumXX(0.0); // stores sum of xi*xi Value sumYX(0.0); // stores sum of yi*xi // copy data in arrays for (uint i = 0; i < args[0].count(); ++i) { X.setElement(i, 0, Value((double)knownX[i])); sumXX = calc->add(sumXX, calc->mul(Value((double)knownX[i]), Value((double)knownX[i]))); } for (uint i = 0; i < args[0].count(); ++i) { Y.setElement(i, 0, Value(args[0].element(i))); // sum yi*xi sumYX = calc->add(sumYX, calc->mul(Value(args[0].element(i)), Value((double)knownX[i]))); } // create parameter for func_slope and func_intercept calls valVector param; param.append(Y); param.append(X); // a1 = [xy]/[x*x] Value a1 = calc->div(sumYX, sumXX); // v2 = INTERCEPT = b Value v2 = func_intercept(param, calc, 0); // v2 is const, we only need to calc it once // fill array up with values for (uint i = 0; i < args[2].count(); ++i) { Value trend; Value v1; if (withOffset) { v1 = calc->mul(func_slope(param, calc, 0), Value(newX[i])); trend = Value(calc->add(v1 , v2)); } else { // b=0 // x*a1 trend = calc->mul(a1, Value(newX[i])); } // set value in res array res.setElement(i, 0, trend); } return (res); // return array } // // Function: trimmean // Value func_trimmean(valVector args, ValueCalc *calc, FuncExtra *) { Value dataSet = args[0]; Value cutOffFrac = args[1]; // constrains 0 <= cutOffFrac < 1 if (calc->lower(cutOffFrac, Value(0)) || !calc->lower(cutOffFrac, Value(1))) return Value::errorVALUE(); // cutOff = floor( n*cutOffFrac/2) int cutOff = floor(calc->div(calc->mul(cutOffFrac , Value((int)dataSet.count())), 2).asFloat()); double res = 0.0; // sort parameter into QList array List array; int valCount = 0; // stores the number of values in array func_array_helper(args[0], calc, array, valCount); if (valCount == 0) return Value::errorVALUE(); std::sort(array.begin(), array.end()); for (int i = cutOff; i < valCount - cutOff ; ++i) res += array[i]; res /= (valCount - 2 * cutOff); return Value(res); } // // Function TTEST // Value func_ttest(valVector args, ValueCalc* calc, FuncExtra*) { Value x = args[0]; Value y = args[1]; int mode = calc->conv()->asInteger(args[2]).asInteger(); int type = calc->conv()->asInteger(args[3]).asInteger(); int numX = calc->count(x); int numY = calc->count(y); // check mode parameter if (mode < 1 || mode > 2) return Value::errorVALUE(); // check type parameter if (type < 1 || type > 3) return Value::errorVALUE(); // check amount of numbers in sequences if (numX < 2 || numY < 2 || (type == 1 && numX != numY)) return Value::errorVALUE(); Value t; Value dof; if (type == 1) { // paired dof = calc->sub(Value(numX), 1); Value mean; calc->twoArrayWalk(x, y, mean, tawSumxmy); mean = calc->div(mean, numX); Value sigma; calc->twoArrayWalk(x, y, sigma, tawSumxmy2); sigma = calc->sqrt(calc->sub(calc->div(sigma, numX), calc->sqr(mean))); t = calc->div(calc->mul(mean, calc->sqrt(dof)), sigma); } else if (type == 2) { // independent, equal variances (revised by Jon Cooper) dof = calc->sub(calc->add(Value(numX), Value(numY)), 2); Value avgX = calc->avg(x); Value avgY = calc->avg(y); Value varX, varY; // summed dev-squares calc->arrayWalk(x, varX, calc->awFunc("devsq"), avgX); calc->arrayWalk(y, varY, calc->awFunc("devsq"), avgY); Value numerator = calc->sub(avgX, avgY); Value pooled_variance = calc->add(varX, varY); pooled_variance = calc->div(pooled_variance, dof); Value denominator = calc->add(calc->div(pooled_variance,Value(numX)), calc->div(pooled_variance,Value(numY))); denominator = calc->sqrt(denominator); t = calc->div(numerator, denominator); } else { // independent, unequal variances (revised by Jon Cooper) Value avgX = calc->avg(x); Value avgY = calc->avg(y); Value varX, varY; calc->arrayWalk(x, varX, calc->awFunc("devsq"), avgX); calc->arrayWalk(y, varY, calc->awFunc("devsq"), avgY); varX = calc->div(varX,calc->sub(Value(numX),1)); varY = calc->div(varY,calc->sub(Value(numY),1)); Value numerator = calc->sub(avgX, avgY); Value denominator = calc->add(calc->div(varX,Value(numX)), calc->div(varY,Value(numY))); denominator = calc->sqrt(denominator); t = calc->div(numerator, denominator); numerator = calc->add(calc->div(varX,Value(numX)),calc->div(varY,Value(numY))); numerator = calc->pow(numerator,2); Value denominator1 = calc->div(calc->pow(calc->div(varX,Value(numX)),2),calc->sub(Value(numX),1)); Value denominator2 = calc->div(calc->pow(calc->div(varY,Value(numY)),2),calc->sub(Value(numY),1)); denominator = calc->add(denominator1,denominator2); dof = calc->div(numerator,denominator); } valVector tmp(3); tmp.insert(0, t); tmp.insert(1, dof); tmp.insert(2, Value(mode)); return func_tdist(tmp, calc, 0); } // // Function: variance // Value func_variance(valVector args, ValueCalc *calc, FuncExtra *) { int count = calc->count(args, false); if (count < 2) return Value::errorVALUE(); Value result = func_devsq(args, calc, 0); return calc->div(result, count - 1); } // // Function: vara // Value func_variancea(valVector args, ValueCalc *calc, FuncExtra *) { int count = calc->count(args); if (count < 2) return Value::errorVALUE(); Value result = func_devsqa(args, calc, 0); return calc->div(result, count - 1); } // // Function: varp // Value func_variancep(valVector args, ValueCalc *calc, FuncExtra *) { int count = calc->count(args, false); if (count == 0) return Value::errorVALUE(); Value result = func_devsq(args, calc, 0); return calc->div(result, count); } // // Function: varpa // Value func_variancepa(valVector args, ValueCalc *calc, FuncExtra *) { int count = calc->count(args); if (count == 0) return Value::errorVALUE(); Value result = func_devsqa(args, calc, 0); return calc->div(result, count); } // // Function: weibull // // returns the Weibull distribution // Value func_weibull(valVector args, ValueCalc *calc, FuncExtra *) { Value x = args[0]; Value alpha = args[1]; Value beta = args[2]; Value kum = args[3]; Value result; if ((!calc->greater(alpha, 0.0)) || (!calc->greater(beta, 0.0)) || calc->lower(x, 0.0)) return Value::errorVALUE(); // ex = exp (-pow (x / beta, alpha)) Value ex; ex = calc->exp(calc->mul(calc->pow(calc->div(x, beta), alpha), -1)); if (calc->isZero(kum)) { // density // result = alpha / pow(beta,alpha) * pow(x,alpha-1.0) * ex result = calc->div(alpha, calc->pow(beta, alpha)); result = calc->mul(result, calc->mul(calc->pow(x, calc->sub(alpha, 1)), ex)); } else // distribution result = calc->sub(1.0, ex); return result; } // // Function ZTEST // Value func_ztest(valVector args, ValueCalc* calc, FuncExtra*) { int number = calc->count(args[0]); if (number < 2) return Value::errorVALUE(); // standard deviation is optional Value sigma = (args.count() > 2) ? args[2] : calc->stddev(args[0], false); // z = Ex - mu / sigma * sqrt(N) Value z = calc->div(calc->mul(calc->sub(calc->avg(args[0]), args[1]), calc->sqrt(Value(number))), sigma); // result = 1 - ( NORMDIST(-abs(z),0,1,1) + ( 1 - NORMDIST(abs(z),0,1,1) ) ) return calc->mul(Value(2.0), calc->gauss(calc->abs(z))); } #include "statistical.moc"