You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nmWTAI-Platform/Src/nmNum/nmRender/nmNumericDisplayFormat.cpp

96 lines
3.7 KiB
C++

#include "nmNumericDisplayFormat.h"
#include <QMap>
#include <QSet>
#include <float.h>
#include <cmath>
namespace {
/** @brief 删除尾零并缩短指数;只清理文字,不把小的非零值当作零。 */
QString trimNumber(QString sText)
{
const int nExponent = sText.indexOf(QLatin1Char('e'));
QString sMantissa = nExponent < 0 ? sText : sText.left(nExponent);
if(sMantissa.contains(QLatin1Char('.'))) {
while(sMantissa.endsWith(QLatin1Char('0'))) sMantissa.chop(1);
if(sMantissa.endsWith(QLatin1Char('.'))) sMantissa.chop(1);
}
if(sMantissa == QLatin1String("-0")) sMantissa = QLatin1String("0");
if(nExponent < 0) return sMantissa;
const int nPower = sText.mid(nExponent + 1).toInt();
return sMantissa + QLatin1String("e") + (nPower >= 0 ? QLatin1String("+") : QLatin1String(""))
+ QString::number(nPower);
}
/** @brief 从默认显示逐级提升精度;最终使用十七位有效数字确保相邻 double 可区分。 */
QString formatWithExtraDigits(double dValue, int nExtraDigits)
{
if(!nmNumericDisplayFormat::isFinite(dValue)) return QString::fromLatin1("--");
if(dValue == 0.0) return QString::fromLatin1("0");
if(nExtraDigits >= 17) return nmNumericDisplayFormat::fullPrecision(dValue);
const double dMagnitude = std::fabs(dValue);
if(dMagnitude < 1e-4 || dMagnitude >= 1e8) {
return trimNumber(QString::number(dValue, 'e', qMin(16, 3 + nExtraDigits)));
}
const int nDecimals = 4 + nExtraDigits;
const int nIntegerDigits = static_cast<int>(std::floor(std::log10(dMagnitude))) + 1;
if(nIntegerDigits + nDecimals >= 17) return nmNumericDisplayFormat::fullPrecision(dValue);
const QString sFixed = trimNumber(QString::number(dValue, 'f', nDecimals));
// 普通范围上沿四舍五入后也使用科学计数法,避免产生一串整数位。
if(std::fabs(sFixed.toDouble()) >= 1e8) {
return trimNumber(QString::number(dValue, 'e', qMin(16, 3 + nExtraDigits)));
}
return sFixed;
}
}
bool nmNumericDisplayFormat::isFinite(double dValue)
{
return dValue == dValue && dValue >= -DBL_MAX && dValue <= DBL_MAX;
}
QString nmNumericDisplayFormat::format(double dValue)
{
return formatWithExtraDigits(dValue, 0);
}
QString nmNumericDisplayFormat::fullPrecision(double dValue)
{
if(!isFinite(dValue)) return QString::fromLatin1("--");
if(dValue == 0.0) return QString::fromLatin1("0");
return trimNumber(QString::number(dValue, 'g', 17));
}
QStringList nmNumericDisplayFormat::formatDistinct(const QVector<double>& vecValues)
{
QVector<int> vecExtraDigits(vecValues.size(), 0);
QStringList listTexts;
// 时间列表可能较长,用映射查找冲突,避免两两比较产生平方复杂度。
for(;;) {
listTexts.clear();
QMap<QString, double> mapFirstValues;
QSet<QString> setCollisions;
for(int nIndex = 0; nIndex < vecValues.size(); ++nIndex) {
const double dValue = vecValues[nIndex];
const QString sText = formatWithExtraDigits(dValue, vecExtraDigits[nIndex]);
listTexts.append(sText);
if(!isFinite(dValue)) continue;
if(mapFirstValues.contains(sText) && mapFirstValues.value(sText) != dValue) {
setCollisions.insert(sText);
} else {
mapFirstValues.insert(sText, dValue);
}
}
if(setCollisions.isEmpty()) break;
bool bIncreased = false;
for(int nIndex = 0; nIndex < vecValues.size(); ++nIndex) {
if(setCollisions.contains(listTexts[nIndex]) && vecExtraDigits[nIndex] < 17) {
++vecExtraDigits[nIndex];
bIncreased = true;
}
}
if(!bIncreased) break;
}
return listTexts;
}