gofloating-pointstring-formatting

Why fmt.Println formats float64 values with different number of decimal places differently?


While I have checked the behavior of float64, I found the following behavior.

package main

import "fmt"

func main() {
    var a float64 = 100000
    fmt.Println(a) // output: 100000

    var b float64 = 1000000
    fmt.Println(b) // output: 1e+06 (expected: 1000000)
}

Could someone please help me why the output differs depending on the number of digits.


Solution

  • You are observing behavior due to Go's default formatting for floating-point numbers. When printing a float64, Go tries to represent the number concisely. It prints the full value for smaller numbers (like 100000). However, once the number has more digits (like 1000000), Go switches to scientific notation (1e+06).

    If you want to ensure the full value is printed without scientific notation, you can use fmt.Printf with a formatting directive:

    package main
    
    import "fmt"
    
    func main() {
        var a float64 = 100000
        fmt.Printf("%.f\n", a)
        var b float64 = 1000000
        fmt.Printf("%.f\n", b)
    }
    

    This will print both numbers without scientific notation:

    100000
    1000000
    

    Try on godbolt.org

    Relevant function that makes this scientific notation decision in source code at go.dev/src/strconv/ftoa.go as of 2024-08-22:

    func formatDigits(dst []byte, shortest bool, neg bool, digs decimalSlice, prec int, fmt byte) []byte {
        switch fmt {
        case 'e', 'E':
            return fmtE(dst, neg, digs, prec, fmt)
        case 'f':
            return fmtF(dst, neg, digs, prec)
        case 'g', 'G':
            // trailing fractional zeros in 'e' form will be trimmed.
            eprec := prec
            if eprec > digs.nd && digs.nd >= digs.dp {
                eprec = digs.nd
            }
            // %e is used if the exponent from the conversion
            // is less than -4 or greater than or equal to the precision.
            // if precision was the shortest possible, use precision 6 for this decision.
            if shortest {
                eprec = 6
            }
            exp := digs.dp - 1
            if exp < -4 || exp >= eprec {
                if prec > digs.nd {
                    prec = digs.nd
                }
                return fmtE(dst, neg, digs, prec-1, fmt+'e'-'g')
            }
            if prec > digs.dp {
                prec = digs.nd
            }
            return fmtF(dst, neg, digs, max(prec-digs.dp, 0))
        }
    
        // unknown format
        return append(dst, '%', fmt)
    }