Floating-point operators

February 8, 2024

Floating-point operators

For floating-point and complex numbers, +x is the same as x, while -x is the negation of x. The result of a floating-point or complex division by zero is not specified beyond the IEEE-754 standard; whether a run-time panic occurs is implementation-specific.

I find this to be quite interesting. An implementation may choose to panic, or not, if you attempt to divide a floating-point or complex number by zero. And indeed, the standard implementation does not panic, as you can see by running the following code in the Playground or on your own machine:

func main() {
	var x float64 = 1.23
	fmt.Println(x / 0)
}

Outputs:

+Inf

But wait, that’s not all… there are other implementation-dependent details!

An implementation may combine multiple floating-point operations into a single fused operation, possibly across statements, and produce a result that differs from the value obtained by executing and rounding the instructions individually.

So as a simple example, if you have code that looks like:

x := float32(0.0000000001)
y := x
x = x * 12.3456789
x = x / 12.3456789
fmt.Println(y, x) // 1e-10 9.9999994e-11

An implementation has the freedom to “fuse” the multiplication followed by division operations, effectively eliminating them, for a more accurate result.

… An explicit floating-point type conversion rounds to the precision of the target type, preventing fusion that would discard that rounding.

For instance, some architectures provide a “fused multiply and add” (FMA) instruction that computes x*y + z without rounding the intermediate result x*y. These examples show when a Go implementation can use that instruction:

// FMA allowed for computing r, because x*y is not explicitly rounded:
r  = x*y + z
r  = z;   r += x*y
t  = x*y; r = t + z
*p = x*y; r = *p + z
r  = x*y + float64(z)

// FMA disallowed for computing r, because it would omit rounding of x*y:
r  = float64(x*y) + z
r  = z; r += float64(x*y)
t  = float64(x*y); r = t + z

So if you want your implementation/platform to (possibly) fuse floating-point operations, avoide explicit conversion to/from floating-point types, which make that impossible. In reality: If in doubt, don’t worry about this level of micro-optimization.

Quotes from The Go Programming Language Specification Version of August 2, 2023


Share this

Direct to your inbox, daily. I respect your privacy .

Unsure? Browse the archive .

Related Content


Comparison operators, continued

Yesterday we started on comparison operators. Today we’ll continue, but there are a lot of types to cover, so I’ll break this down into a two or three parts, to keep each day’s email easily digestible. Recall that we had just been introduced to the concepts of ordered and comparable, and this is where we pick up… Comparison operators … These terms and the result of the comparisons are defined as follows:


Divide by zero, and shifting

Integer operators … If the divisor is a constant, it must not be zero. If the divisor is zero at run time, a run-time panic occurs. I’m sure you expected that. Division by zero is pretty universally not allowed. var a = 3 var b = 0 var c = a / 0 // Won't compile var d = a / b // run-time panic … If the dividend is non-negative and the divisor is a constant power of 2, the division may be replaced by a right shift, and computing the remainder may be replaced by a bitwise AND operation:


Arithmetic operators

Arithmetic operators Arithmetic operators apply to numeric values and yield a result of the same type as the first operand. The four standard arithmetic operators (+, -, *, /) apply to integer, floating-point, and complex types; + also applies to strings. The bitwise logical and shift operators apply to integers only. + sum integers, floats, complex values, strings - difference integers, floats, complex values * product integers, floats, complex values / quotient integers, floats, complex values % remainder integers & bitwise AND integers | bitwise OR integers ^ bitwise XOR integers &^ bit clear (AND NOT) integers << left shift integer << integer >= 0 >> right shift integer >> integer >= 0 There should be nothing surprising here.