From c717278deedaf5ff437be13a15bca48d4421bfb1 Mon Sep 17 00:00:00 2001 From: pchang718 Date: Sat, 16 May 2026 18:17:13 +0800 Subject: [PATCH] Refactor: update coefficient mapping to a1=-1-r, a2=r+delta and remove 0.5 step --- src/App.vue | 22 +++++++------- src/app-options.js | 73 +++++++++++++++------------------------------- 2 files changed, 34 insertions(+), 61 deletions(-) diff --git a/src/App.vue b/src/App.vue index e2b8b05..95b58bf 100644 --- a/src/App.vue +++ b/src/App.vue @@ -451,11 +451,11 @@
- r = (a2 - a1 - 1) / 2 + r = -1 - a1 {{ formatFineCoeff(currentR) }}
- δ = (a1 + a2 + 1) / 2 + δ = a1 + a2 + 1 {{ formatFineCoeff(currentDelta) }}
@@ -463,7 +463,7 @@

運算說明:
- δ = (a1 + a2 + 1) / 2,r = (a2 - a1 - 1) / 2。更改數值會自動更新圖表;桌機可用 ± 或 shift + 滾輪,觸控時按住數值面板上下拖曳。 + a1 = -1 - r, a2 = r + δ。更改數值會自動更新圖表;桌機可用 ± 或 shift + 滾輪,觸控時按住數值面板上下拖曳。

@@ -605,21 +605,21 @@ {{ formatFixedFineCoeff(fixedCurrentDelta) }} -
+
r
+ class="stepper-button w-full h-[42px] flex items-center justify-center rounded-full role-bg-primary-soft role-hover-primary-soft role-text-primary transition-all duration-150 active:scale-90 text-lg font-bold shadow-sm">- + class="min-w-0 w-full bg-white dark:bg-gray-800 border border-slate-200 dark:border-gray-700 rounded-lg p-3 text-xs font-mono text-center text-slate-700 dark:text-gray-300"> + class="stepper-button w-full h-[42px] flex items-center justify-center rounded-full role-bg-primary-soft role-hover-primary-soft role-text-primary transition-all duration-150 active:scale-90 text-lg font-bold shadow-sm">+
@@ -627,18 +627,18 @@
- r = (a2 - a1 - a0) / 2 + r = -a0 - a1 {{ formatFixedFineCoeff(fixedCurrentR) }}
- δ = (a1 + a2 + a0) / 2 + δ = a1 + a2 + a0 {{ formatFixedFineCoeff(fixedCurrentDelta) }}

運算說明:
- a0 = 2^{{ shiftBitsA }},δ = (a1 + a2 + a0) / 2,r = (a2 - a1 - a0) / 2。 + a0 = 2^{{ shiftBitsA }},a1 = -a0 - r, a2 = r + δ。

