Go atomic.AddFloat32()
Asked Answered
L

2

9

I need a function to atomically add float32 values in Go. This is what came up with based on some C code I found:

package atomic

import (
    "sync/atomic"
    "unsafe"
    "math"
)

func AddFloat32(addr *float32, delta float32) (new float32) {
    unsafeAddr := (*uint32)(unsafe.Pointer(addr))

    for {
        oldValue := math.Float32bits(*addr)
        new       = *addr + delta
        newValue := math.Float32bits(new)

        if atomic.CompareAndSwapUint32(unsafeAddr, oldValue, newValue) {
            return
        }
    }
}

Should it work (i.e really be atomic)? Is there a better/faster way to do it in Go?

Leadership answered 15/12, 2014 at 20:14 Comment(0)
I
9

Look for some code from the Go standard library to adapt. For example, from go/src/sync/atomic/64bit_arm.go,

func addUint64(val *uint64, delta uint64) (new uint64) {
    for {
        old := *val
        new = old + delta
        if CompareAndSwapUint64(val, old, new) {
            break
        }
    }
    return
}

For float32 that becomes,

package main

import (
    "fmt"
    "math"
    "sync/atomic"
    "unsafe"
)

func AddFloat32(val *float32, delta float32) (new float32) {
    for {
        old := *val
        new = old + delta
        if atomic.CompareAndSwapUint32(
            (*uint32)(unsafe.Pointer(val)),
            math.Float32bits(old),
            math.Float32bits(new),
        ) {
            break
        }
    }
    return
}

func main() {
    val, delta := float32(math.Pi), float32(math.E)
    fmt.Println(val, delta, val+delta)
    new := AddFloat32(&val, delta)
    fmt.Println(val, new)
}

Output:

3.1415927 2.7182817 5.8598747
5.8598747 5.8598747
Infatuated answered 15/12, 2014 at 22:57 Comment(2)
@JimB: When you say it may fail, do you mean that it depends on the target architecture? Is the quoted code from go/src/sync/atomic/64bit_arm.go only reliable on arm?Leadership
@B_old: sorry, I seem to have sufficiently confused the subject here. I agree that @peterSO's example is correct, and will properly update the float32 value. The comment I made was just about the race detector and how it could flag the unprotected read from *addr, though it doesn't currently. Ensuring that code passes under -race is often critical, and if this does flag it in the future, it's not hard to work around. (removed old comments, and they weren't really useful)Platelet
S
1

As of go 1.19, sync/atomic has dedicated atomic types (such as atomic.Uint32), but unfortunately there is no atomic.Float32. But in a similar fashion, you could implement atomic floating point addition like this:

import (
    "math"
    "sync/atomic"
)

type AtomicFloat32 struct {
    bits uint32
}

func (af *AtomicFloat32) Add(x float32) (newf float32) {
    for done := false; !done; {
        oldbits := atomic.LoadUint32(&af.bits)
        newf = math.Float32frombits(oldbits) + x
        newbits := math.Float32bits(newf)
        done = atomic.CompareAndSwapUint32(&af.bits, oldbits, newbits)
    }

    return
}

This method is essentially the same as in peterSO's answer, but has the advantage that you don't need to import unsafe.

It should be noted that floating point addition, unlike integer addition, is not commutative, so both this and peterSO's answer might suffer from an ABA problem. However, since the Go spec requires IEEE-754 floating points, non-commutativity can only happen with NaN payloads, so this implementation should still be OK for most purposes.

Stadium answered 12/8, 2024 at 14:8 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.