Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide atomic type for float32 #107

Merged
merged 1 commit into from May 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
77 changes: 77 additions & 0 deletions float32.go
@@ -0,0 +1,77 @@
// @generated Code generated by gen-atomicwrapper.

// Copyright (c) 2020-2022 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package atomic

import (
"encoding/json"
"math"
)

// Float32 is an atomic type-safe wrapper for float32 values.
type Float32 struct {
_ nocmp // disallow non-atomic comparison

v Uint32
}

var _zeroFloat32 float32

// NewFloat32 creates a new Float32.
func NewFloat32(val float32) *Float32 {
x := &Float32{}
if val != _zeroFloat32 {
x.Store(val)
}
return x
}

// Load atomically loads the wrapped float32.
func (x *Float32) Load() float32 {
return math.Float32frombits(x.v.Load())
}

// Store atomically stores the passed float32.
func (x *Float32) Store(val float32) {
x.v.Store(math.Float32bits(val))
}

// Swap atomically stores the given float32 and returns the old
// value.
func (x *Float32) Swap(val float32) (old float32) {
return math.Float32frombits(x.v.Swap(math.Float32bits(val)))
}

// MarshalJSON encodes the wrapped float32 into JSON.
func (x *Float32) MarshalJSON() ([]byte, error) {
return json.Marshal(x.Load())
}

// UnmarshalJSON decodes a float32 from JSON.
func (x *Float32) UnmarshalJSON(b []byte) error {
var v float32
if err := json.Unmarshal(b, &v); err != nil {
return err
}
x.Store(v)
return nil
}
69 changes: 69 additions & 0 deletions float32_ext.go
@@ -0,0 +1,69 @@
// Copyright (c) 2020 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package atomic

import (
"math"
"strconv"
)

//go:generate bin/gen-atomicwrapper -name=Float32 -type=float32 -wrapped=Uint32 -pack=math.Float32bits -unpack=math.Float32frombits -swap -json -imports math -file=float32.go

// Add atomically adds to the wrapped float32 and returns the new value.
func (f *Float32) Add(delta float32) float32 {
for {
old := f.Load()
new := old + delta
if f.CAS(old, new) {
return new
}
}
}

// Sub atomically subtracts from the wrapped float32 and returns the new value.
func (f *Float32) Sub(delta float32) float32 {
return f.Add(-delta)
}

// CAS is an atomic compare-and-swap for float32 values.
//
// Note: CAS handles NaN incorrectly. NaN != NaN using Go's inbuilt operators
// but CAS allows a stored NaN to compare equal to a passed in NaN.
// This avoids typical CAS loops from blocking forever, e.g.,
//
// for {
// old := atom.Load()
// new = f(old)
// if atom.CAS(old, new) {
// break
// }
// }
//
// If CAS did not match NaN to match, then the above would loop forever.
func (f *Float32) CAS(old, new float32) (swapped bool) {
return f.v.CAS(math.Float32bits(old), math.Float32bits(new))
}

// String encodes the wrapped value as a string.
func (f *Float32) String() string {
// 'g' is the behavior for floats with %v.
return strconv.FormatFloat(float64(f.Load()), 'g', -1, 32)
}
72 changes: 72 additions & 0 deletions float32_test.go
@@ -0,0 +1,72 @@
// Copyright (c) 2020 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package atomic

import (
"encoding/json"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestFloat32(t *testing.T) {
atom := NewFloat32(4.2)

require.Equal(t, float32(4.2), atom.Load(), "Load didn't work.")

require.True(t, atom.CAS(4.2, 0.5), "CAS didn't report a swap.")
require.Equal(t, float32(0.5), atom.Load(), "CAS didn't set the correct value.")
require.False(t, atom.CAS(0.0, 1.5), "CAS reported a swap.")

atom.Store(42.0)
require.Equal(t, float32(42.0), atom.Load(), "Store didn't set the correct value.")
require.Equal(t, float32(42.5), atom.Add(0.5), "Add didn't work.")
require.Equal(t, float32(42.0), atom.Sub(0.5), "Sub didn't work.")

require.Equal(t, float32(42.0), atom.Swap(45.0), "Swap didn't return the old value.")
require.Equal(t, float32(45.0), atom.Load(), "Swap didn't set the correct value.")

t.Run("JSON/Marshal", func(t *testing.T) {
atom.Store(42.5)
bytes, err := json.Marshal(atom)
require.NoError(t, err, "json.Marshal errored unexpectedly.")
require.Equal(t, []byte("42.5"), bytes, "json.Marshal encoded the wrong bytes.")
})

t.Run("JSON/Unmarshal", func(t *testing.T) {
err := json.Unmarshal([]byte("40.5"), &atom)
require.NoError(t, err, "json.Unmarshal errored unexpectedly.")
require.Equal(t, float32(40.5), atom.Load(), "json.Unmarshal didn't set the correct value.")
})

t.Run("JSON/Unmarshal/Error", func(t *testing.T) {
err := json.Unmarshal([]byte("\"40.5\""), &atom)
require.Error(t, err, "json.Unmarshal didn't error as expected.")
assertErrorJSONUnmarshalType(t, err,
"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err)
})

t.Run("String", func(t *testing.T) {
assert.Equal(t, "42.5", NewFloat32(42.5).String(),
"String() returned an unexpected value.")
})
}