How to Convert String to Float in Go

Use the `strconv.ParseFloat` function from the standard library to convert a string to a float, specifying the bit size (32 or 64) and handling the returned error.

The moment a string stops being text

You are reading sensor data from a CSV file. The temperature column contains "23.7". The humidity column contains "0.0042". In Python, float("23.7") just works. In JavaScript, Number("23.7") does the same. Go stops you at the door. It hands you a function, demands you specify the precision, and forces you to check for failure before you touch the number. This is not bureaucracy. It is a design choice that prevents silent data corruption from reaching your business logic.

Why Go refuses to guess

Go treats type conversion like a contract. You are not asking the language to guess what a string means. You are explicitly requesting a transformation and accepting responsibility for what happens if the request fails. The standard library function strconv.ParseFloat is the only way to do this. It takes two arguments: the string and the bit size. The bit size tells Go how much precision you need. 64 gives you a float64. 32 gives you a float32. The function always returns two values: the number and an error. If the string contains letters, multiple decimal points, or is empty, the error value will not be nil. You check it. You handle it. You move on.

Go does not guess. You specify. You verify.

The minimal conversion

Here is the baseline pattern for turning a string into a float64.

package main

import (
	"fmt"
	"strconv"
)

func main() {
	raw := "3.14159"
	// Request a 64-bit float. The second return value catches parsing failures.
	val, err := strconv.ParseFloat(raw, 64)
	// Exit early if the string does not represent a valid number.
	if err != nil {
		fmt.Println("parse failed:", err)
		return
	}
	// Safe to use the value now.
	fmt.Printf("result: %.2f\n", val)
}

The pattern is identical for float32, except you pass 32 as the bit size. The function still returns a float64 internally, so you cast it down immediately after the error check.

raw := "12.5"
// Parse to the largest size first to avoid precision loss during conversion.
val64, err := strconv.ParseFloat(raw, 32)
if err != nil {
	fmt.Println("invalid:", err)
	return
}
// Cast down to 32-bit after validation succeeds.
val32 := float32(val64)
fmt.Printf("32-bit result: %f\n", val33)

What happens under the hood

When the program runs, ParseFloat scans the string character by character. It validates the format against IEEE 754 rules. It handles optional signs, decimal points, and scientific notation like 1.2e-3. If the format is valid, it allocates a float64 on the stack and returns it alongside a nil error. If the string is "abc" or "12.3.4", the function stops scanning, constructs a *strconv.NumError, and returns it. The error type contains the original function name, the failing string, and a human-readable message. You never get a silent zero. You never get a panic. You get a value you can inspect or propagate.

The bit size argument does not just change the return type. It changes the validation threshold. A float32 can only hold about seven decimal digits of precision. If you pass "12345678.9" with bit size 32, the parser will succeed but truncate the value. The compiler will not warn you. The runtime will not panic. The data will simply lose accuracy. That is why you pick the bit size based on your domain requirements, not your convenience. Financial calculations usually stick to float64 or integer cents. Graphics shaders often use float32 to save memory. Sensor telemetry typically uses float64 until it hits a database.

The parser is strict. Your error handling is stricter.

Real-world parsing with validation

Real code rarely parses a single hardcoded string. You usually process a batch of values and need to track which ones failed. You also need to preserve context so downstream callers know exactly where the pipeline broke.

package main

import (
	"fmt"
	"strconv"
)

// ParseReadings converts a slice of string measurements into float64 values.
// It stops at the first invalid entry and returns a descriptive error.
func ParseReadings(raws []string) ([]float64, error) {
	results := make([]float64, 0, len(raws))
	for i, raw := range raws {
		// Use 64 for standard precision. Scientific notation is allowed.
		val, err := strconv.ParseFloat(raw, 64)
		if err != nil {
			// Wrap the error with context about which index failed.
			return nil, fmt.Errorf("reading %d: %w", i, err)
		}
		results = append(results, val)
	}
	return results, nil
}

func main() {
	data := []string{"22.5", "23.1", "bad_sensor", "21.8"}
	temps, err := ParseReadings(data)
	if err != nil {
		fmt.Println("validation stopped:", err)
		return
	}
	fmt.Println("valid readings:", temps)
}

The %w verb in fmt.Errorf wraps the original error. This preserves the error chain so you can use errors.Is or errors.As later to check if the root cause was a *strconv.NumError. The if err != nil pattern looks verbose. It is verbose by design. The Go community accepts the repetition because it makes the failure path impossible to ignore. You do not hide errors behind panics. You do not swallow them with blank identifiers. You route them up the call stack until a handler can decide what to do.

Where things break

The compiler catches the obvious mistakes. Pass a string where a number is expected and you get cannot use "3.14" (untyped string constant) as float64 value in argument. Forget to handle the second return value and you get assignment mismatch: 1 variable but strconv.ParseFloat returns 2 values. The runtime catches the subtle ones. Passing " 3.14 " works because Go trims whitespace automatically. Passing "3,14" fails because Go expects a period for decimals. If you drop the error check with val, _ := strconv.ParseFloat(raw, 64), the program continues with 0.0 when parsing fails. That zero will silently corrupt averages, break physics calculations, or crash downstream services.

Silent zeros are worse than loud errors. Check the return value.

Picking the right tool

Use strconv.ParseFloat when you need strict validation and explicit error handling for user input or configuration files. Use fmt.Sscanf when you are parsing structured text that mixes numbers and labels, like "temp: 23.5". Use a custom parser when you need to handle locale-specific decimal separators like commas. Use a hardcoded float literal when the value is known at compile time and never changes. Use math.NaN() or math.Inf() when you need to represent missing or unbounded data instead of failing the parse.

Pick the parser that matches your data shape. Validate early.

Where to go next