diff --git a/src/app-options.js b/src/app-options.js index 4c48092..88821a1 100755 --- a/src/app-options.js +++ b/src/app-options.js @@ -58,7 +58,7 @@ export default { shiftBitRepeatDelayTimer: null, shiftBitRepeatTimer: null, shiftBitDrag: null, - fixedAFineStep: 0.5, + fixedAFineStep: 1, fixedAFineDrag: null, fixedAFineRepeatDelayTimer: null, fixedAFineRepeatTimer: null, @@ -171,10 +171,13 @@ export default { return a[2] || 0; }, currentDelta() { - return (this.currentA1 + this.currentA2 + 1) / 2; + // New Model: a1 = -1 - r, a2 = r + delta + // => delta = a1 + a2 + 1 + return this.currentA1 + this.currentA2 + 1; }, currentR() { - return (this.currentA2 - this.currentA1 - 1) / 2; + // New Model: a1 = -1 - r => r = -1 - a1 + return -1 - this.currentA1; }, currentA1A2Diff() { return this.currentA1 - this.currentA2; @@ -192,10 +195,13 @@ export default { return this.fixedPointCoeffs.a[2]?.val || 0; }, fixedCurrentDelta() { - return (this.fixedCurrentA1 + this.fixedCurrentA2 + this.fixedA0Int) / 2; + // New Model: a1 = -a0 - r, a2 = r + delta + // => delta = a1 + a2 + a0 + return this.fixedCurrentA1 + this.fixedCurrentA2 + this.fixedA0Int; }, fixedCurrentR() { - return (this.fixedCurrentA2 - this.fixedCurrentA1 - this.fixedA0Int) / 2; + // New Model: a1 = -a0 - r => r = -a0 - a1 + return -this.fixedA0Int - this.fixedCurrentA1; }, fixedCurrentA1A2Diff() { return this.fixedCurrentA1 - this.fixedCurrentA2; @@ -337,7 +343,7 @@ export default { this.aFineStep = Number(clampedStep.toPrecision(12)); }, adjustFixedAFineStep(direction) { - const steps = [0.5, 1, 2, 4, 8, 32, 256, 1024, 4096, 16384, 65536]; + const steps = [1, 2, 4, 8, 32, 256, 1024, 4096, 16384, 65536]; let currentIdx = steps.indexOf(this.fixedAFineStep); // 如果當前值不在數列中,找最接近的一個 @@ -358,7 +364,8 @@ export default { const nextDelta = Number(delta); const nextR = Number(r); if (!Number.isFinite(nextDelta) || !Number.isFinite(nextR)) return; - this.setA1A2(-1 - nextR + nextDelta, nextR + nextDelta); + // New Model: a1 = -1 - r, a2 = r + delta + this.setA1A2(-1 - nextR, nextR + nextDelta); }, setAFineDirect(target, rawValue) { const nextValue = parseFloat(rawValue); @@ -413,20 +420,14 @@ export default { window.removeEventListener('pointercancel', this.stopAFineDrag); }, setFixedDeltaR(delta, r) { - // 使用「兩倍值」空間進行整數運算 - let S = Math.round(delta * 2); - let D = Math.round(r * 2); + const nextDelta = Math.round(delta); + const nextR = Math.round(r); const a0 = this.fixedA0Int; - // 核心邏輯:a1, a2 為整數的充要條件是 S 與 D 的奇偶性相同 - // 這裡採用「優先滿足目標值」的策略:如果奇偶不同,則微調其中一個 - // 由於此函數由 UI 直接調用,我們無法判斷使用者剛才改了誰 - // 所以我們預設採用 Math.round 來尋找最接近的整數組合 - const a2 = Math.round((S + D) / 2); - const a1 = a2 - D - a0; - - this.fixedOverrides.a[1] = a1; - this.fixedOverrides.a[2] = a2; + // New Model: a1 = -a0 - r, a2 = r + delta + // If r and delta are integers, a1 and a2 are guaranteed to be integers. + this.fixedOverrides.a[1] = -a0 - nextR; + this.fixedOverrides.a[2] = nextR + nextDelta; this.debouncedUpdateBode(); }, setFixedAFineDirect(target, rawValue) { @@ -439,40 +440,12 @@ export default { } }, adjustFixedAFineValue(target, steps) { - let S = Math.round(this.fixedCurrentDelta * 2); - let D = Math.round(this.fixedCurrentR * 2); - const dVal = Math.round(steps * this.fixedAFineStep * 2); - const a0 = this.fixedA0Int; - + const dVal = Math.round(steps * this.fixedAFineStep); if (target === 'delta') { - // 優先保證 delta 變動到位 - S += dVal; - // 檢查奇偶性是否匹配 - if ((S % 2 + 2) % 2 !== (D % 2 + 2) % 2) { - // 核心:震盪平衡邏輯。 - // 如果 S 變為偶數,D 應該變為最近的偶數;如果 S 變為奇數,D 應該變為最近的奇數。 - // 我們判斷目前 D 是在 .5 還是 .0,然後向反方向跳轉,防止單向漂移。 - if (D % 2 !== 0) { - D -= 1; // 10.5 -> 10.0 - } else { - D += 1; // 10.0 -> 10.5 - } - } + this.setFixedDeltaR(this.fixedCurrentDelta + dVal, this.fixedCurrentR); } else if (target === 'r') { - // 如果 r 沒被禁用,則優先保證 r 變動到位 - D += dVal; - if ((S % 2 + 2) % 2 !== (D % 2 + 2) % 2) { - if (S % 2 !== 0) S -= 1; - else S += 1; - } + this.setFixedDeltaR(this.fixedCurrentDelta, this.fixedCurrentR + dVal); } - - const a2 = (S + D) / 2; - const a1 = a2 - D - a0; - - this.fixedOverrides.a[1] = a1; - this.fixedOverrides.a[2] = a2; - this.debouncedUpdateBode(); }, handleFixedAFineWheel(target, event) { if (!event.shiftKey) {