diff --git a/qml/Main.qml b/qml/Main.qml index f0ae300..389f9fe 100644 --- a/qml/Main.qml +++ b/qml/Main.qml @@ -22,12 +22,14 @@ ApplicationWindow { Layout.fillWidth: true ColumnLayout { + anchors.fill: parent spacing: 10 TextField { id: numberInput placeholderText: "Enter a number" // NOTE: We can't use the IntValidator here, since it doesn't support 64-bit values (long long) + // Entering invalid numbers might cause unexpected behavior (will be fed to parseInt). Layout.fillWidth: true font.pixelSize: 16 onTextChanged: factorizationController.reset() @@ -49,10 +51,16 @@ ApplicationWindow { } Button { - text: "Pause" + text: factorizationController.isPaused ? "Reset" : factorizationController.isRunning ? "Pause" : "Reset" Layout.fillWidth: true - enabled: factorizationController.isRunning - onClicked: factorizationController.stop() + enabled: factorizationController.isRunning || factorizationController.isPaused + onClicked: { + if (factorizationController.isPaused) { + factorizationController.reset(); + } else { + factorizationController.stop(); + } + } } } } @@ -61,7 +69,9 @@ ApplicationWindow { GroupBox { title: "Settings" Layout.fillWidth: true + ColumnLayout { + anchors.fill: parent spacing: 10 RowLayout { @@ -89,37 +99,64 @@ ApplicationWindow { } } - CheckBox { - text: "Stop at sqrt(n)" - checked: true - onCheckedChanged: factorizationController.useSqrtOptimization = checked + RowLayout { + Layout.fillWidth: true + + CheckBox { + text: "Stop at sqrt(n)" + checked: factorizationController.useSqrtOptimization + onCheckedChanged: factorizationController.useSqrtOptimization = checked + } + + CheckBox { + text: "Pause when factor found" + checked: factorizationController.pauseOnFound + onCheckedChanged: factorizationController.pauseOnFound = checked + } } } } - ProgressBar { - Layout.fillWidth: true - from: 0 - to: 100 - value: factorizationController.progress - } - GroupBox { title: "Current Status" Layout.fillWidth: true - ColumnLayout { - spacing: 5 - Text { - text: "Current Factor: " + factorizationController.currentFactor - font.pixelSize: 14 - Layout.alignment: Qt.AlignHCenter - } + ColumnLayout { + anchors.fill: parent + spacing: 5 Text { text: "Current Number (being factorized): " + factorizationController.curFactNumber font.pixelSize: 14 - Layout.alignment: Qt.AlignHCenter + } + + Text { + text: "Current Factor: " + factorizationController.currentFactor + font.pixelSize: 14 + } + + Text { + text: "Stop Factor: " + factorizationController.stopFactor + font.pixelSize: 14 + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 25 + + ProgressBar { + anchors.fill: parent + to: 100 + value: factorizationController.progress + } + + Text { + text: factorizationController.progress.toFixed(1) + "%" + anchors.centerIn: parent + font.pixelSize: 16 + color: "black" + font.bold: true + } } } } @@ -137,7 +174,7 @@ ApplicationWindow { } Text { - text: factorizationController.useSqrtOptimization ? "Note: Only factors up to sqrt(n) are searched." : "Note: Searching all factors (slower)." + text: factorizationController.useSqrtOptimization ? "Note: Only factors up to sqrt(n) are searched." : "Warning: Searching all factors (inefficient)." font.pixelSize: 12 Layout.alignment: Qt.AlignHCenter } diff --git a/src/FactorizationController.cpp b/src/FactorizationController.cpp index 803c0ab..dc9cf79 100644 --- a/src/FactorizationController.cpp +++ b/src/FactorizationController.cpp @@ -3,30 +3,37 @@ #include FactorizationController::FactorizationController(QObject* parent) : - QObject{parent}, m_isRunning(false), m_useSqrtOptimization(true), m_iterationsPerCycle(1), m_currentFactNumber(0), m_currentFactor(2) { + QObject{parent}, m_isRunning(false), m_useSqrtOptimization(true), m_pauseOnFound(false), m_iterationsPerCycle(1), m_currentFactNumber(0), m_originalNumber(0), + m_currentFactor(0), m_stopFactor(0) { assert(connect(&m_timer, &QTimer::timeout, this, &FactorizationController::onTimerTick)); } -long long FactorizationController::number() const { +unsigned long long FactorizationController::number() const { return m_originalNumber; } -long long FactorizationController::curFactNumber() const { +unsigned long long FactorizationController::curFactNumber() const { return m_currentFactNumber; } -int FactorizationController::progress() const { - // If current factor is at or below 2 already, we must be done, - // stopFactor can never be less than 2, that makes no sense. - if (m_currentFactNumber == 1 || m_currentFactor <= 2 || m_currentFactor > m_stopFactor) +double FactorizationController::progress() const { + // If the original number is set to 0, the computation wasn't yet initialized / was reset. + // If the current factor is at 2, the computation has just started, return early to avoid + // zero division issues. + if (m_originalNumber == 0 || m_currentFactor <= 2) + return 0; + + // If the current number being factorized is 1, we must be done, all factors were found + // Alternatively, we're also done when the current factor passes the stop factor. + if (m_currentFactNumber == 1 || m_currentFactor > m_stopFactor) return 100; // Determine the progress based on the current factor, in comparison to stop factor. // since factor being <= 2 means 100%, we sub 2 here. double progress = static_cast(m_currentFactor - 2) / static_cast(m_stopFactor - 2); - // Return the value as int percentage - return static_cast(std::clamp(progress * 100, 0.0, 100.0)); + // Return the value as percentage + return std::clamp(progress * 100, 0.0, 100.0); } bool FactorizationController::isFinished() const { @@ -51,7 +58,29 @@ void FactorizationController::setUseSqrtOptimization(bool value) { m_useSqrtOptimization = value; emit useSqrtOptimizationChanged(); - reset(); + + // We're in a reset state + if (m_originalNumber == 0) + return; + + if (m_useSqrtOptimization) + m_stopFactor = static_cast(std::sqrt(m_currentFactNumber)); + else + m_stopFactor = m_currentFactNumber - 1; + emit stopFactorChanged(); + emit progressChanged(); +} + +bool FactorizationController::pauseOnFound() const { + return m_pauseOnFound; +} + +void FactorizationController::setPauseOnFound(bool value) { + if (value == m_pauseOnFound) + return; + + m_pauseOnFound = value; + emit pauseOnFoundChanged(); } int FactorizationController::iterationsPerCycle() const { @@ -66,21 +95,31 @@ void FactorizationController::setIterationsPerCycle(int number) { emit iterationsPerCycleChanged(); } -long long FactorizationController::currentFactor() const { +unsigned long long FactorizationController::currentFactor() const { return m_currentFactor; } -QList FactorizationController::factors() const { +unsigned long long FactorizationController::stopFactor() const { + return m_stopFactor; +} + +QList FactorizationController::factors() const { return m_factors; } -void FactorizationController::start(long long number) { +void FactorizationController::start(unsigned long long number) { m_originalNumber = number; m_currentFactNumber = number; m_currentFactor = 2; m_isRunning = true; m_isFinished = false; m_factors.clear(); + if (m_useSqrtOptimization) { + // we could also just compute this every time, but sqrt is pretty expensive + m_stopFactor = static_cast(std::sqrt(m_currentFactNumber)); + } else { + m_stopFactor = m_currentFactNumber - 1; + } emit isRunningChanged(); emit isFinishedChanged(); emit numberChanged(); @@ -88,13 +127,7 @@ void FactorizationController::start(long long number) { emit progressChanged(); emit factorsChanged(); emit curFactNumberChanged(); - - if (m_useSqrtOptimization) { - // we could also just compute this every time, but sqrt is pretty expensive - m_stopFactor = static_cast(std::sqrt(m_currentFactNumber)); - } else { - m_stopFactor = m_currentFactNumber - 1; - } + emit stopFactorChanged(); m_timer.start(0); } @@ -134,13 +167,17 @@ void FactorizationController::reset() { return; m_isPaused = false; - m_currentFactNumber = m_originalNumber; - m_currentFactor = 2; + m_originalNumber = 0; + m_currentFactNumber = 0; + m_currentFactor = 0; + m_stopFactor = 0; m_factors.clear(); emit isPausedChanged(); emit currentFactorChanged(); - emit progressChanged(); + emit curFactNumberChanged(); emit factorsChanged(); + emit stopFactorChanged(); + emit progressChanged(); } void FactorizationController::onTimerTick() { @@ -189,12 +226,16 @@ void FactorizationController::factorize() { // the new number being factorized, rather than keeping it at the original number. // (This might make the progress bar jump radically) if (m_useSqrtOptimization) { - m_stopFactor = static_cast(std::sqrt(m_currentFactNumber)); + m_stopFactor = static_cast(std::sqrt(m_currentFactNumber)); } else { m_stopFactor = m_currentFactNumber - 1; } + emit stopFactorChanged(); emit progressChanged(); + if (m_pauseOnFound) + stop(); + // Don't increase current factor, keep dividing by it until no longer divisible } else { m_currentFactor++; diff --git a/src/FactorizationController.h b/src/FactorizationController.h index c3c6ab7..af4d3e9 100644 --- a/src/FactorizationController.h +++ b/src/FactorizationController.h @@ -26,11 +26,13 @@ class FactorizationController : public QObject { Q_PROPERTY(bool isPaused READ isPaused NOTIFY isPausedChanged) Q_PROPERTY(int iterationsPerCycle READ iterationsPerCycle WRITE setIterationsPerCycle NOTIFY iterationsPerCycleChanged) Q_PROPERTY(bool useSqrtOptimization READ useSqrtOptimization WRITE setUseSqrtOptimization NOTIFY useSqrtOptimizationChanged) - Q_PROPERTY(long long number READ number NOTIFY numberChanged) - Q_PROPERTY(long long curFactNumber READ curFactNumber NOTIFY curFactNumberChanged) - Q_PROPERTY(long long currentFactor READ currentFactor NOTIFY currentFactorChanged) - Q_PROPERTY(int progress READ progress NOTIFY progressChanged) - Q_PROPERTY(QList factors READ factors NOTIFY factorsChanged) + Q_PROPERTY(bool pauseOnFound READ pauseOnFound WRITE setPauseOnFound NOTIFY pauseOnFoundChanged) + Q_PROPERTY(unsigned long long number READ number NOTIFY numberChanged) + Q_PROPERTY(unsigned long long curFactNumber READ curFactNumber NOTIFY curFactNumberChanged) + Q_PROPERTY(unsigned long long currentFactor READ currentFactor NOTIFY currentFactorChanged) + Q_PROPERTY(unsigned long long stopFactor READ stopFactor NOTIFY stopFactorChanged) + Q_PROPERTY(double progress READ progress NOTIFY progressChanged) + Q_PROPERTY(QList factors READ factors NOTIFY factorsChanged) public: explicit FactorizationController(QObject* parent = nullptr); @@ -41,7 +43,7 @@ class FactorizationController : public QObject { * @brief Gets the current progress of factorization. * @return The progress percentage (0-100). */ - int progress() const; + double progress() const; /** * @brief Checks if the factorization process is running. @@ -87,11 +89,17 @@ class FactorizationController : public QObject { */ bool useSqrtOptimization() const; + /** + * @brief Checks whether the computation should be paused once a new factor is found. + * @return True if the computation will get paused on new factor being found, otherwise False. + */ + bool pauseOnFound() const; + /** * @brief Gets the original number being factorized. * @return The number being factorized. */ - long long number() const; + unsigned long long number() const; /** * @brief The current number being factorized. @@ -100,7 +108,7 @@ class FactorizationController : public QObject { * As the factorization process is going on, the number being factorized might change * from the original number, as we're dividing it by the found factors. */ - long long curFactNumber() const; + unsigned long long curFactNumber() const; /** * @brief Gets the current factor being tested. @@ -110,7 +118,17 @@ class FactorizationController : public QObject { * is a factor, going one by one. This returns the current factor (to be checked in the * next iteration). */ - long long currentFactor() const; + unsigned long long currentFactor() const; + + /** + * @brief Get the number at which the factorization process will stop + * @return The current stop factor. + * + * The stop factor determines at which factor will the factorization process be stopped. + * If sqrt optimizations are enabled, this will generally be the square root of the number + * currently being factorized. If not, it will be the number being factorized - 1. + */ + unsigned long long stopFactor() const; /** * @brief Gets the list of discovered factors (may not be complete). @@ -120,7 +138,7 @@ class FactorizationController : public QObject { * be complete (if the computation is still running). If you need to know whether you * can trust this result, check isFinished first. */ - QList factors() const; + QList factors() const; // endregion // region: Setters @@ -149,11 +167,20 @@ class FactorizationController : public QObject { * Generally, there's no reason to disable this optimization, however, if you wish to * perform a slower search, it is a way to achieve that. * - * Note that changing this value while the computation is in progress will reset the - * computation. + * This value can be changed even as a computation is ongoing; if you do so, the stop factor + * will change immediately, which will very likely cause the progress bar to rapidly change. + * It is possible that after enabling, the computation will immediately finish, as the stop + * factor will be less than the sqrt(x). */ void setUseSqrtOptimization(bool value); + /** + * @brief Enable or disable auto-pausing on new factor being found. + * + * This value can be changed even as a computation is ongoing. + */ + void setPauseOnFound(bool value); + // endregion // region: Functions @@ -164,7 +191,7 @@ class FactorizationController : public QObject { * This will start a 0-tick timer, which triggers a partial computation on each run, * to let the event loop cycle in between. This implementation does NOT rely on threads. */ - Q_INVOKABLE void start(long long number); + Q_INVOKABLE void start(unsigned long long number); /** * @brief Stops the ongoing factorization process. @@ -197,12 +224,14 @@ class FactorizationController : public QObject { void isFinishedChanged(); void isPausedChanged(); void useSqrtOptimizationChanged(); + void pauseOnFoundChanged(); void iterationsPerCycleChanged(); void numberChanged(); void currentFactorChanged(); void factorsChanged(); void progressChanged(); void curFactNumberChanged(); + void stopFactorChanged(); private slots: void onTimerTick(); @@ -212,12 +241,13 @@ class FactorizationController : public QObject { bool m_isFinished; ///< Indicates whether the factorization process is done. bool m_isPaused; ///< Indicates whether the factorization process is paused (can be resumed). bool m_useSqrtOptimization; ///< Indicates whether to use the sqrt optimization + bool m_pauseOnFound; ///< Indicates whether the computation should be paused when a new factor is found. int m_iterationsPerCycle; ///< The number of iterations to perform per cycle. - long long m_currentFactNumber; ///< The number currently being factorized. - long long m_originalNumber; ///< The original input number. - long long m_currentFactor; ///< The current divisor being checked. - long long m_stopFactor; ///< The stopping limit (square root of original number, saved for efficiency). - QList m_factors; ///< List of discovered prime factors. + unsigned long long m_currentFactNumber; ///< The number currently being factorized. + unsigned long long m_originalNumber; ///< The original input number. + unsigned long long m_currentFactor; ///< The current divisor being checked. + unsigned long long m_stopFactor; ///< The stopping limit (square root of original number, saved for efficiency). + QList m_factors; ///< List of discovered prime factors. QTimer m_timer; ///< Timer for stepwise execution. /**