go
50 lines · 8 steps
Parsing human-readable byte sizes in Go
A regex plus a lookup table turn strings like "1.5gb" into an exact byte count.
Explained by
highlit
1package humanize
2
3import (
4 "fmt"
5 "math"
6 "regexp"
7 "strconv"
8 "strings"
9)
10
11var sizePattern = regexp.MustCompile(`(?i)^\s*([0-9]*\.?[0-9]+)\s*([kmgtpe]?i?b?)\s*$`)
12
13var unitFactors = map[string]uint64{
14 "": 1,
15 "b": 1,
16 "kb": 1000, "mb": 1000 * 1000, "gb": 1000 * 1000 * 1000,
17 "tb": 1000 * 1000 * 1000 * 1000,
18 "pb": 1000 * 1000 * 1000 * 1000 * 1000,
19 "kib": 1 << 10, "mib": 1 << 20, "gib": 1 << 30,
20 "tib": 1 << 40, "pib": 1 << 50,
21}
22
23func ParseBytes(s string) (uint64, error) {
24 matches := sizePattern.FindStringSubmatch(s)
25 if matches == nil {
26 return 0, fmt.Errorf("humanize: invalid size %q", s)
27 }
28
29 num, err := strconv.ParseFloat(matches[1], 64)
30 if err != nil {
31 return 0, fmt.Errorf("humanize: invalid number in %q: %w", s, err)
32 }
33
34 unit := strings.ToLower(matches[2])
35 if unit != "" && unit != "b" && !strings.HasSuffix(unit, "b") {
36 unit += "b"
37 }
38
39 factor, ok := unitFactors[unit]
40 if !ok {
41 return 0, fmt.Errorf("humanize: unknown unit %q", matches[2])
42 }
43
44 bytes := num * float64(factor)
45 if bytes < 0 || bytes > math.MaxUint64 {
46 return 0, fmt.Errorf("humanize: size %q out of range", s)
47 }
48
49 return uint64(bytes), nil
50}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1A precompiled regex with capture groups cleanly separates a numeric value from its unit suffix.
- 2A map of unit-to-multiplier keeps decimal (kb) and binary (kib) scaling declarative instead of buried in branches.
- 3Validating the float range before converting to uint64 prevents silent overflow on out-of-range input.
Related explainers
rust
use serde::Deserialize; #[derive(Debug, Deserialize)] #[serde(untagged)]
Parsing flexible JSON shapes with serde
deserialization
enums
json
Intermediate
6 steps
ruby
require "shellwords" require "open3" module Backup
Building safe shell commands in Ruby
shell-out
subprocess
command-injection
Intermediate
7 steps
go
package streaming import ( "bufio"
Streaming NDJSON logs over HTTP in Go
http-streaming
channels
select
Advanced
10 steps
ruby
class UserAgentParser BROWSERS = [ [/Edg\/([\d.]+)/, "Edge"], [/OPR\/([\d.]+)/, "Opera"],
Parsing user-agent strings in Ruby
regex
pattern-matching
lookup-tables
Intermediate
8 steps
javascript
function evaluate(expression) { const tokens = tokenize(expression); let pos = 0;
Building a recursive descent calculator
parsing
recursion
operator-precedence
Intermediate
8 steps
go
package api import ( "crypto/sha256"
ETag conditional requests in Gin
http-caching
etag
conditional-requests
Intermediate
6 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/parsing-human-readable-byte-sizes-in-go-explained-go-e87a/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.