Skip to content

Commit 393f19e

Browse files
authored
Merge pull request #81 from LeoLin990405/fix/compact-stat-rounding
fix: round compact stat badges to nearest instead of truncating
2 parents ce87b83 + 596f438 commit 393f19e

3 files changed

Lines changed: 67 additions & 8 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## 0.8.3 - Unreleased
44

5+
- Round compact stat badges (issue, PR, star, and release download counts) to the nearest thousand and million instead of truncating, so a count like 19,999 shows as "20K" rather than "19K" and matches GitHub's own compact numbers (thanks @LeoLin990405). (#81)
6+
57
## 0.8.2 - 2026-06-12
68

79
- Streamline the GitHub API submenu by showing sample age once, while moving shared-budget guidance into a dedicated API settings tab.

Sources/RepoBar/Support/StatValueFormatter.swift

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,21 @@ enum StatValueFormatter {
88
return "\(short)K"
99
}
1010
if value < 1_000_000 {
11-
return "\(value / 1000)K"
11+
let thousands = self.rounded(value, divisor: 1000)
12+
return thousands >= 1000 ? "1M" : "\(thousands)K"
1213
}
1314
if value < 10_000_000 {
1415
let short = self.oneDecimal(value, divisor: 1_000_000)
1516
return "\(short)M"
1617
}
17-
if value >= 1_000_000_000 {
18-
return "999M"
19-
}
20-
return "\(value / 1_000_000)M"
18+
let millions = self.rounded(value, divisor: 1_000_000)
19+
return millions >= 1000 ? "999M" : "\(millions)M"
20+
}
21+
22+
private static func rounded(_ value: Int, divisor: Int) -> Int {
23+
// Round half up without an intermediate `value + divisor/2` that could
24+
// overflow near Int.max: compare the remainder against half the divisor.
25+
value / divisor + (value % divisor * 2 >= divisor ? 1 : 0)
2126
}
2227

2328
private static func oneDecimal(_ value: Int, divisor: Double) -> String {
@@ -26,9 +31,6 @@ enum StatValueFormatter {
2631
if formatted.hasSuffix(".0") {
2732
return String(formatted.dropLast(2))
2833
}
29-
if formatted.hasPrefix("10") {
30-
return "10"
31-
}
3234
return formatted
3335
}
3436
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
@testable import RepoBar
2+
import Testing
3+
4+
struct StatValueFormatterTests {
5+
@Test
6+
func `passes through values below one thousand`() {
7+
#expect(StatValueFormatter.compact(0) == "0")
8+
#expect(StatValueFormatter.compact(42) == "42")
9+
#expect(StatValueFormatter.compact(999) == "999")
10+
}
11+
12+
@Test
13+
func `keeps one decimal below ten thousand`() {
14+
#expect(StatValueFormatter.compact(1000) == "1K")
15+
#expect(StatValueFormatter.compact(1500) == "1.5K")
16+
#expect(StatValueFormatter.compact(1999) == "2K")
17+
#expect(StatValueFormatter.compact(9999) == "10K")
18+
}
19+
20+
@Test
21+
func `rounds thousands to nearest instead of truncating`() {
22+
#expect(StatValueFormatter.compact(10000) == "10K")
23+
#expect(StatValueFormatter.compact(10500) == "11K")
24+
#expect(StatValueFormatter.compact(19999) == "20K")
25+
#expect(StatValueFormatter.compact(99500) == "100K")
26+
#expect(StatValueFormatter.compact(123_456) == "123K")
27+
}
28+
29+
@Test
30+
func `rolls rounded thousands up into millions`() {
31+
#expect(StatValueFormatter.compact(999_499) == "999K")
32+
#expect(StatValueFormatter.compact(999_500) == "1M")
33+
#expect(StatValueFormatter.compact(999_999) == "1M")
34+
}
35+
36+
@Test
37+
func `keeps one decimal below ten million`() {
38+
#expect(StatValueFormatter.compact(1_000_000) == "1M")
39+
#expect(StatValueFormatter.compact(1_500_000) == "1.5M")
40+
#expect(StatValueFormatter.compact(9_999_999) == "10M")
41+
}
42+
43+
@Test
44+
func `rounds millions and caps oversized counts`() {
45+
#expect(StatValueFormatter.compact(10_500_000) == "11M")
46+
#expect(StatValueFormatter.compact(99_999_999) == "100M")
47+
#expect(StatValueFormatter.compact(999_000_000) == "999M")
48+
#expect(StatValueFormatter.compact(1_000_000_000) == "999M")
49+
}
50+
51+
@Test
52+
func `caps Int.max without integer overflow`() {
53+
#expect(StatValueFormatter.compact(Int.max) == "999M")
54+
}
55+
}

0 commit comments

Comments
 (0)