Floats in Go
27 January 2025 / 5 min read
We already had a look at integers, now let’s dive deep into floats in Go (Golang). We’ll cover everything from basic concepts to advanced details, including representation, precision, operations, and best practices.
What are Floats in Go?
Floats in Go are data types used to represent floating-point numbers, which are numbers with a fractional component. Go provides two floating-point types:
float32
: 32-bit floating-point number (single precision)float64
: 64-bit floating-point number (double precision)
By default, floating-point literals in Go are of type float64
.
Floating-Point Representation
Floats in Go follow the IEEE 754 standard for floating-point arithmetic. This standard defines how floating-point numbers are represented in binary.
Property | float32 | float64 |
---|---|---|
Size | 32 bits (4 bytes) | 64 bits (8 bytes) |
Sign Bits | 1 | 1 |
Exponent Bits | 8 | 11 |
Mantissa (Fraction) Bits | 23 | 52 |
Precision | ~7 decimal digits | ~15 decimal digits |
Range | ±1.18e-38 to ±3.4e38 | ±2.23e-308 to ±1.8e308 |
Default Use | No | Yes (default for floating-point literals) |
Memory Usage | Lower | Higher |
Performance | Faster on some 32-bit systems | Generally as fast as float32 on modern CPUs |
Use Case | Memory-constrained environments | General-purpose, higher precision needs (still not 100% precise due to the nature of floats) |
Declaring and Initializing Floats
var f1 float32 = 3.14
var f2 float64 = 3.141592653589793
f3 := 3.14 // Type inferred as float64
Floating-Point Literals
Floating-point literals can be written in decimal or scientific notation:
a := 3.14 // Decimal notation
b := 6.022e23 // Scientific notation
c := 1.616e-34 // Small numbers
Zero Value
The zero value for floating-point types (float32
and float64
) is 0
(zero). This is the default value assigned to a variable of type float32
or float64
if it is declared but not explicitly initialized.
var f32 float32
var f64 float64
fmt.Println(f32) // Output: 0
fmt.Println(f64) // Output: 0
Precision and Rounding Errors
Floating-point numbers are approximations due to their binary representation. This can lead to rounding errors:
fmt.Println(0.1 + 0.2) // Output: 0.30000000000000004
Why?
- Many decimal fractions (e.g., 0.1) cannot be represented exactly in binary.
- Operations on floats can accumulate small errors.
Special Values
Floats in Go can represent special values defined by IEEE 754:
- Positive Infinity (+Inf): Result of dividing a positive number by zero.
- Negative Infinity (-Inf): Result of dividing a negative number by zero.
- NaN (Not a Number): Result of invalid operations like
0/0
ormath.Sqrt(-1)
.
Checking for Special Values
import "math"
x := math.Inf(1) // Positive infinity
y := math.Inf(-1) // Negative infinity
z := math.NaN() // NaN
fmt.Println(math.IsInf(x, 1)) // true
fmt.Println(math.IsInf(y, -1)) // true
fmt.Println(math.IsNaN(z)) // true
Arithmetic Operations
Go supports standard arithmetic operations on floats:
a := 3.14
b := 2.71
sum := a + b
diff := a - b
prod := a * b
quot := a / b
Comparison of Floats
Due to precision issues, direct comparison of floats using ==
is not recommended. Instead, use a small tolerance (epsilon
) to compare floats:
import "math"
const epsilon = 1e-9
func almostEqual(a, b float64) bool {
return math.Abs(a - b) < epsilon
}
fmt.Println(almostEqual(0.1+0.2, 0.3)) // true
Type Conversion
Go requires explicit type conversion between float32
and float64
:
var f32 float32 = 3.14
var f64 float64 = float64(f32)
Mathematical Functions
The math
package provides many functions for working with floats:
import "math"
x := 2.0
y := math.Sqrt(x) // Square root
z := math.Pow(x, 3) // x^3
a := math.Sin(x) // Sine of x
b := math.Log(x) // Natural logarithm
Formatting Floats
Use fmt
package to format floats:
f := 3.141592653589793
fmt.Printf("%.2f\n", f) // 3.14
fmt.Printf("%e\n", f) // 3.141593e+00 (scientific notation)
fmt.Printf("%g\n", f) // 3.141592653589793 (compact representation)
Parsing Floats from Strings
Use strconv.ParseFloat
to parse strings into floats:
s := "3.14"
f, err := strconv.ParseFloat(s, 64) // 64-bit float
if err != nil {
fmt.Println("Error:", err)
}
fmt.Println(f) // 3.14
Best Practices
- Use
float64
by default: It provides better precision and is the default type for floating-point literals. - Avoid direct equality checks: Use a tolerance (
epsilon
) for comparisons. - Be aware of precision limitations: Understand that floats are approximations.
- Use the math package: It provides robust functions for mathematical operations.
Common Pitfalls
- Accumulation of errors: Repeated operations can magnify rounding errors.
- Unexpected results: Due to binary representation, some decimal numbers cannot be represented exactly.
- Performance:
float64
operations are generally slower thanfloat32
on 32-bit systems.
Avoid floats when precision matters
As already discussed, if you absolutely have to use floats, then use float64
in most cases, except in memory-constrained environment.
In many cases though the question is not whether to use float64
or float32
, but whether to use floats at all.
Let’s have a common scenario in real-world applications where floats intuitively seem as the first choice: Money.
Floating-point types are not suitable for representing monetary values due to their inherent imprecision. Here’s why:
Precision Issues
Floating-point numbers are approximations. For example, 0.1
cannot be represented exactly in binary, leading to small rounding errors:
fmt.Println(0.1 + 0.2) // Output: 0.30000000000000004
This imprecision can accumulate over multiple calculations, leading to significant errors in financial computations.
Rounding Errors
Financial calculations often require exact decimal precision (e.g., cents in dollars). Floats cannot guarantee this precision:
balance := 100.0
interest := 0.01
for i := 0; i < 365; i++ {
balance += balance * interest
}
fmt.Println(balance) // May not match exact expected value due to rounding errors
Better Alternative: Use Integers
It’s better to represent monetary values as integers (e.g., cents instead of dollars) and perform all calculations using integers to avoid floating-point inaccuracies.
We can still convert to floats only for display purposes at the end when all calculations have been done.
Example:
// Represent $100.50 as 10050 cents
balance := 10050 // In cents
interest := 10 // 0.10% as an integer
// Calculate interest
balance += (balance * interest) / 100
// Convert to dollars for display
fmt.Printf("Balance: $%.2f\n", float64(balance)/100)
Advantages of Using Integers:
- Exact Precision: No rounding errors during calculations.
- Predictable Results: Ensures consistency in financial computations.
- Simpler Debugging: Easier to trace and verify calculations.
When to Use Floats
Use floats only when precision is not critical, because floats are always approximations.