WebUI: replace rounding function from MooTools

The `round()` returning floating point number is not a good idea. This is due to floating point
representation is imprecise and sometimes it cannot faithfully represent a number, for example
`0.09 + 0.01 !== 0.1 `. Therefore, it should be avoided and/or utilize other function
to achieve the goal.

Also, improve `window.qBittorrent.Misc.toFixedPointString()` and add test cases.

PR #22281.
This commit is contained in:
Chocobo1
2025-02-17 15:11:55 +08:00
committed by GitHub
parent 8da43a4054
commit 955688c125
8 changed files with 134 additions and 47 deletions

View File

@@ -1231,9 +1231,7 @@ window.qBittorrent.DynamicTable ??= (() => {
// progress
this.columns["progress"].updateTd = function(td, row) {
const progress = this.getRowValue(row);
let progressFormatted = (progress * 100).round(1);
if ((progressFormatted === 100.0) && (progress !== 1.0))
progressFormatted = 99.9;
const progressFormatted = window.qBittorrent.Misc.toFixedPointString((progress * 100), 1);
const div = td.firstElementChild;
if (div !== null) {
@@ -1782,10 +1780,7 @@ window.qBittorrent.DynamicTable ??= (() => {
// progress
this.columns["progress"].updateTd = function(td, row) {
const progress = this.getRowValue(row);
let progressFormatted = (progress * 100).round(1);
if ((progressFormatted === 100.0) && (progress !== 1.0))
progressFormatted = 99.9;
progressFormatted += "%";
const progressFormatted = `${window.qBittorrent.Misc.toFixedPointString((progress * 100), 1)}%`;
td.textContent = progressFormatted;
td.title = progressFormatted;
};

View File

@@ -92,6 +92,9 @@ window.qBittorrent.Misc ??= (() => {
* JS counterpart of the function in src/misc.cpp
*/
const friendlyUnit = (value, isSpeed) => {
if ((value === undefined) || (value === null) || Number.isNaN(value) || (value < 0))
return "QBT_TR(Unknown)QBT_TR[CONTEXT=misc]";
const units = [
"QBT_TR(B)QBT_TR[CONTEXT=misc]",
"QBT_TR(KiB)QBT_TR[CONTEXT=misc]",
@@ -102,15 +105,6 @@ window.qBittorrent.Misc ??= (() => {
"QBT_TR(EiB)QBT_TR[CONTEXT=misc]"
];
if ((value === undefined) || (value === null) || (value < 0))
return "QBT_TR(Unknown)QBT_TR[CONTEXT=misc]";
let i = 0;
while ((value >= 1024.0) && (i < 6)) {
value /= 1024.0;
++i;
}
const friendlyUnitPrecision = (sizeUnit) => {
if (sizeUnit <= 2) // KiB, MiB
return 1;
@@ -120,15 +114,20 @@ window.qBittorrent.Misc ??= (() => {
return 3;
};
let i = 0;
while ((value >= 1024) && (i < 6)) {
value /= 1024;
++i;
}
let ret;
if (i === 0) {
ret = `${value} ${units[i]}`;
}
else {
const precision = friendlyUnitPrecision(i);
const offset = Math.pow(10, precision);
// Don't round up
ret = `${(Math.floor(offset * value) / offset).toFixed(precision)} ${units[i]}`;
ret = `${toFixedPointString(value, precision)} ${units[i]}`;
}
if (isSpeed)
@@ -163,12 +162,12 @@ window.qBittorrent.Misc ??= (() => {
};
const friendlyPercentage = (value) => {
let percentage = (value * 100).round(1);
let percentage = value * 100;
if (Number.isNaN(percentage) || (percentage < 0))
percentage = 0;
if (percentage > 100)
percentage = 100;
return `${percentage.toFixed(1)}%`;
return `${toFixedPointString(percentage, 1)}%`;
};
/*
@@ -225,13 +224,27 @@ window.qBittorrent.Misc ??= (() => {
};
const toFixedPointString = (number, digits) => {
// Do not round up number
const power = Math.pow(10, digits);
return (Math.floor(power * number) / power).toFixed(digits);
if (Number.isNaN(number))
return number.toString();
const sign = (number < 0) ? "-" : "";
// Do not round up `number`
// Small floating point numbers are imprecise, thus process as a String
const tmp = Math.trunc(`${Math.abs(number)}e${digits}`).toString();
if (digits <= 0) {
return (tmp === "0") ? tmp : `${sign}${tmp}`;
}
else if (digits < tmp.length) {
const idx = tmp.length - digits;
return `${sign}${tmp.slice(0, idx)}.${tmp.slice(idx)}`;
}
else {
const zeros = "0".repeat(digits - tmp.length);
return `${sign}0.${zeros}${tmp}`;
}
};
/**
*
* @param {String} text the text to search
* @param {Array<String>} terms terms to search for within the text
* @returns {Boolean} true if all terms match the text, false otherwise

View File

@@ -116,13 +116,13 @@ window.qBittorrent.ProgressBar ??= (() => {
}
function ProgressBar_setValue(value) {
value = parseFloat(value);
value = Number(value);
if (Number.isNaN(value))
value = 0;
value = Math.min(Math.max(value, 0), 100);
this.vals.value = value;
const displayedValue = `${value.round(1).toFixed(1)}%`;
const displayedValue = `${window.qBittorrent.Misc.toFixedPointString(value, 1)}%`;
this.vals.dark.textContent = displayedValue;
this.vals.light.textContent = displayedValue;

View File

@@ -383,25 +383,18 @@ window.qBittorrent.PropFiles ??= (() => {
is_seed = (files.length > 0) ? files[0].is_seed : true;
const rows = files.map((file, index) => {
let progress = (file.progress * 100).round(1);
if ((progress === 100) && (file.progress < 1))
progress = 99.9;
const ignore = (file.priority === FilePriority.Ignored);
const checked = (ignore ? TriState.Unchecked : TriState.Checked);
const remaining = (ignore ? 0 : (file.size * (1.0 - file.progress)));
const row = {
fileId: index,
checked: checked,
checked: (ignore ? TriState.Unchecked : TriState.Checked),
fileName: file.name,
name: window.qBittorrent.Filesystem.fileName(file.name),
size: file.size,
progress: progress,
progress: window.qBittorrent.Misc.toFixedPointString((file.progress * 100), 1),
priority: normalizePriority(file.priority),
remaining: remaining,
remaining: (ignore ? 0 : (file.size * (1 - file.progress))),
availability: file.availability
};
return row;
});

View File

@@ -64,7 +64,7 @@ MochaUI.extend({
new Slider($("uplimitSliderarea"), $("uplimitSliderknob"), {
steps: maximum,
offset: 0,
initialStep: up_limit.round(),
initialStep: Math.round(up_limit),
onChange: (pos) => {
if (pos > 0) {
$("uplimitUpdatevalue").value = pos;
@@ -82,7 +82,7 @@ MochaUI.extend({
$("upLimitUnit").style.visibility = "hidden";
}
else {
$("uplimitUpdatevalue").value = up_limit.round();
$("uplimitUpdatevalue").value = Math.round(up_limit);
$("upLimitUnit").style.visibility = "visible";
}
}
@@ -111,7 +111,7 @@ MochaUI.extend({
new Slider($("uplimitSliderarea"), $("uplimitSliderknob"), {
steps: maximum,
offset: 0,
initialStep: (up_limit / 1024.0).round(),
initialStep: Math.round(up_limit / 1024),
onChange: (pos) => {
if (pos > 0) {
$("uplimitUpdatevalue").value = pos;
@@ -129,7 +129,7 @@ MochaUI.extend({
$("upLimitUnit").style.visibility = "hidden";
}
else {
$("uplimitUpdatevalue").value = (up_limit / 1024.0).round();
$("uplimitUpdatevalue").value = Math.round(up_limit / 1024);
$("upLimitUnit").style.visibility = "visible";
}
});
@@ -173,7 +173,7 @@ MochaUI.extend({
new Slider($("dllimitSliderarea"), $("dllimitSliderknob"), {
steps: maximum,
offset: 0,
initialStep: dl_limit.round(),
initialStep: Math.round(dl_limit),
onChange: (pos) => {
if (pos > 0) {
$("dllimitUpdatevalue").value = pos;
@@ -191,7 +191,7 @@ MochaUI.extend({
$("dlLimitUnit").style.visibility = "hidden";
}
else {
$("dllimitUpdatevalue").value = dl_limit.round();
$("dllimitUpdatevalue").value = Math.round(dl_limit);
$("dlLimitUnit").style.visibility = "visible";
}
}
@@ -220,7 +220,7 @@ MochaUI.extend({
new Slider($("dllimitSliderarea"), $("dllimitSliderknob"), {
steps: maximum,
offset: 0,
initialStep: (dl_limit / 1024.0).round(),
initialStep: Math.round(dl_limit / 1024),
onChange: (pos) => {
if (pos > 0) {
$("dllimitUpdatevalue").value = pos;
@@ -238,7 +238,7 @@ MochaUI.extend({
$("dlLimitUnit").style.visibility = "hidden";
}
else {
$("dllimitUpdatevalue").value = (dl_limit / 1024.0).round();
$("dllimitUpdatevalue").value = Math.round(dl_limit / 1024);
$("dlLimitUnit").style.visibility = "visible";
}
});