Skip to content

Commit d5fa65f

Browse files
committed
feat(bfloat16): add error handling infrastructure
Add BFloat16Error type mirroring Float16Error with "bfloat16" prefix in error messages. Wire it into strict conversion path (BFloat16FromFloat32WithMode) and all checked arithmetic operations (BFloat16AddWithMode, BFloat16MulWithMode, BFloat16DivWithMode). Update existing tests to assert the new typed error.
1 parent a89cb77 commit d5fa65f

5 files changed

Lines changed: 128 additions & 23 deletions

File tree

bfloat16.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,14 +159,14 @@ func BFloat16FromFloat32WithMode(f32 float32, convMode ConversionMode, roundMode
159159
// Check for special values and ranges
160160
if math.IsNaN(float64(f32)) {
161161
if convMode == ModeStrict {
162-
return 0, &Float16Error{Op: "BFloat16FromFloat32", Msg: "NaN conversion in strict mode", Code: ErrNaN}
162+
return 0, &BFloat16Error{Op: "BFloat16FromFloat32", Msg: "NaN conversion in strict mode", Code: ErrNaN}
163163
}
164164
return BFloat16QuietNaN, nil
165165
}
166166

167167
if math.IsInf(float64(f32), 0) {
168168
if convMode == ModeStrict {
169-
return 0, &Float16Error{Op: "BFloat16FromFloat32", Msg: "Inf conversion in strict mode", Code: ErrInfinity}
169+
return 0, &BFloat16Error{Op: "BFloat16FromFloat32", Msg: "Inf conversion in strict mode", Code: ErrInfinity}
170170
}
171171
// Already handled by BFloat16FromFloat32WithRounding, which preserves Inf
172172
return b, nil
@@ -180,7 +180,7 @@ func BFloat16FromFloat32WithMode(f32 float32, convMode ConversionMode, roundMode
180180

181181
if f32 > bf16Max || f32 < bf16Min {
182182
if convMode == ModeStrict {
183-
return 0, &Float16Error{Op: "BFloat16FromFloat32", Msg: "overflow in strict mode", Code: ErrOverflow}
183+
return 0, &BFloat16Error{Op: "BFloat16FromFloat32", Msg: "overflow in strict mode", Code: ErrOverflow}
184184
}
185185
// ModeIEEE: saturate to infinity
186186
if f32 > 0 {
@@ -194,7 +194,7 @@ func BFloat16FromFloat32WithMode(f32 float32, convMode ConversionMode, roundMode
194194
// and the result after rounding is zero, it's an underflow.
195195
if f32 != 0 && math.Abs(float64(f32)) < float64(bf16SmallestNormalPos) && b.IsZero() {
196196
if convMode == ModeStrict {
197-
return 0, &Float16Error{Op: "BFloat16FromFloat32", Msg: "underflow in strict mode", Code: ErrUnderflow}
197+
return 0, &BFloat16Error{Op: "BFloat16FromFloat32", Msg: "underflow in strict mode", Code: ErrUnderflow}
198198
}
199199
// ModeIEEE: saturate to zero (already handled by rounding to zero)
200200
return b, nil

bfloat16_arithmetic.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ func BFloat16AddWithMode(a, b BFloat16, mode ArithmeticMode, rounding RoundingMo
77
// Handle NaN propagation: if either operand is NaN, propagate it
88
if a.IsNaN() || b.IsNaN() {
99
if mode == ModeExactArithmetic {
10-
return 0, &Float16Error{Op: "bfloat16_add", Msg: "NaN operand in exact mode", Code: ErrNaN}
10+
return 0, &BFloat16Error{Op: "bfloat16_add", Msg: "NaN operand in exact mode", Code: ErrNaN}
1111
}
1212
return BFloat16QuietNaN, nil
1313
}
@@ -24,7 +24,7 @@ func BFloat16AddWithMode(a, b BFloat16, mode ArithmeticMode, rounding RoundingMo
2424
if a.IsInf(0) || b.IsInf(0) {
2525
if a.IsInf(1) && b.IsInf(-1) || a.IsInf(-1) && b.IsInf(1) {
2626
if mode == ModeExactArithmetic {
27-
return 0, &Float16Error{Op: "bfloat16_add", Msg: "infinity - infinity is undefined", Code: ErrInvalidOperation}
27+
return 0, &BFloat16Error{Op: "bfloat16_add", Msg: "infinity - infinity is undefined", Code: ErrInvalidOperation}
2828
}
2929
return BFloat16QuietNaN, nil
3030
}
@@ -64,7 +64,7 @@ func BFloat16MulWithMode(a, b BFloat16, mode ArithmeticMode, rounding RoundingMo
6464
// NaN propagation
6565
if a.IsNaN() || b.IsNaN() {
6666
if mode == ModeExactArithmetic {
67-
return 0, &Float16Error{Op: "bfloat16_mul", Msg: "NaN operand in exact mode", Code: ErrNaN}
67+
return 0, &BFloat16Error{Op: "bfloat16_mul", Msg: "NaN operand in exact mode", Code: ErrNaN}
6868
}
6969
return BFloat16QuietNaN, nil
7070
}
@@ -75,7 +75,7 @@ func BFloat16MulWithMode(a, b BFloat16, mode ArithmeticMode, rounding RoundingMo
7575
// 0 * Inf = NaN
7676
if (aZero && b.IsInf(0)) || (a.IsInf(0) && bZero) {
7777
if mode == ModeExactArithmetic {
78-
return 0, &Float16Error{Op: "bfloat16_mul", Msg: "zero times infinity is undefined", Code: ErrInvalidOperation}
78+
return 0, &BFloat16Error{Op: "bfloat16_mul", Msg: "zero times infinity is undefined", Code: ErrInvalidOperation}
7979
}
8080
return BFloat16QuietNaN, nil
8181
}
@@ -119,23 +119,23 @@ func BFloat16DivWithMode(a, b BFloat16, mode ArithmeticMode, rounding RoundingMo
119119
// NaN propagation
120120
if a.IsNaN() || b.IsNaN() {
121121
if mode == ModeExactArithmetic {
122-
return 0, &Float16Error{Op: "bfloat16_div", Msg: "NaN operand in exact mode", Code: ErrNaN}
122+
return 0, &BFloat16Error{Op: "bfloat16_div", Msg: "NaN operand in exact mode", Code: ErrNaN}
123123
}
124124
return BFloat16QuietNaN, nil
125125
}
126126

127127
// 0 / 0 = NaN
128128
if a.IsZero() && b.IsZero() {
129129
if mode == ModeExactArithmetic {
130-
return 0, &Float16Error{Op: "bfloat16_div", Msg: "zero divided by zero is undefined", Code: ErrInvalidOperation}
130+
return 0, &BFloat16Error{Op: "bfloat16_div", Msg: "zero divided by zero is undefined", Code: ErrInvalidOperation}
131131
}
132132
return BFloat16QuietNaN, nil
133133
}
134134

135135
// finite / 0 = +/-Inf
136136
if b.IsZero() {
137137
if mode == ModeExactArithmetic {
138-
return 0, &Float16Error{Op: "bfloat16_div", Msg: "division by zero", Code: ErrDivisionByZero}
138+
return 0, &BFloat16Error{Op: "bfloat16_div", Msg: "division by zero", Code: ErrDivisionByZero}
139139
}
140140
if a.Signbit() != b.Signbit() {
141141
return BFloat16NegativeInfinity, nil
@@ -154,7 +154,7 @@ func BFloat16DivWithMode(a, b BFloat16, mode ArithmeticMode, rounding RoundingMo
154154
// Inf / Inf = NaN
155155
if a.IsInf(0) && b.IsInf(0) {
156156
if mode == ModeExactArithmetic {
157-
return 0, &Float16Error{Op: "bfloat16_div", Msg: "infinity divided by infinity is undefined", Code: ErrInvalidOperation}
157+
return 0, &BFloat16Error{Op: "bfloat16_div", Msg: "infinity divided by infinity is undefined", Code: ErrInvalidOperation}
158158
}
159159
return BFloat16QuietNaN, nil
160160
}

bfloat16_arithmetic_test.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ func TestBFloat16AddWithMode(t *testing.T) {
3535
if err == nil {
3636
t.Fatalf("expected error, got nil")
3737
}
38-
fe, ok := err.(*Float16Error)
38+
fe, ok := err.(*BFloat16Error)
3939
if !ok {
40-
t.Fatalf("expected *Float16Error, got %T", err)
40+
t.Fatalf("expected *BFloat16Error, got %T", err)
4141
}
4242
if fe.Code != tt.errCode {
4343
t.Errorf("error code = %d, want %d", fe.Code, tt.errCode)
@@ -83,9 +83,9 @@ func TestBFloat16SubWithMode(t *testing.T) {
8383
if err == nil {
8484
t.Fatalf("expected error, got nil")
8585
}
86-
fe, ok := err.(*Float16Error)
86+
fe, ok := err.(*BFloat16Error)
8787
if !ok {
88-
t.Fatalf("expected *Float16Error, got %T", err)
88+
t.Fatalf("expected *BFloat16Error, got %T", err)
8989
}
9090
if fe.Code != tt.errCode {
9191
t.Errorf("error code = %d, want %d", fe.Code, tt.errCode)
@@ -133,9 +133,9 @@ func TestBFloat16MulWithMode(t *testing.T) {
133133
if err == nil {
134134
t.Fatalf("expected error, got nil")
135135
}
136-
fe, ok := err.(*Float16Error)
136+
fe, ok := err.(*BFloat16Error)
137137
if !ok {
138-
t.Fatalf("expected *Float16Error, got %T", err)
138+
t.Fatalf("expected *BFloat16Error, got %T", err)
139139
}
140140
if fe.Code != tt.errCode {
141141
t.Errorf("error code = %d, want %d", fe.Code, tt.errCode)
@@ -193,9 +193,9 @@ func TestBFloat16DivWithMode(t *testing.T) {
193193
if err == nil {
194194
t.Fatalf("expected error, got nil")
195195
}
196-
fe, ok := err.(*Float16Error)
196+
fe, ok := err.(*BFloat16Error)
197197
if !ok {
198-
t.Fatalf("expected *Float16Error, got %T", err)
198+
t.Fatalf("expected *BFloat16Error, got %T", err)
199199
}
200200
if fe.Code != tt.errCode {
201201
t.Errorf("error code = %d, want %d", fe.Code, tt.errCode)
@@ -291,9 +291,9 @@ func TestBFloat16NaNPropagationAllModes(t *testing.T) {
291291
if err == nil {
292292
t.Fatal("expected error for NaN in exact mode, got nil")
293293
}
294-
fe, ok := err.(*Float16Error)
294+
fe, ok := err.(*BFloat16Error)
295295
if !ok {
296-
t.Fatalf("expected *Float16Error, got %T", err)
296+
t.Fatalf("expected *BFloat16Error, got %T", err)
297297
}
298298
if fe.Code != ErrNaN {
299299
t.Errorf("error code = %d, want %d (ErrNaN)", fe.Code, ErrNaN)

types.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,23 @@ func (e *Float16Error) Error() string {
3434
return "float16: " + e.Msg
3535
}
3636

37+
// BFloat16Error provides detailed error information for bfloat16 operations
38+
type BFloat16Error struct {
39+
Op string
40+
Msg string
41+
Code ErrorCode
42+
}
43+
44+
func (e *BFloat16Error) Error() string {
45+
if e == nil {
46+
return "<nil>"
47+
}
48+
if e.Op != "" {
49+
return fmt.Sprintf("bfloat16 %s: %s", e.Op, e.Msg)
50+
}
51+
return "bfloat16: " + e.Msg
52+
}
53+
3754
// RoundingMode controls how results are rounded during conversion/arithmetic
3855
type RoundingMode int
3956

types_test.go

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,94 @@
11
package float16
22

3-
import "testing"
3+
import (
4+
"errors"
5+
"math"
6+
"testing"
7+
)
8+
9+
func TestBFloat16Error(t *testing.T) {
10+
t.Run("Error_WithOp", func(t *testing.T) {
11+
e := &BFloat16Error{Op: "BFloat16FromFloat32", Msg: "overflow in strict mode", Code: ErrOverflow}
12+
got := e.Error()
13+
want := "bfloat16 BFloat16FromFloat32: overflow in strict mode"
14+
if got != want {
15+
t.Errorf("Error() = %q, want %q", got, want)
16+
}
17+
})
18+
19+
t.Run("Error_WithoutOp", func(t *testing.T) {
20+
e := &BFloat16Error{Msg: "some error", Code: ErrNaN}
21+
got := e.Error()
22+
want := "bfloat16: some error"
23+
if got != want {
24+
t.Errorf("Error() = %q, want %q", got, want)
25+
}
26+
})
27+
28+
t.Run("Error_Nil", func(t *testing.T) {
29+
var e *BFloat16Error
30+
if e.Error() != "<nil>" {
31+
t.Errorf("nil BFloat16Error.Error() = %q, want %q", e.Error(), "<nil>")
32+
}
33+
})
34+
35+
t.Run("StrictConversion_ReturnsTypedError", func(t *testing.T) {
36+
_, err := BFloat16FromFloat32WithMode(math.MaxFloat32, ModeStrict, RoundNearestEven)
37+
if err == nil {
38+
t.Fatal("expected error for overflow in strict mode")
39+
}
40+
var bfErr *BFloat16Error
41+
if !errors.As(err, &bfErr) {
42+
t.Fatalf("expected *BFloat16Error, got %T", err)
43+
}
44+
if bfErr.Code != ErrOverflow {
45+
t.Errorf("Code = %v, want ErrOverflow", bfErr.Code)
46+
}
47+
})
48+
49+
t.Run("StrictConversion_NaN_ReturnsTypedError", func(t *testing.T) {
50+
_, err := BFloat16FromFloat32WithMode(float32(math.NaN()), ModeStrict, RoundNearestEven)
51+
if err == nil {
52+
t.Fatal("expected error for NaN in strict mode")
53+
}
54+
var bfErr *BFloat16Error
55+
if !errors.As(err, &bfErr) {
56+
t.Fatalf("expected *BFloat16Error, got %T", err)
57+
}
58+
if bfErr.Code != ErrNaN {
59+
t.Errorf("Code = %v, want ErrNaN", bfErr.Code)
60+
}
61+
})
62+
63+
t.Run("CheckedArithmetic_ReturnsTypedError", func(t *testing.T) {
64+
nan := BFloat16QuietNaN
65+
_, err := BFloat16AddWithMode(nan, BFloat16One, ModeExactArithmetic, RoundNearestEven)
66+
if err == nil {
67+
t.Fatal("expected error for NaN in exact mode")
68+
}
69+
var bfErr *BFloat16Error
70+
if !errors.As(err, &bfErr) {
71+
t.Fatalf("expected *BFloat16Error, got %T", err)
72+
}
73+
if bfErr.Code != ErrNaN {
74+
t.Errorf("Code = %v, want ErrNaN", bfErr.Code)
75+
}
76+
})
77+
78+
t.Run("CheckedDivByZero_ReturnsTypedError", func(t *testing.T) {
79+
_, err := BFloat16DivWithMode(BFloat16One, BFloat16PositiveZero, ModeExactArithmetic, RoundNearestEven)
80+
if err == nil {
81+
t.Fatal("expected error for division by zero in exact mode")
82+
}
83+
var bfErr *BFloat16Error
84+
if !errors.As(err, &bfErr) {
85+
t.Fatalf("expected *BFloat16Error, got %T", err)
86+
}
87+
if bfErr.Code != ErrDivisionByZero {
88+
t.Errorf("Code = %v, want ErrDivisionByZero", bfErr.Code)
89+
}
90+
})
91+
}
492

593
func TestFromInt(t *testing.T) {
694
tests := []struct {

0 commit comments

Comments
 (0)