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;