review fix: avoid bigint cache-hit formatting

This commit is contained in:
pku-xht
2026-08-19 14:41:23 +08:00
parent a4da0f40d5
commit fa2ce12162
5 changed files with 56 additions and 23 deletions
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-19-high-cache-hit-decimal-display.md
2026-08-19-high-cache-hit-decimal-display.md: 29e0ebc11ae690b918c7e9364f29635de1e50f2a
2026-08-19-high-cache-hit-decimal-display.zh.md: 28cd99a4c321cc4da0ce03142e2fbf438539b0ff
2026-08-19-high-cache-hit-decimal-display.md: 952d838fdf330175915e13ef767fd80d145506b2
2026-08-19-high-cache-hit-decimal-display.zh.md: 4a72becd608e756507f3d5a6fd051b4b2b437964
@@ -21,7 +21,7 @@ Users therefore could not distinguish a nearly complete cache hit from a true fu
| Non-full ratio whose current rounding is 100% | Minimum decimal precision whose rounded result is below 100% |
| 100% | `100%` |
Every non-empty ratio starts at zero decimal places. A non-full ratio increases precision one place at a time only while rounding would produce 100%, so `99.1%` and `99.49%` remain `99%`, while `99.5%`, `99.95%`, and `99.995%` retain one, two, and three decimal places respectively. `StatsLine` scales and rounds the integer token counts with `bigint`, which avoids floating-point formatting limits and imposes no precision cap or substitute label. A full hit does not carry a redundant decimal. The same derived string feeds the inline row and its overflow tooltip.
Every non-empty ratio starts at zero decimal places. A non-full ratio increases precision one place at a time only while rounding would produce 100%, so `99.1%` and `99.49%` remain `99%`, while `99.5%`, `99.95%`, and `99.995%` retain one, two, and three decimal places respectively. `StatsLine` uses exact small-factor comparisons over the safe-integer token counts, then scales the near-full gap only while the intermediate remains within that range. This avoids floating-point tie errors without imposing a precision cap or substitute label. A full hit does not carry a redundant decimal. The same derived string feeds the inline row and its overflow tooltip.
## Ownership and lifecycle
@@ -31,7 +31,7 @@ Live updates, reload replay, and reconnect recovery all restore the same `tokenU
## Verification
The component spec pins the zero denominator, ordinary integer rounding, each precision boundary through three decimal places, a near-full cumulative sample that needs fourteen decimal places, the true `100%` result, both locales, and equality between inline and tooltip values. The assembled `lifecycle-chrome` replay sidecar selects `9,950 / 10,000 = 99.5%` as a deterministic ratio that integer rounding would misreport as 100% while the base session fixture remains recordable; the live assertion and post-reload browser snapshot both display `99.5%` without another model call.
The component spec pins the zero denominator, ordinary integer rounding, half-step rounding at several decimal precisions, each precision boundary through three decimal places, a near-full cumulative sample that needs fourteen decimal places, the true `100%` result, both locales, and equality between inline and tooltip values. The assembled `lifecycle-chrome` replay sidecar selects `9,950 / 10,000 = 99.5%` as a deterministic ratio that integer rounding would misreport as 100% while the base session fixture remains recordable; the live assertion and post-reload browser snapshot both display `99.5%` without another model call.
## Alternatives considered
@@ -21,7 +21,7 @@ Web 会话统计行会把所有非空缓存命中率舍入为整数。真实比
| 当前舍入结果为 100% 的非满命中 | 舍入结果低于 100% 所需的最少小数位 |
| 100% | `100%` |
所有非空比率都从零位小数开始。非满命中只有在舍入结果会成为 100% 时才逐位增加精度,因此 `99.1%``99.49%` 仍显示为 `99%`,而 `99.5%``99.95%``99.995%` 分别保留一位、两位与三位小数。`StatsLine` 使用 `bigint` 缩放并舍入整数 token 计数,从而避开浮点格式化限制,且不设置精度上限或替代文案。真实满命中不会携带多余的小数。同一份派生字符串同时用于行内统计与溢出 tooltip。
所有非空比率都从零位小数开始。非满命中只有在舍入结果会成为 100% 时才逐位增加精度,因此 `99.1%``99.49%` 仍显示为 `99%`,而 `99.5%``99.95%``99.995%` 分别保留一位、两位与三位小数。`StatsLine` 对安全整数 token 计数执行精确的小因子比较,并且只在中间值仍处于该范围内时缩放接近满命中的差值。该算法既避开浮点临界值误差,也不设置精度上限或替代文案。真实满命中不会携带多余的小数。同一份派生字符串同时用于行内统计与溢出 tooltip。
## 归属与生命周期
@@ -31,7 +31,7 @@ token-meter 继续从完整持久会话日志折叠用量。标准投影值变
## 验证
组件测试固定了零分母、普通整数舍入、直至三位小数的各个精度边界、需要十四位小数的近满累计样本、真实 `100%`、两种 locale,以及行内值与 tooltip 值的一致性。组装后的 `lifecycle-chrome` replay sidecar 将 `9,950 / 10,000 = 99.5%` 选作确定性测试输入;该比率按整数舍入会误报为 100%,同时基础会话 fixture 仍可重录。活跃页面断言与刷新后的浏览器快照都会显示 `99.5%`,且不会产生额外模型调用。
组件测试固定了零分母、普通整数舍入、多个小数精度上的半步舍入、直至三位小数的各个精度边界、需要十四位小数的近满累计样本、真实 `100%`、两种 locale,以及行内值与 tooltip 值的一致性。组装后的 `lifecycle-chrome` replay sidecar 将 `9,950 / 10,000 = 99.5%` 选作确定性测试输入;该比率按整数舍入会误报为 100%,同时基础会话 fixture 仍可重录。活跃页面断言与刷新后的浏览器快照都会显示 `99.5%`,且不会产生额外模型调用。
## 备选方案
@@ -101,6 +101,26 @@ export function formatDuration(ms: number): string {
return `${Math.floor(whole / 60)}m${whole % 60}s`
}
/** Round a cache-read ratio to an integer percentage, with positive ties rounded up. */
function roundedIntegerPercent(cacheReadTokens: number, denominator: number): number {
const denominatorQuotient = Math.floor(denominator / 200)
const denominatorRemainder = denominator % 200
let lower = 0
let upper = 100
while (lower < upper) {
const candidate = Math.floor((lower + upper + 1) / 2)
const factor = candidate * 2 - 1
const threshold = factor * denominatorQuotient
+ Math.ceil(factor * denominatorRemainder / 200)
if (cacheReadTokens >= threshold) {
lower = candidate
} else {
upper = candidate - 1
}
}
return lower
}
/**
* Display-ready cache-hit share of prompt-side input over the whole durable log.
* @param usage - the session's token-usage projection value.
@@ -109,26 +129,36 @@ export function formatDuration(ms: number): string {
* 100, and no billed input returns null.
*/
export function cacheHitPercent(usage: TokenUsageProjection): string | null {
const cacheReadTokens = BigInt(usage.cacheReadTokens)
const denominator = BigInt(usage.uncachedInputTokens)
+ cacheReadTokens
+ BigInt(usage.cacheWriteTokens)
if (denominator === 0n) return null
if (cacheReadTokens === denominator) return '100'
const denominator = billedInputTokens(usage)
if (denominator === 0) return null
const missedInputTokens = usage.uncachedInputTokens + usage.cacheWriteTokens
if (missedInputTokens === 0) return '100'
let decimalPlaces = 0
let decimalScale = 1n
while (true) {
const fullHit = 100n * decimalScale
const rounded = (2n * cacheReadTokens * fullHit + denominator) / (2n * denominator)
if (rounded < fullHit) {
if (decimalPlaces === 0) return rounded.toString()
const digits = rounded.toString().padStart(decimalPlaces + 1, '0')
return `${digits.slice(0, -decimalPlaces)}.${digits.slice(-decimalPlaces)}`
}
const integerPercent = roundedIntegerPercent(usage.cacheReadTokens, denominator)
if (integerPercent < 100) return String(integerPercent)
// At the first distinguishing precision, the rounded result is 100 minus
// one to five units in the final decimal place. Scale only while the next
// multiplication remains at or below the denominator, then derive that
// final digit through exact small-factor comparisons.
let decimalPlaces = 1
let scaledDoubleGap = missedInputTokens * 200
const denominatorTens = Math.floor(denominator / 10)
while (scaledDoubleGap <= denominatorTens) {
scaledDoubleGap *= 10
decimalPlaces += 1
decimalScale *= 10n
}
const denominatorOnes = denominator % 10
let roundedLoss = 5
for (let loss = 1; loss < 5; loss += 1) {
const factor = loss * 2 + 1
const threshold = factor * denominatorTens + Math.floor(factor * denominatorOnes / 10)
if (scaledDoubleGap <= threshold) {
roundedLoss = loss
break
}
}
return `99.${'9'.repeat(decimalPlaces - 1)}${10 - roundedLoss}`
}
/**
@@ -216,7 +216,10 @@ describe('StatsLine', () => {
{ actual: '99.5%', tokenUsageValue: tokenUsage(995, 5), expected: 'Cache hit 99.5%' },
{ actual: '99.94%', tokenUsageValue: tokenUsage(9_994, 6), expected: 'Cache hit 99.9%' },
{ actual: '99.95%', tokenUsageValue: tokenUsage(9_995, 5), expected: 'Cache hit 99.95%' },
{ actual: '99.955%', tokenUsageValue: tokenUsage(19_991, 9), expected: 'Cache hit 99.96%' },
{ actual: '99.985%', tokenUsageValue: tokenUsage(19_997, 3), expected: 'Cache hit 99.99%' },
{ actual: '99.995%', tokenUsageValue: tokenUsage(19_999, 1), expected: 'Cache hit 99.995%' },
{ actual: '99.9975%', tokenUsageValue: tokenUsage(39_999, 1), expected: 'Cache hit 99.998%' },
{
actual: 'the closest non-full ratio available from safe integer cumulative counts',
tokenUsageValue: tokenUsage(Number.MAX_SAFE_INTEGER - 1, 1),