File 2001-better-numeral-system-view.patch of Package kcalc

diff --git a/kcalc.cpp b/kcalc.cpp
index ad83e0135df70ce2f951e93e5098e1de86987914..75c58574cd4adabcbc2dacee47ce2600d201256a 100644
--- a/kcalc.cpp
+++ b/kcalc.cpp
@@ -1419,9 +1419,10 @@ void KCalculator::slotInputChanged()
     } else if (calculation_failure_) {
         switch (calculation_result_code_) {
         case CalcEngine::ResultCode::MISIING_RIGHT_UNARY_ARG:
+            break;
         case CalcEngine::ResultCode::MISIING_RIGHT_BINARY_ARG:
             if (calc_display->text().isEmpty()) {
-                numeralSystemView->clearNumber();
+                numeralSystemView->setNumber(KNumber::NaN);
             }
             break;
         case CalcEngine::ResultCode::MATH_ERROR:
@@ -2029,7 +2030,7 @@ void KCalculator::slotSetNumeralMode()
 //------------------------------------------------------------------------------
 void KCalculator::slotBaseModeAmountChanged(const KNumber &number)
 {
-    numeralSystemView->setNumber(number.toUint64(), base_mode_);
+    numeralSystemView->setNumber(number, base_mode_);
 }
 
 //------------------------------------------------------------------------------
@@ -2039,7 +2040,7 @@ void KCalculator::slotBaseModeAmountChanged(const KNumber &number)
 void KCalculator::slotClearBaseModeAmount()
 {
     if (m_parsingResult != KCalcParser::ParsingResult::SUCCESS_SINGLE_KNUMBER) {
-        numeralSystemView->clearNumber();
+        numeralSystemView->setNumber(KNumber::NaN);
     }
 }
 
diff --git a/kcalc_numeralsystem_view.cpp b/kcalc_numeralsystem_view.cpp
index 24d029fd34cdc905e329743dfb30d239e844d2ca..9d3eb0784e438d9e4b016e8db88666d53766466b 100644
--- a/kcalc_numeralsystem_view.cpp
+++ b/kcalc_numeralsystem_view.cpp
@@ -14,6 +14,8 @@
 #include <KLocalizedString>
 #include <KSqueezedTextLabel>
 
+#include "kcalc_settings.h"
+
 // KCalcNumeralSystemLabel
 
 KCalcNumeralSystemLabel::KCalcNumeralSystemLabel(QWidget *parent)
@@ -42,10 +44,21 @@ KCalcNumeralSystemLabel::KCalcNumeralSystemLabel(QWidget *parent)
     updateAlignment();
 }
 
-void KCalcNumeralSystemLabel::setNumber(quint64 number, int base)
+void KCalcNumeralSystemLabel::setNumber(const KNumber &number, int base)
 {
-    m_textLabel->setText(QString::number(number, base).toUpper());
-    m_textLabel->setAccessibleName(m_textLabel->fullText());
+    QString text;
+    if (KCalcSettings::twosComplement() && base != 10 && number.type() != KNumber::TYPE_ERROR) {
+        text = QString::number(number.integerPart().toUint64(), base).toUpper();
+    } else {
+        int fixedPrecision = -1;
+        if (KCalcSettings::fixed()) {
+            fixedPrecision = KCalcSettings::fixedPrecision();
+        }
+        text = number.toStringInBase(base, KCalcSettings::precision(), fixedPrecision);
+    }
+
+    m_textLabel->setText(text);
+    m_textLabel->setAccessibleName(text);
 
     m_numeralSystemLabel->setText(QStringLiteral("<small><b>%1</b></small>").arg(base));
 }
@@ -83,9 +96,15 @@ KCalcNumeralSystemView::KCalcNumeralSystemView(QWidget *parent)
     setLayout(layout);
 
     updateLabels();
+
+    connect(KCalcSettings::self(), &KCalcSettings::configChanged, this, [this]() {
+        if (m_number.type() != KNumber::TYPE_ERROR) {
+            updateLabels();
+        }
+    });
 }
 
