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
python
import datetime from dataclasses import dataclass
Parsing fixed-width records in Python
parsing
generators
dataclasses
Intermediate
8 steps
go
package handlers type ListFilters struct { Status string `form:"status" binding:"omitempty,oneof=active archived all"`
Cross-field query validation in Gin
validation
struct-tags
query-binding
Intermediate
9 steps
rust
use std::collections::HashMap; #[derive(Debug)] pub struct RequestHead {
Parsing an HTTP request head in Rust
parsing
error-handling
iterators
Intermediate
9 steps
php
<?php final class MarkdownParser {
Building a small Markdown-to-HTML parser in PHP
state-machine
parsing
closures
Intermediate
10 steps
go
package editor import ( "context"
How a debouncer coalesces bursts in Go
debounce
concurrency
timers
Intermediate
8 steps
ruby
class TemplateInterpolator PLACEHOLDER = /\{\{\s*([\w.]+)\s*\}\}/ def initialize(strict: false)
Interpolating templates with dotted keys in Ruby
regex
string-interpolation
hash-traversal
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.