Video Coming Soon...
36: numfmt
The key to solving numfmt is to understand that it's replicating an international standard for displaying numbers that is absolutely insane. Don't try to make sense of it. Just assume someone who probably says things like, "Half past two quarters near 3pm" also decided that 1,200,000 would 1.2M but that 10,001,000 would be 11M.
A quick hint is you can get the magnitude of a number with:
mag := math.Floor(math.Log10(number))
The Challenge
You are required to implement the SI style of number output up to, but not including billions. Everything above billions will be printed out exactly with besos at the end. If you want you can go as far as you want with this, but the pattern gets stranger the larger you get.
Requirements
Before you peek at my packages, try looking at strconv and math.
See the list of requirements
- fmt -- https://pkg.go.dev/fmt
- flag -- https://pkg.go.dev/flag
- strconv -- https://pkg.go.dev/strconv
- log -- https://pkg.go.dev/log
- math -- https://pkg.go.dev/math
- bufio -- https://pkg.go.dev/bufio
- os -- https://pkg.go.dev/os
Spoilers
My code isn't quite exactly the same, but it's close enough. I'd say the biggest new thing to learn here is the use of fmt.Sprintf and the use of the % format style found in C languages. Before you peek at my solution, try reading the percent style formatting and the fmt.Scanf example.
See my first version code
View Source file go-coreutils/numfmt/main.go Onlypackage main
import (
"fmt"
"flag"
"strconv"
"log"
"math"
"bufio"
"os"
)
type Opts struct {
From string
To string
Numbers []string
}
func ParseOpts() Opts {
var opts Opts
flag.StringVar(&opts.From, "from", "", "Convert from")
flag.StringVar(&opts.To, "to", "", "Convert to")
flag.Parse()
opts.Numbers = flag.Args()
return opts
}
func to_si(num string) string {
number, err := strconv.ParseFloat(num, 64)
if err != nil { log.Fatal("that's not a number") }
mag := math.Floor(math.Log10(number))
switch {
case mag < 3:
return num
case mag == 3:
// need to separate k from hundres
as_k := math.Floor(float64(number) / 1000.0)
mod := math.Ceil(float64(int(number) % 1000))
return fmt.Sprintf("%d.%dk", int(as_k), int(mod))
case mag > 3 && mag < 6:
as_m := math.Ceil(float64(number) / 1000.0)
return fmt.Sprintf("%dk", int(as_m))
case mag == 6:
// need to separate mil from k
as_m := math.Floor(float64(number) / 1000000.0)
mod := math.Ceil(float64(int(number) % 1000000) / 1000.0)
return fmt.Sprintf("%d.%dM", int(as_m), int(mod))
case mag > 6 && mag <= 9:
as_m := math.Ceil(float64(number) / 1000000.0)
return fmt.Sprintf("%dM", int(as_m))
default:
return fmt.Sprintf("%sbesos", num)
}
}
func main() {
opts := ParseOpts()
if opts.From != "" {
log.Fatal("you should implement this")
}
if len(opts.Numbers) == 0 {
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
num := scanner.Text()
fmt.Println(to_si(num))
}
} else {
for _, num := range opts.Numbers {
fmt.Println(to_si(num))
}
}
}
Testing It
If you want to push your learning further then you can try to implement an automated test for this. I actually need to learn how to test utilities like this with Go, so for now just consider this an extra challenge for later until I learn how to teach it.
Register for Learn Go the Hard Way
Register today for the course and get the all currently available videos and lessons, plus all future modules for no extra charge.