-void KCalcNumeralSystemView::setNumber(quint64 number, int base)
+void KCalcNumeralSystemView::setNumber(const KNumber &number, int base)
 {
     if (m_number == number && m_base == base) {
         return;
@@ -97,42 +116,30 @@ void KCalcNumeralSystemView::setNumber(quint64 number, int base)
     updateLabels();
 }
 
-void KCalcNumeralSystemView::clearNumber()
-{
-    if (!m_number) {
-        return;
-    }
-
-    m_number.reset();
-
-    updateLabels();
-}
-
 void KCalcNumeralSystemView::updateLabels()
 {
-    const bool isValidNumber = m_number.has_value();
+    const bool isValidNumber = m_number.type() != KNumber::TYPE_ERROR;
     if (isValidNumber) {
-        const quint64 number = m_number.value();
         switch (m_base) {
         case 16:
-            m_label1->setNumber(number, 10);
-            m_label2->setNumber(number, 8);
-            m_label3->setNumber(number, 2);
+            m_label1->setNumber(m_number, 10);
+            m_label2->setNumber(m_number, 8);
+            m_label3->setNumber(m_number, 2);
             break;
         case 8:
-            m_label1->setNumber(number, 16);
-            m_label2->setNumber(number, 10);
-            m_label3->setNumber(number, 2);
+            m_label1->setNumber(m_number, 16);
+            m_label2->setNumber(m_number, 10);
+            m_label3->setNumber(m_number, 2);
             break;
         case 2:
-            m_label1->setNumber(number, 16);
-            m_label2->setNumber(number, 10);
-            m_label3->setNumber(number, 8);
+            m_label1->setNumber(m_number, 16);
+            m_label2->setNumber(m_number, 10);
+            m_label3->setNumber(m_number, 8);
             break;
         default: // 10
-            m_label1->setNumber(number, 16);
-            m_label2->setNumber(number, 8);
-            m_label3->setNumber(number, 2);
+            m_label1->setNumber(m_number, 16);
+            m_label2->setNumber(m_number, 8);
+            m_label3->setNumber(m_number, 2);
             break;
         }
     }
diff --git a/kcalc_numeralsystem_view.h b/kcalc_numeralsystem_view.h
index 60b2832c5d1be3803c2cd025bac4f3e8c02ceff6..f52d6182c00aadbec9c032c3c3068229926517d6 100644
--- a/kcalc_numeralsystem_view.h
+++ b/kcalc_numeralsystem_view.h
@@ -8,6 +8,8 @@
 
 #include <QWidget>
 
+#include "knumber/knumber.h"
+
 class QLabel;
 
 class KSqueezedTextLabel;
@@ -20,7 +22,7 @@ class KCalcNumeralSystemLabel : public QWidget
 public:
     explicit KCalcNumeralSystemLabel(QWidget *parent = nullptr);
 
-    void setNumber(quint64 number, int base);
+    void setNumber(const KNumber &number, int base);
 
 protected:
     void changeEvent(QEvent *event) override;
@@ -40,8 +42,7 @@ class KCalcNumeralSystemView : public QWidget
 public:
     explicit KCalcNumeralSystemView(QWidget *parent = nullptr);
 
-    void setNumber(quint64 number, int base);
-    void clearNumber();
+    void setNumber(const KNumber &number, int base = 10);
 
 private:
     void updateLabels();
@@ -50,7 +51,7 @@ private:
     KCalcNumeralSystemLabel *m_label2 = nullptr;
     KCalcNumeralSystemLabel *m_label3 = nullptr;
 
-    std::optional<quint64> m_number;
+    KNumber m_number;
     int m_base = 10;
 };
 
diff --git a/knumber/knumber.cpp b/knumber/knumber.cpp
index bd1b07f19465d30d210a93bcfe2c236ba235b684..94043bccc83646734b70655ce7fa13d0cb8a1619 100644
--- a/knumber/knumber.cpp
+++ b/knumber/knumber.cpp
@@ -16,6 +16,8 @@
 #include <QStringList>
 #include <cmath>
 
+using namespace Qt::StringLiterals;
+
 QString KNumber::GroupSeparator = QStringLiteral(",");
 QString KNumber::DecimalSeparator = QStringLiteral(".");
 
@@ -32,58 +34,61 @@ namespace
 {
 namespace impl
 {
+static const QLatin1String baseChars("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+
 //------------------------------------------------------------------------------
 // Name: increment
 //------------------------------------------------------------------------------
-void increment(QString &str, int position)
+void increment(QString &str, int position, int base)
 {
-    for (int i = position; i >= 0; i--) {
-        const char last_char = str[i].toLatin1();
-        switch (last_char) {
-        case '0':
-            str[i] = QLatin1Char('1');
-            break;
-        case '1':
-            str[i] = QLatin1Char('2');
-            break;
-        case '2':
-            str[i] = QLatin1Char('3');
-            break;
-        case '3':
-            str[i] = QLatin1Char('4');
-            break;
-        case '4':
-            str[i] = QLatin1Char('5');
-            break;
-        case '5':
-            str[i] = QLatin1Char('6');
-            break;
-        case '6':
-            str[i] = QLatin1Char('7');
-            break;
-        case '7':
-            str[i] = QLatin1Char('8');
-            break;
-        case '8':
-            str[i] = QLatin1Char('9');
+    if (position >= str.length()) {
+        return;
+    }
+
+    for (int i = position; i >= 0; --i) {
+        const int charIndex = baseChars.indexOf(str.at(i));
+        if (charIndex == -1) {
+            if (i == position) {
+                return;
+            }
+            continue; // may be a decimal separator
+        }
+
+        // check if we can simply increase the current value
+        if (charIndex < base - 1) {
+            str[i] = baseChars[charIndex + 1];
             break;
-        case '9':
-            str[i] = QLatin1Char('0');
-            if (i == 0) {
-                str.prepend(QLatin1Char('1'));
+        }
+
+        str[i] = u'0';
+        if (i == 0) {
+            str.prepend(u'1');
+        }
+    }
+}
+
+//------------------------------------------------------------------------------
+// Name: maybeIncrement
+//------------------------------------------------------------------------------
+void maybeIncrement(QString &str, int position, int base)
+{
+    if (position < str.length() - 1) {
+        const int charIndex = baseChars.indexOf(str.at(position + 1));
+        if (charIndex != -1) {
+            // check if rounding up is not needed
+            if (charIndex < (base + 1) / 2) {
+                return;
             }
-            continue;
-        default:
-            continue;
         }
-        break;
     }
+
+    increment(str, position, base);
 }
 
 //------------------------------------------------------------------------------
 // Name: round
 //------------------------------------------------------------------------------
-void round(QString &str, int precision, const QString &decimalSeparator)
+void round(QString &str, int precision, const QString &decimalSeparator, int base)
 {
     int decimalSymbolPos = str.indexOf(decimalSeparator);
     if (decimalSymbolPos == -1) {
@@ -103,10 +108,7 @@ void round(QString &str, int precision, const QString &decimalSeparator)
         } else if (extraZeroNeeded > 0) {
             str.append(QString().fill(QLatin1Char('0'), extraZeroNeeded));
         } else {
-            // decide whether to round up or down based on the digit after desired length
-            if (str.at(desiredLength).toLatin1() >= '5') {
-                increment(str, desiredLength - 1);
-            }
+            maybeIncrement(str, desiredLength - 1, base);
             decimalSymbolPos = str.indexOf(decimalSeparator);
             str.truncate(decimalSymbolPos + precision + 1);
         }
@@ -118,37 +120,33 @@ void round(QString &str, int precision, const QString &decimalSeparator)
 //------------------------------------------------------------------------------
 // Name: round
 //------------------------------------------------------------------------------
-QString KNumber::round(const QString &s, int precision) const
+QString KNumber::round(const QString &s, int precision, int base) const
 {
-    QString tmp = s;
+    if (precision < 0) {
+        return s;
+    }
 
-    const QRegularExpression rx(QString(QLatin1String(R"(^[+-]?\d+(%1\d+)*(e[+-]?\d+)?$)")).arg(QRegularExpression::escape(DecimalSeparator)));
+    static const QLatin1String numRegexStr(R"(^[+-]?([0-9A-F]+(?:%1[0-9A-F]+)?)([pe@][+-]?[0-9]+)?$)");
 
-    if (precision < 0 || !rx.match(tmp).hasMatch()) {
+    const QRegularExpression numRegex(numRegexStr.arg(QRegularExpression::escape(DecimalSeparator)));
+    const QRegularExpressionMatch match = numRegex.match(s);
+    if (!match.hasMatch()) {
         return s;
     }
 
-    // Skip the sign (for now)
-    const bool neg = (tmp[0] == QLatin1Char('-'));
-    if (neg || tmp[0] == QLatin1Char('+')) {
+    QString tmp = s;
+    const bool neg = (tmp.at(0) == u'-');
+    if (neg || tmp.at(0) == u'+') {
         tmp.remove(0, 1);
     }
 
-    // Split off exponential part (including 'e'-symbol)
-    QString mantString = tmp.section(QLatin1Char('e'), 0, 0, QString::SectionCaseInsensitiveSeps);
-    QString expString = tmp.section(QLatin1Char('e'), 1, 1, QString::SectionCaseInsensitiveSeps | QString::SectionIncludeLeadingSep);
-
-    if (expString.length() == 1) {
-        expString.clear();
-    }
-
-    impl::round(mantString, precision, KNumber::decimalSeparator());
-
+    QString significand = match.captured(1);
+    impl::round(significand, precision, DecimalSeparator, base);
     if (neg) {
-        mantString.prepend(QLatin1Char('-'));
+        significand.prepend(u'-');
     }
 
-    return mantString + expString;
+    return significand + match.captured(2);
 }
 
 //------------------------------------------------------------------------------
@@ -650,6 +648,50 @@ QString KNumber::toQString(int width, int precision) const
     }
 }
 
+//------------------------------------------------------------------------------
+// Name: toStringInBase
+//------------------------------------------------------------------------------
+QString KNumber::toStringInBase(int base, int width, int precision) const
+{
+    if (base < 2 || base > 36) {
+        return QString();
+    }
+
+    if (value_->is_zero()) {
+        return "0"_L1;
+    }
+
+    QString str = value_->toStringInBase(base, width);
+
+    const Type numType = type();
+    if (numType != TYPE_ERROR) {
+        if (base == 16) {
+            // avoid uppercasing the exponent prefix "p"
+            static const QRegularExpression afRegex("([a-f]+)"_L1);
+            for (const auto &match : afRegex.globalMatch(str)) {
+                str.replace(match.capturedStart(1), match.capturedLength(1), match.captured(1).toUpper());
+            }
+        } else if (base > 10) {
+            str = str.toUpper();
+        }
+
+        switch (numType) {
+        case TYPE_FLOAT:
+        case TYPE_FRACTION:
+            localizeDecimalSeparator(str);
+            break;
+        default:
+            break;
+        }
+
+        if (precision >= 0 && precision < width) {
+            str = round(str, precision, base);
+        }
+    }
+
+    return str;
+}
+
 //------------------------------------------------------------------------------
 // Name: localizeDecimalSeparator
 // Desc: replaces the internal '.' as decimal separator
diff --git a/knumber/knumber.h b/knumber/knumber.h
index a77c3a06873546ae1489aa2c6e4a21f5ccdec536..aacdd4cafb3c81e3d49adb4d87b08ffa3ce0916a 100644
--- a/knumber/knumber.h
+++ b/knumber/knumber.h
@@ -96,11 +96,12 @@ public:
 
 public:
     QString toQString(int width = -1, int precision = -1) const;
+    QString toStringInBase(int base, int width = -1, int precision = -1) const;
     quint64 toUint64() const;
     qint64 toInt64() const;
 
 private:
-    QString round(const QString &s, int precision) const;
+    QString round(const QString &s, int precision, int base = 10) const;
 
 public:
     KNumber abs() const;
diff --git a/knumber/knumber_base.h b/knumber/knumber_base.h
index 2d97609ee98d4005bdef5db0509361a5814350c7..d3c10236ed95c0f67ab9a730445f7257b026859d 100644
--- a/knumber/knumber_base.h
+++ b/knumber/knumber_base.h
@@ -30,6 +30,7 @@ public:
 
 public:
     virtual QString toString(int precision) const = 0;
+    virtual QString toStringInBase(int base, int precision) const = 0;
     virtual quint64 toUint64() const = 0;
     virtual qint64 toInt64() const = 0;
 
diff --git a/knumber/knumber_error.cpp b/knumber/knumber_error.cpp
index 5da6f5cbebae6e8973c53a1790a133eac79d9831..8e19ef0446f6a2717e90b035b271361ce76e2d5f 100644
--- a/knumber/knumber_error.cpp
+++ b/knumber/knumber_error.cpp
@@ -99,6 +99,16 @@ QString knumber_error::toString(int precision) const
     }
 }
 
+//------------------------------------------------------------------------------
+// Name: toStringInBase
+//------------------------------------------------------------------------------
+QString knumber_error::toStringInBase(int base, int precision) const
+{
+    Q_UNUSED(base);
+
+    return toString(precision);
+}
+
 //------------------------------------------------------------------------------
 // Name:
 //------------------------------------------------------------------------------
diff --git a/knumber/knumber_error.h b/knumber/knumber_error.h
index a560c019e4334399885ec2124daee1d8b3e192c1..cfe7c8af38fb059728c97a56e298d9a729a19e6b 100644
--- a/knumber/knumber_error.h
+++ b/knumber/knumber_error.h
@@ -30,6 +30,7 @@ public:
 
 public:
     QString toString(int precision) const override;
+    QString toStringInBase(int base, int precision) const override;
     quint64 toUint64() const override;
     qint64 toInt64() const override;
 
diff --git a/knumber/knumber_float.cpp b/knumber/knumber_float.cpp
index ea2ac1b113bc7f50e5736c8bc300475e638cb9bf..83d059a4ab6a7e6899c55aac98cf996554f8f388 100644
--- a/knumber/knumber_float.cpp
+++ b/knumber/knumber_float.cpp
@@ -620,6 +620,59 @@ QString knumber_float::toString(int precision) const
     return QLatin1String(&buf[0]);
 }
 
+//------------------------------------------------------------------------------
+// Name: toStringInBase
+//------------------------------------------------------------------------------
+QString knumber_float::toStringInBase(int base, int precision) const
+{
+    if (precision < 0) {
+        precision = 3 * mpf_get_default_prec() / 10;
+    }
+
+    const size_t bufferSize = std::max(precision + 2, 7);
+    QByteArray buffer(bufferSize, Qt::Uninitialized);
+    mpfr_exp_t exp;
+    mpfr_get_str(buffer.data(), &exp, base, precision, mpfr_, rounding_mode);
+
+    const auto trimTrailingZeros = [&buffer](const int from, const int count) {
+        auto i = std::find_if(buffer.crend() - from - count, buffer.crend() - from, [](const char c) {
+            return c != '0';
+        });
+        buffer.erase(i.base(), buffer.cbegin() + from + count);
+    };
+
+    const int offset = buffer.at(0) == '-' ? 1 : 0;
+    if (exp > precision || exp < -3) {
+        const int pos = offset + 1;
+        trimTrailingZeros(pos, precision - 1);
+        if (buffer.at(pos) != '\0') {
+            buffer.insert(pos, '.');
+        }
+        const char expPrefix = base == 2 || base == 16 ? 'p' : base > 10 ? '@' : 'e';
+        if (base == 2 || base == 16) {
+            exp = std::log2(std::pow(base, exp - 1));
+        } else {
+            --exp;
+        }
+
+        return QString::asprintf("%s%c%+.2li", buffer.constData(), expPrefix, exp);
+    }
+
+    if (exp <= 0) {
+        const int pos = offset;
+        trimTrailingZeros(pos, precision);
+        buffer.insert(pos, "0." + QByteArray(std::abs(exp), '0'));
+    } else if (exp < precision) {
+        const int pos = exp + offset;
+        trimTrailingZeros(pos, precision - exp);
+        if (buffer.at(pos) != '\0') {
+            buffer.insert(pos, '.');
+        }
+    }
+
+    return QLatin1String(buffer.constData());
+}
+
 //------------------------------------------------------------------------------
 // Name:
 //------------------------------------------------------------------------------
diff --git a/knumber/knumber_float.h b/knumber/knumber_float.h
index f33dcbbf5b5ee6fe6b5b1bdabe4c7ce56b58f1c2..e009bcf25d5c12d26d5dedcc2c1f07e0860871ac 100644
--- a/knumber/knumber_float.h
+++ b/knumber/knumber_float.h
@@ -43,6 +43,7 @@ private:
 
 public:
     QString toString(int precision) const override;
+    QString toStringInBase(int base, int precision) const override;
     quint64 toUint64() const override;
     qint64 toInt64() const override;
 
diff --git a/knumber/knumber_fraction.cpp b/knumber/knumber_fraction.cpp
index dde0c59e1efeece129bcfd42cd6fa02e4c39968c..eab6d98a6d10b8c6ec092648a156c7a414b8d545 100644
--- a/knumber/knumber_fraction.cpp
+++ b/knumber/knumber_fraction.cpp
@@ -781,6 +781,40 @@ QString knumber_fraction::toString(int precision) const
     }
 }
 
+//------------------------------------------------------------------------------
+// Name: toStringInBase
+//------------------------------------------------------------------------------
+QString knumber_fraction::toStringInBase(int base, int precision) const
+{
+    if (knumber_fraction::default_fractional_output) {
+        knumber_integer floor(this);
+        if (split_off_integer_for_fraction_output && !floor.is_zero()) {
+            knumber_integer numerator(0), denominator(0);
+            mpq_get_num(numerator.mpz_, mpq_);
+            mpq_get_den(denominator.mpz_, mpq_);
+
+            numerator.sub(floor.mul(&denominator));
+            if (numerator.sign() < 0) {
+                numerator.neg();
+            }
+
+            const QString floorStr = floor.toStringInBase(base, precision);
+            const QString numeratorStr = numerator.toStringInBase(base, precision);
+            const QString denominatorStr = denominator.toStringInBase(base, precision);
+
+            return QLatin1String("%1 %2/%3").arg(floorStr, numeratorStr, denominatorStr);
+        } else {
+            const size_t bufferSize = mpz_sizeinbase(mpq_numref(mpq_), base) + mpz_sizeinbase(mpq_denref(mpq_), base) + 3;
+            QByteArray buffer(bufferSize, Qt::Uninitialized);
+            mpq_get_str(buffer.data(), base, mpq_);
+
+            return QLatin1String(buffer.constData());
+        }
+    } else {
+        return knumber_float(this).toStringInBase(base, precision);
+    }
+}
+
 //------------------------------------------------------------------------------
 // Name:
 //------------------------------------------------------------------------------
diff --git a/knumber/knumber_fraction.h b/knumber/knumber_fraction.h
index 4c7ea26449ca79b46044ce4f586d5ec68ba1c8f4..651a28aeca04666b7efa8d2115a3668f91bd016f 100644
--- a/knumber/knumber_fraction.h
+++ b/knumber/knumber_fraction.h
@@ -41,6 +41,7 @@ public:
 
 public:
     QString toString(int precision) const override;
+    QString toStringInBase(int base, int precision) const override;
     quint64 toUint64() const override;
     qint64 toInt64() const override;
 
diff --git a/knumber/knumber_integer.cpp b/knumber/knumber_integer.cpp
index 594c9c8115b910156edf497f1c06944ce302b065..94e641bb3987a8e3903721644c49fb2bc0f189d4 100644
--- a/knumber/knumber_integer.cpp
+++ b/knumber/knumber_integer.cpp
@@ -709,6 +709,22 @@ QString knumber_integer::toString(int precision) const
     return QLatin1String(&buf[0]);
 }
 
+//------------------------------------------------------------------------------
+// Name: toStringInBase
+//------------------------------------------------------------------------------
+QString knumber_integer::toStringInBase(int base, int precision) const
+{
+    if (precision > 0) {
+        return knumber_float(this).toStringInBase(base, precision);
+    }
+
+    const size_t bufferSize = mpz_sizeinbase(mpz_, base) + 2;
+    QByteArray buffer(bufferSize, Qt::Uninitialized);
+    mpz_get_str(buffer.data(), base, mpz_);
+
+    return QLatin1String(buffer.constData());
+}
+
 //------------------------------------------------------------------------------
 // Name:
 //------------------------------------------------------------------------------
diff --git a/knumber/knumber_integer.h b/knumber/knumber_integer.h
index 97efd656f4c604abbd5649eb3c22b18485152d51..17b201e614161f6947b55a6701157680040c31b2 100644
--- a/knumber/knumber_integer.h
+++ b/knumber/knumber_integer.h
@@ -33,6 +33,7 @@ public:
 
 public:
     QString toString(int precision) const override;
+    QString toStringInBase(int base, int precision) const override;
     quint64 toUint64() const override;
     qint64 toInt64() const override;
 
openSUSE Build Service is sponsored by