The float package is a software implementation of floating-point arithmetics that conforms to the 80-bit IEEE 754 extended double precision floating-point format
This package is derived from the original SoftFloat package and was implemented as a basis for a Motorola M68881/M68882 FPU emulation in pure Go
go get github.com/jenska/float@v1.3.0- Go 1.27 or later
- Full IEEE 754 Compliance: Correctly rounded basic operations in all four rounding modes and at 32/64/80-bit rounding precision
- Complete Arithmetic Operations: Add, Sub, Mul, Div, Rem, Mod, Sqrt, Scale and more
- Transcendental Functions: Exponentials, logarithms, trigonometric, inverse trigonometric and hyperbolic functions, correctly rounded in practice
- Type Conversions: To/from int8, int16, int32, int64, float32, float64 (also as raw bit patterns) and decimal strings
- Motorola 68881/68882 Support: Every arithmetic operation of the FPU's instruction set, unnormal operands, the 96-bit memory format, constant ROM values and a configurable default NaN, as a basis for FPU emulators
- String Formatting:
'b','e','E','f','g','G'verbs and a raw hexadecimal dump (Internal) - Exception Handling: IEEE 754 exception flags with customizable handlers
- High Performance: Optimized bit-level operations
- Goroutine-safe environments: an
Envholds its own rounding mode, precision and exception flags, so each goroutine can compute independently; the X80 methods use package-level settings and are not goroutine-safe
See the package documentation for details. The 68881/68882 column names the FPU operation each function implements.
| Name | Purpose |
|---|---|
RoundingMode |
RoundNearestEven, RoundToZero, RoundDown, RoundUp |
RoundingPrecision |
80 (64-bit significand), 64 (53 bits) or 32 (24 bits) |
DetectTininess |
TininessAfterRounding or TininessBeforeRounding |
DefaultNaN |
NaN returned by invalid operations; set to NewFromBits(0x7FFF, 0xFFFFFFFFFFFFFFFF) for the 68881/68882 |
Exception, GetExceptions, HasException, ClearExceptions, ... |
Accumulated exception flags ExceptionInvalid, ExceptionDivbyzero, ExceptionOverflow, ExceptionUnderflow, ExceptionInexact |
SetExceptionHandler |
Callback for every raised exception |
These package-level settings are used by the X80 methods and package functions
(a.Add(b), Pow10(n), ...) and are not safe for concurrent use. For
concurrent code, or to emulate several FPUs, use an Env: it has the same
settings as fields, accumulates its own Exception flags and calls its own
Handler. Every operation that rounds or raises exceptions is also an Env
method (e.Add(a, b), e.Sin(a), e.Pow10(n), e.Parse(s),
e.Constant(ConstPi), ...). An Env belongs to one goroutine at a time;
separate Envs share no state. The zero Env rounds to nearest even at
80-bit precision.
| Method | Description | 68881/68882 |
|---|---|---|
Add, Sub, Mul, Div |
Basic operations | FADD, FSUB, FMUL, FDIV |
SglMul, SglDiv |
Single-precision multiply and divide with extended exponent range | FSGLMUL, FSGLDIV |
Rem, RemQuo |
IEEE remainder, optionally with the low quotient bits | FREM |
Mod, ModQuo |
Truncated remainder (C fmod), optionally with the low quotient bits |
FMOD |
Sqrt |
Square root | FSQRT |
RoundToInt, Trunc |
Round to integer with the current mode / toward zero | FINT, FINTRZ |
RoundToPrecision |
Round to 32/64/80-bit precision | FMOVE to a register |
Scale, GetExp, GetMan |
Multiply by 2^n, extract exponent / significand | FSCALE, FGETEXP, FGETMAN |
Abs, Neg |
Sign-bit operations | FABS, FNEG |
| Method | 68881/68882 | Method | 68881/68882 |
|---|---|---|---|
Exp, Exp2, Exp10, Expm1 |
FETOX, FTWOTOX, FTENTOX, FETOXM1 | Sin, Cos, Tan, Sincos |
FSIN, FCOS, FTAN, FSINCOS |
Ln, Log2, Log10, Log1p |
FLOGN, FLOG2, FLOG10, FLOGNP1 | Asin, Acos, Atan |
FASIN, FACOS, FATAN |
Sinh, Cosh, Tanh, Atanh |
FSINH, FCOSH, FTANH, FATANH |
Eq,Lt,Le,Gt,Ge(invalid exception on NaN) and the...Quietvariants (invalid only on signaling NaN),EqSignalingIsNaN,IsSignalingNaN,IsInf,IsZero,IsSubnormal,SignbitNormalizereturns the canonical encoding of unnormals and pseudo-denormals
| From X80 | To X80 | 68881/68882 format |
|---|---|---|
ToInt8, ToInt16, ToInt32, ToInt64 (+ RoundZero variants for 32/64) |
Int32ToFloatX80, Int64ToFloatX80 |
.B, .W, .L |
ToFloat32Bits, ToFloat32 |
NewFromFloat32Bits, Float32ToFloatX80 |
.S |
ToFloat64Bits, ToFloat64 |
NewFromFloat64Bits, Float64ToFloatX80, NewFromFloat64 |
.D |
Bytes96 |
NewFromBytes96 |
.X (12 bytes) |
Bytes |
NewFromBytes |
10-byte x87 format |
Bits |
NewFromBits |
raw sign/exponent and significand |
Format, Append, String |
Parse |
decimal strings, for .P packed decimal |
The ...Bits conversions preserve NaN payloads and signaling NaNs, which Go's
float32/float64 handling may not.
X80Zero,X80One,X80MinusOne,X80InfPos,X80InfNeg,X80NaNX80Pi,X80E,X80Ln2,X80Ln10,X80Log2E,X80Log10E,X80Log10Of2,X80Sqrt2, correctly rounded to nearestConstant.Value()roundsConstPi,ConstE,ConstLn2,ConstLn10,ConstLog2E,ConstLog10EorConstLog10Of2under the current rounding mode and precisionPow10(n)returns 10^n correctly rounded
Together with X80Zero these cover the 68881/68882 FMOVECR constant ROM.
Note that the ROM stores log10(2) as 3FFD9A209A84FBCFF798, one unit below
the correctly rounded value that X80Log10Of2 holds.
Add, Sub, Mul, Div, Rem, Mod, Sqrt, Scale, the conversions, Pow10, Parse
and Constant.Value are correctly rounded; this is checked against math/big
on hundreds of thousands of random operands.
The transcendental functions are evaluated with a 128-bit significand and rounded once, in the current rounding mode and precision. Their evaluation error is below 2^-110, so results are correctly rounded except in astronomically rare cases: measured against 400-bit references on 54,000 random arguments over the whole domains, no result was off by more than 0.5 ULP. The trigonometric argument reduction is exact enough for every X80 value, including arguments like 10^4000.
- Arithmetic operations are optimized for speed while maintaining accuracy
- Series expansions are tuned for convergence speed vs precision trade-offs
- Memory layout is optimized for 64-bit architectures
- Arithmetic, conversions and transcendental functions do not allocate
- Trigonometric argument reduction multiplies by a precomputed table of 2/π (generated with
go generate) ParseandPow10fall back to exactmath/bigarithmetic only for inputs within 2^-44 ULP of a rounding boundary, or with more than 38 significant digits
Run benchmarks with:
go test -run '^$' -bench . ./...Typical performance on modern hardware:
- Basic arithmetic: ~5-35 ns per operation
- Transcendental functions: ~130-400 ns per operation
- Conversions: ~3-20 ns per operation
Parse,String: ~200-300 ns
package main
import (
"fmt"
"github.com/jenska/float"
)
func main() {
pi := float.X80Pi
fmt.Println(pi) // 21 significant digits
fmt.Println(pi.Format('e', 10)) // fixed number of decimals
x, err := float.Parse("0.1")
if err != nil {
panic(err)
}
fmt.Println(x.Mul(float.Int64ToFloatX80(3)))
fmt.Println(float.X80One.Exp(), pi.Div(float.Int64ToFloatX80(6)).Sin())
}Output:
3.14159265358979323851
3.1415926536e+00
0.300000000000000000011
2.71828182845904523543 0.5
package main
import (
"fmt"
"github.com/jenska/float"
)
func main() {
float.SetExceptionHandler(func(exc int) {
if exc&float.ExceptionOverflow != 0 {
fmt.Println("overflow")
}
if exc&float.ExceptionDivbyzero != 0 {
fmt.Println("division by zero")
}
})
defer float.SetExceptionHandler(nil)
fmt.Println(float.Int64ToFloatX80(20000).Exp()) // e^20000 exceeds ~1.19e4932
fmt.Println(float.X80Zero.Ln())
// Flags accumulate until cleared, also without a handler.
float.ClearExceptions()
third := float.X80One.Div(float.Int64ToFloatX80(3))
fmt.Println(third, float.HasException(float.ExceptionInexact))
}Output:
overflow
+Inf
division by zero
-Inf
0.333333333333333333342 true
package main
import (
"encoding/binary"
"fmt"
"github.com/jenska/float"
)
func main() {
x := float.X80Pi
// Sign/exponent word and 64-bit significand
high, low := x.Bits()
fmt.Printf("%04X %016X\n", high, low)
fmt.Println(float.NewFromBits(high, low) == x)
// 12-byte 68881/68882 memory format (.X): exponent, padding, significand
b := x.Bytes96(binary.BigEndian)
fmt.Printf("% X\n", b)
fmt.Println(float.NewFromBytes96(b, binary.BigEndian) == x)
}Output:
4000 C90FDAA22168C235
true
40 00 00 00 C9 0F DA A2 21 68 C2 35
true
package main
import (
"fmt"
"github.com/jenska/float"
)
func main() {
x64 := 1.0 / 3
x80 := float.X80One.Div(float.Int64ToFloatX80(3))
fmt.Printf("float64: %.21g\n", x64)
fmt.Println("X80: ", x80)
}Output:
float64: 0.33333333333333331483
X80: 0.333333333333333333342
# Run all tests
go test ./...
# Skip the slower accuracy sweeps
go test -short ./...
# Run one group of tests, e.g. the transcendental accuracy tests
go test -run TestTranscendentalAccuracy -v
# Coverage report (coverage.out and coverage.html)
make coverage
# Run benchmarks
go test -run '^$' -bench . ./...Test coverage is about 84% of statements; make coverage produces the current
figure.
Test categories:
- Differential tests (
reference_test.go,functions_test.go): arithmetic, comparisons, conversions,Parse,Pow10and formatting compared with exactmath/bigresults on hundreds of thousands of random operands, in all rounding modes and precisions - Accuracy tests (
transcendental_test.go): every transcendental function compared with 400-bit references over its whole domain - Edge cases: NaN propagation, infinities, signed zeros, subnormals, unnormals, overflow and underflow, and the exception flags each operation raises
- Unit tests: per-operation tables in
operations_test.go,conversions_test.goandcomparisons_test.go
The implementation is validated against:
- Exact
math/bigarithmetic for every correctly rounded operation - Independently computed 400-bit references for the transcendental functions
- Known mathematical constants (π, e, ln 2, √2, ...), recomputed from series in the tests
- IEEE 754 special cases and exception semantics
- Fork the repository
- Clone your fork:
git clone https://github.com/<your-username>/float.git - Install dependencies:
go mod download - Run tests:
go test ./... - Make your changes
- Add tests for new functionality
- Ensure all tests pass:
go test -cover - Submit a pull request
- Follow standard Go formatting:
go fmt - Use
gofmt -sfor additional simplifications - Add godoc comments for all exported functions/types
- Write comprehensive tests for new features
- Update documentation for API changes
- Performance optimizations
- More comprehensive test coverage
- Documentation improvements
- Port to other languages
This project is licensed under the MIT License - see the LICENSE file for details.
See CHANGELOG.md for a complete list of changes and version history.
- SoftFloat - Reference soft float implementation
- Go math package - Standard Go math library
- decimal - Arbitrary precision decimal numbers
The library implements IEEE 754 exception handling with the following exception flags:
ExceptionInvalid: Invalid operation (e.g., sqrt of negative number, 0/0)ExceptionDenormal: Defined for x87 compatibility; never raisedExceptionDivbyzero: Division by zeroExceptionOverflow: Result too large to representExceptionUnderflow: Result too small to representExceptionInexact: Result not exactly representable
// Set a custom exception handler
float.SetExceptionHandler(func(exc int) {
fmt.Printf("Floating-point exception: %x\n", exc)
})
// Check for exceptions
if float.HasException(float.ExceptionInvalid) {
fmt.Println("Invalid operation occurred")
}
// Clear exceptions
float.ClearExceptions()Exceptions are raised during operations but don't prevent execution. Operations return appropriate IEEE 754 values (NaN, Inf) for exceptional conditions.
- add more examples