GPA Scales Do Not Convert. Building a Calculator That Admits It.
Every GPA calculator eventually gets the same feature request: “can you convert my 8.6/10 to a
4.0 GPA?” The obvious implementation is one line:
const converted = (gpa / fromMax) * toMax; // wrong, and confidently so
Enter fullscreen mode Exit fullscreen mode
This is linear rescaling, and it is wrong for a reason that has nothing to do with
arithmetic: the scales are not measuring the same thing at the same resolution.
What the scales actually are
A grading scale is a mapping from letter or percentage bands to numbers. The bands are
not evenly spaced, and they are not the same across systems:
US 4.0 A = 4.0 B = 3.0 C = 2.0 D = 1.0 F = 0
(a letter covers ~10 percentage points, roughly linear)
India 10.0 often CGPA ≈ percentage / 9.5, but institution-specific
(the divisor is a convention, not a conversion)
France 20.0 14/20 is a strong result; 18/20 is exceptional and rare
(the top of the scale is deliberately almost unreachable)
Enter fullscreen mode Exit fullscreen mode
Rescaling a French 14/20 linearly gives 2.8 on a 4.0 scale — a US “B−”. Anyone familiar
with both systems will tell you a 14/20 is a considerably better result than that. The
error is not in the multiplication; it is in assuming both scales use their full range
the same way. French grading compresses the top deliberately. US 4.0 grading does not.
The same applies going the other way. A 10.0-scale CGPA of 8.6 mapped linearly is 3.44,
but many Indian institutions publish a conversion of CGPA × 9.5 = percentage, which
gives 81.7% — and 81.7% is not a 3.44 in most US conversion tables either.
So what should a calculator do?
The honest answer is: compute within a scale, and be loud about conversion between
them.
-
Within a scale, GPA is well-defined and worth computing precisely: it is
Σ(grade_points × credits) / Σ(credits), and the only real complexity is credit weighting and how the institution rounds. - Between scales, there is no universally correct function. There are institutional conversion tables, and they disagree with each other.
A calculator that silently applies linear rescaling is producing a number that looks
authoritative and is not. One that supports multiple scales natively
— 4.0, 4.3, 4.5, 5.0, 7.0, 10.0, 20-point, percentage — lets a student compute in the
system they are actually graded in, which is the number they actually need.
The weighting bug that is much more common
Conversion is the flashy problem. The bug that bites more people is credit weighting.
// wrong: unweighted mean, ignores credit hours
const gpa = grades.reduce((a, g) => a + g, 0) / grades.length;
// right: credit-weighted mean
const gpa = courses.reduce((a, c) => a + c.points * c.credits, 0)
/ courses.reduce((a, c) => a + c.credits, 0);
Enter fullscreen mode Exit fullscreen mode
The two agree only when every course carries identical credits — which is true in some
high school systems and false in essentially every university. A student taking a 1-credit
seminar and a 4-credit core course does not have those two grades count equally, and a
calculator that says otherwise gives a number the registrar will never produce.
Then there is weighted vs unweighted in the US high school sense, which is a completely
different meaning of “weighted”: AP/honours courses map an A to 5.0 instead of 4.0. Both
concepts are real, both are called weighting, and conflating them in the UI produces
support emails forever. They need different words in the interface.
Rounding is a policy, not a detail
3.4950 displayed as 3.50 versus 3.49 decides scholarship eligibility at some
institutions. There is no neutral choice here:
- Round half up, round half even, or truncate — all three are in real use
- Round once at the end, or round each semester and then average the rounded values — these give different answers
- Some institutions cap the displayed GPA at the scale maximum, some let weighted values exceed it
A calculator cannot know which policy applies. What it can do is not hide the choice: show
enough decimal places that the user can see they are near a boundary, rather than
silently rounding them across it.
What I would tell someone building one
- Compute the credit-weighted mean. Always. The unweighted version is a different statistic and almost never the one asked for.
- Support scales natively instead of converting into one canonical scale internally. Round-tripping through a canonical scale loses information at every hop.
- If you offer conversion at all, label it as an estimate and say which table you used.
- Use different words for “credit-weighted” and “AP/honours weighted” in the UI. They are unrelated concepts that share a name.
- Show one more decimal place than you think you need. Boundary cases matter more than precision looks like it should.
None of this is difficult code. It is mostly the discipline of not producing a number
that is more confident than the underlying data supports — which, for a tool students use
to make decisions about their applications, seems like the minimum bar.
If you want the credit-weighted version without writing it yourself,
the calculator is here — it computes on the scale you are
actually graded on rather than converting you into one.