Capacity from Both Sides
Anton1. The problem
First, why the old capacity math had to go at all. While auditing the old method I found an arithmetic bug that inflated the final value by 1 to 8%, depending on session length (the cumulative method was the main victim). Simply fixing the bug would have dropped cumulative estimates by 3–4% on average for everyone – overnight, for no visible reason.
Digging further, it turned out that the old approach – taking the mean of a pool of numbers – systematically understates capacity, because an individual session tends to under-measure for a whole bunch of reasons. The arithmetic bug organically inflated the value and smoothed that problem over; in the end the user saw an estimate that looked like the truth.
So fixing just the arithmetic, without changing the approach, would have crashed capacity values for every user out there. The problem needed a different way of looking at the data. The way out came from statistics – the quantile.
2. The solution
After hundreds of tests on real data from real devices, the winner was clear-cut: the weighted p80 quantile.
To understand the choice, you need to understand the nature of the errors. Almost everything that makes a single session lie pulls the value down: trickle charge slips past the full-charge detector, heat skews the percent scale, sensor error piles up over slow overnight charges. There's practically nothing that inflates an honest session. So the mean of such measurements is always biased low – and crawls lower as "bad" sessions accumulate. The upper edge of recent measurements, though, is what the battery has demonstrably accepted. The p80 quantile is that edge, made robust to outliers.
Here's how it works. The app keeps a pool of recent measurements – up to 40 charges, none older than 60 days (so degradation isn't masked by ancient highs). Each charge votes with a weight equal to the share of the scale it actually measured: a 70% charge weighs five times as much as a 15% top-up. Before the count, quarantine kicks in: anything above the pool median ×1.25 is barred from voting – that cuts off phantom measurements and sensor glitches, and the threshold is derived from the data itself; design capacity isn't consulted at all. Then the app takes the point below which 80% of the total vote weight sits, interpolating between neighboring values – every new charge nudges the result a little in its own direction, but the result always stays within actually measured numbers. Until there are at least three measurements, the app abstains and keeps showing the previous value.
As a bonus, this closed the old split into singular and cumulative methods: there is one method now, and a long charge differs from a short one only by the weight of its vote.
The quantile answered how to fold measurements into a single number. But there was a question one level deeper: what exactly counts as the measurement of a single charge?
3. The tail
One of the big questions of the new approach was: should capacity charged above 100% count toward the final value? Initially the answer seemed obvious – yes, it's real capacity that went into the battery, so it has to be counted. This capacity got a name – the tail.
Tests across devices showed that the tail can take an extra 0% to 15%+ of battery capacity, depending on the device. A spread that huge demanded more careful data handling – the tail got its own storage logic. Testing began…
Testing went great: the new method produced solid estimates, except on devices with a big tail. The most vivid example: a Nothing Phone 1, two years of careful active use plus a year of rare power-ons, expected capacity 85–90%. Its result read roughly like this: 3950 (the 0–100% scale) + 550 (the tail) = 4500 = 100% of design capacity. Which is simply impossible.
Now the question was how to check whether the tail comes back to the user as real energy – energy visible from the other side. Put simply: how do you know those 550 mAh are real? That's where the concept of the new capacity method was born – by discharging.
4. And from the other side?
Traditionally, capacity in this industry is measured on the charge side; discharge as a source of estimates usually gets sidestepped, for a few reasons:
- Current stability. While charging, current flows steadily, no sharp spikes or dips. On discharge, spikes are just normal life: a single swipe can multiply battery draw, and tracking that is hard.
- Stability of data collection. When a device is plugged in, Android keeps the CPU awake, screen on or off (only partially true these days: some aggressive skins put the CPU to sleep even on a charger). That let the app collect data reliably. On discharge, every screen-off meant the app froze and fell asleep = collection over.
- The uneven percent map. I think many know the feeling: the drop from, say, 100 to 90 takes ages, and then the battery starts falling fast. It's a factor on charge too, but a smaller one.
Building the by-discharging method would have meant solving all of these. Luckily, the app has long had an independent architecture that answers them – the runtime prediction map. The map is simple: for every percent there's a record of the average time spent on it and the average current in mA. From that you can get the weight of a given percent in mAh. The final estimate is just the sum of all the weights (percents with no data get the average value).
This fairly simple map solves every problem on the list:
- Current instability is evened out by sheer number of records. The fragmentation protects against one-off anomalies – they get smoothed away by the records that follow.
- Collection stability only exists while the screen is on. So the final result uses screen-on data exclusively. The map supported that filter from day one.
- The unevenness is absorbed by the map's very structure. It was built to catch exactly that.
Back to the Nothing Phone 1 and its tail. By discharging showed 3940 on it – slightly below the charge-side estimate without the tail. A near-perfect match. The tail's fate was sealed. Dozens more tests on other devices later, the verdict was in: the tail does not count toward the by-charging capacity – and it was the new method that made the call possible.
A separate note here. The tail verdict – count it or not – is based on data from my test devices specifically. The conclusions on them were unambiguous, but that doesn't mean other devices can't have exceptions. If the tail on your device does come back as real capacity – tell me: by discharging is exactly the tool to check that. If such reports come in, a manual "count the tail" option may show up in the final estimate.
5. The confidence indicator
One thing left: showing how much the number can be trusted right now. The classic solution is to count sessions: few sessions = low confidence, many = high. On real databases that turned out to be a lie in both directions. Twenty 15% top-ups formally look like "plenty of data", but each sees a tiny slice of the scale and gets extrapolated sevenfold – they measure almost nothing. Three 70% charges are formally "too few" – yet they probe nearly the whole scale.
So the indicator counts measured volume, not sessions. Each charge contributes proportionally to the square of the scale share it measured (squared – because extrapolation error grows inversely with range). The contributions add up to "full-cycle equivalents": those twenty top-ups are 0.45 cycles of evidence; three 70% charges are 1.47. High confidence takes two full cycles, at least five votes, and a value spread within 10% – if the measurements argue with each other, the tier is capped at Medium no matter how much data piles up.
Version upgrades get separate handling: if the young estimate agrees with years of accumulated history, trust isn't reset to zero – the old value has years of data behind it, and an update shouldn't cross that out overnight. There are three grades: Low, Medium, High.
6. Conclusions
The main outcome of this update isn't any particular formula – it's a system of checks. Capacity is now computed along two independent paths: what the battery accepts, and what it gives back. The methods rely on different data and don't know about each other – which is exactly why their agreement means something. Tellingly, the tail's fate wasn't settled by a theoretical argument: 550 mAh that looked great on the charge side simply weren't there on discharge.
What this means in practice:
- Capacity values may change after the update, noticeably for some. That's not a bug: the old arithmetic flattered, the new one measures.
- Charge above 100% isn't gone – it's shown as its own line; it just no longer blends into the scale capacity.
- Next to the value there's now a confidence indicator: it shows how much measurement stands behind the number, and it grows as charges accumulate.
- The data behind the estimate is out in the open: the measurement grid with its weights, the percent map, the quarantined anomalies. The number is no longer a black box.
Both methods are already live in the 2.6.0 beta. What comes next is field statistics: the more devices run through both calculations, the clearer it gets where the methods agree – and where there's still work to do.