Math #
Numeric computation is everywhere — calculating discounts, generating random numbers, validating geographic coordinates, processing statistical data, and implementing cryptographic algorithms. Go provides the math package in the standard library covering all common mathematical needs: important constants, trigonometric functions, logarithms, exponents, rounding, and the boundary values of numeric types. Beyond that, Go also provides math/rand for random numbers and math/big for arbitrary-precision arithmetic when float64 and int64 are no longer enough. This article covers the entire math package ecosystem in Go — how it works, when to use it, and the numeric pitfalls to watch out for.
Mathematical Constants #
The math package defines the important constants often needed in scientific and engineering computation. All these constants are float64 with full precision.
import (
"fmt"
"math"
)
func main() {
fmt.Println(math.Pi) // 3.141592653589793 — π
fmt.Println(math.E) // 2.718281828459045 — Euler's number
fmt.Println(math.Phi) // 1.618033988749895 — golden ratio φ
fmt.Println(math.Sqrt2) // 1.4142135623730951 — √2
fmt.Println(math.SqrtE) // 1.6487212707001282 — √e
fmt.Println(math.Log2E) // 1.4426950408889634 — log₂(e)
fmt.Println(math.Log10E) // 0.4342944819032518 — log₁₀(e)
fmt.Println(math.Ln2) // 0.6931471805599453 — ln(2)
}
Besides mathematical constants, math also defines the boundary values for Go’s numeric types:
// Maximum and minimum float64 values
fmt.Println(math.MaxFloat64) // 1.7976931348623157e+308
fmt.Println(math.SmallestNonzeroFloat64) // 5e-324
// Maximum integer values (useful for minimum-search algorithms)
fmt.Println(math.MaxInt8) // 127
fmt.Println(math.MaxInt16) // 32767
fmt.Println(math.MaxInt32) // 2147483647
fmt.Println(math.MaxInt64) // 9223372036854775807
fmt.Println(math.MinInt64) // -9223372036854775808
fmt.Println(math.MaxFloat32) // 3.4028234663852886e+38
Special Values: Inf and NaN #
math supports the IEEE 754 special values that you need to understand when working with float64:
// Infinity — the result of operations exceeding the float64 range
posInf := math.Inf(1) // +∞
negInf := math.Inf(-1) // -∞
fmt.Println(math.IsInf(posInf, 1)) // true
fmt.Println(math.IsInf(negInf, -1)) // true
fmt.Println(1.0 / 0.0) // compile error — Go doesn't allow this
fmt.Println(math.Log(0)) // -Inf
// NaN — Not a Number, the result of undefined operations
nan := math.NaN()
fmt.Println(math.IsNaN(nan)) // true
fmt.Println(nan == nan) // false! NaN is not equal to itself
fmt.Println(math.Sqrt(-1)) // NaN
// ANTI-PATTERN: comparing float64 with ==
var x float64 = math.Sqrt(-1)
if x == math.NaN() { // always false, even if x is NaN
fmt.Println("this is NaN")
}
// CORRECT: use math.IsNaN
if math.IsNaN(x) {
fmt.Println("this is NaN")
}
Rounding: Floor, Ceil, Round, and Trunc #
These four rounding functions have different behaviors and are often confusing if not understood well.
| Function | Behavior | Example (+2.7) | Example (-2.7) |
|---|---|---|---|
math.Floor(x) | Round down (floor) | 2 | -3 |
math.Ceil(x) | Round up (ceiling) | 3 | -2 |
math.Round(x) | Round to nearest (half away from zero) | 3 | -3 |
math.Trunc(x) | Truncate decimals (toward zero) | 2 | -2 |
x := 2.7
y := -2.7
fmt.Println(math.Floor(x), math.Floor(y)) // 2 -3
fmt.Println(math.Ceil(x), math.Ceil(y)) // 3 -2
fmt.Println(math.Round(x), math.Round(y)) // 3 -3
fmt.Println(math.Trunc(x), math.Trunc(y)) // 2 -2
// Mod — the remainder (modulo) for float64
fmt.Println(math.Mod(10.5, 3.0)) // 1.5
fmt.Println(math.Mod(-10.5, 3.0)) // -1.5
// Modf — split the integer and fractional parts
integer, fractional := math.Modf(3.75)
fmt.Println(integer, fractional) // 3 0.75
integer, fractional = math.Modf(-3.75)
fmt.Println(integer, fractional) // -3 -0.75
Rounding to N Decimal Places #
math doesn’t provide a function to round to N decimal places directly, but this pattern is very common in applications:
// Round to 2 decimal places
func roundTo(x float64, decimals int) float64 {
factor := math.Pow(10, float64(decimals))
return math.Round(x*factor) / factor
}
fmt.Println(roundTo(3.14159, 2)) // 3.14
fmt.Println(roundTo(2.675, 2)) // 2.68
fmt.Println(roundTo(1.005, 2)) // might not be 1.01 because of floating point!
Money rounding must not usefloat64. The binary representation offloat64can’t represent all decimals exactly — for example0.1 + 0.2produces0.30000000000000004, not0.3. For financial calculations, use integers (cents/smallest unit) or themath/bigpackage withbig.Rat/big.Float, which offer exact precision.
Roots and Powers #
Sqrt, Cbrt, and Pow #
// Square root
fmt.Println(math.Sqrt(16)) // 4
fmt.Println(math.Sqrt(2)) // 1.4142135623730951
fmt.Println(math.Sqrt(-1)) // NaN — the root of a negative is not real
// Cube root
fmt.Println(math.Cbrt(27)) // 3
fmt.Println(math.Cbrt(-8)) // -2 (unlike Sqrt, Cbrt supports negatives)
// Power — Pow(x, y) = x^y
fmt.Println(math.Pow(2, 10)) // 1024
fmt.Println(math.Pow(3, 3)) // 27
fmt.Println(math.Pow(4, 0.5)) // 2 (same as Sqrt(4))
fmt.Println(math.Pow(2, -1)) // 0.5
// Pow10 — powers of 10, more efficient than Pow(10, n)
fmt.Println(math.Pow10(3)) // 1000
fmt.Println(math.Pow10(-2)) // 0.01
Hypot — The Hypotenuse Length #
math.Hypot(p, q) computes √(p² + q²) with better numerical accuracy than a manual implementation:
// ANTI-PATTERN: compute manually — prone to overflow for large values
func distanceManual(x, y float64) float64 {
return math.Sqrt(x*x + y*y) // can overflow if x or y is very large
}
// CORRECT: use Hypot — handles edge cases internally
fmt.Println(math.Hypot(3, 4)) // 5
fmt.Println(math.Hypot(5, 12)) // 13
// Distance between two coordinate points
func distance(x1, y1, x2, y2 float64) float64 {
return math.Hypot(x2-x1, y2-y1)
}
Logarithms and Exponents #
Log, Log2, Log10 #
// Log — natural logarithm (base e)
fmt.Println(math.Log(math.E)) // 1
fmt.Println(math.Log(1)) // 0
fmt.Println(math.Log(0)) // -Inf
fmt.Println(math.Log(-1)) // NaN
// Log2 — base-2 logarithm
fmt.Println(math.Log2(1024)) // 10
fmt.Println(math.Log2(8)) // 3
// Log10 — base-10 logarithm
fmt.Println(math.Log10(1000)) // 3
fmt.Println(math.Log10(0.01)) // -2
// Logarithm to any base N — the change of base formula
func logN(x, base float64) float64 {
return math.Log(x) / math.Log(base)
}
fmt.Println(logN(81, 3)) // 4 (3^4 = 81)
Exp and Exp2 #
// Exp — e^x (the inverse of Log)
fmt.Println(math.Exp(1)) // 2.718281828459045 (= e)
fmt.Println(math.Exp(0)) // 1
fmt.Println(math.Exp(3)) // 20.085536923187668
// Exp2 — 2^x (the inverse of Log2)
fmt.Println(math.Exp2(10)) // 1024
fmt.Println(math.Exp2(0.5)) // 1.4142135623730951 (= √2)
// Expm1 — e^x - 1, accurate for x near zero
// Use this instead of Exp(x)-1 for very small x
fmt.Println(math.Expm1(0.0001)) // 0.00010000500016667084
fmt.Println(math.Exp(0.0001) - 1) // 0.00010000500016667084 (same)
fmt.Println(math.Expm1(1e-20)) // 1e-20 (accurate)
fmt.Println(math.Exp(1e-20) - 1) // 0 (precision lost!)
Trigonometry #
All trigonometric functions in math use radians, not degrees. Converting from degrees to radians uses the formula degrees × π / 180.
// Degrees <-> radians conversion
func toRadians(degrees float64) float64 {
return degrees * math.Pi / 180
}
func toDegrees(radians float64) float64 {
return radians * 180 / math.Pi
}
// Basic trigonometric functions
fmt.Println(math.Sin(math.Pi / 2)) // 1 (sin 90°)
fmt.Println(math.Cos(0)) // 1 (cos 0°)
fmt.Println(math.Tan(math.Pi / 4)) // 0.9999999999999999 ≈ 1 (tan 45°)
// Sin and Cos together (more efficient than calling both)
s, c := math.Sincos(math.Pi / 4)
fmt.Printf("sin(45°)=%.4f, cos(45°)=%.4f\n", s, c) // 0.7071, 0.7071
// Inverse functions (arc)
fmt.Println(math.Asin(1)) // 1.5707963267948966 (= π/2)
fmt.Println(math.Acos(1)) // 0
fmt.Println(math.Atan(1)) // 0.7853981633974483 (= π/4)
// Atan2 — the angle from coordinates (x, y), handling all quadrants
fmt.Println(math.Atan2(1, 1)) // π/4 (quadrant I)
fmt.Println(math.Atan2(1, -1)) // 3π/4 (quadrant II)
fmt.Println(math.Atan2(-1, -1)) // -3π/4 (quadrant III)
Pattern: Coordinate Conversion #
// Convert Cartesian coordinates to polar
func toPolar(x, y float64) (r, theta float64) {
r = math.Hypot(x, y)
theta = math.Atan2(y, x) // the angle in radians
return
}
// Convert polar coordinates to Cartesian
func toCartesian(r, theta float64) (x, y float64) {
x = r * math.Cos(theta)
y = r * math.Sin(theta)
return
}
r, theta := toPolar(3, 4)
fmt.Printf("r=%.2f, theta=%.4f rad (%.2f°)\n",
r, theta, toDegrees(theta)) // r=5.00, theta=0.9273 rad (53.13°)
Absolute Values, Min, and Max #
Abs, Min, Max, and Dim #
// Abs — the absolute value
fmt.Println(math.Abs(-5.5)) // 5.5
fmt.Println(math.Abs(3.2)) // 3.2
fmt.Println(math.Abs(0)) // 0
// Min and Max — the smallest/largest of two float64s
fmt.Println(math.Min(3.5, 7.2)) // 3.5
fmt.Println(math.Max(3.5, 7.2)) // 7.2
fmt.Println(math.Min(math.NaN(), 5)) // NaN — note this behavior
// Dim — max(x-y, 0) — useful for calculations that must not be negative
fmt.Println(math.Dim(5, 3)) // 2 (5-3=2)
fmt.Println(math.Dim(3, 5)) // 0 (3-5=-2, returned as 0)
fmt.Println(math.Dim(5, 5)) // 0
// Pattern: calculate remaining time (must not be negative)
func timeRemaining(deadline, now float64) float64 {
return math.Dim(deadline, now)
}
math.Minandmath.Maxonly work withfloat64. For integers (int,int64, etc.), Go 1.21 addedmin()andmax()as built-in functions that can be used directly without any import. For Go versions before 1.21, you need to write manual comparisons usingif.
Signbit and Copysign #
// Signbit — is the value negative (including -0 and -Inf)
fmt.Println(math.Signbit(-3.14)) // true
fmt.Println(math.Signbit(3.14)) // false
fmt.Println(math.Signbit(math.Inf(-1))) // true
// Copysign — copy the sign from y to x
fmt.Println(math.Copysign(5, -1)) // -5
fmt.Println(math.Copysign(5, 1)) // 5
fmt.Println(math.Copysign(-5, 1)) // 5
The math/rand Package — Random Numbers #
The math/rand package provides a fast pseudo-random number generator, suitable for simulation, testing, games, and data shuffling. This isn’t cryptographically secure random — for security, use crypto/rand.
Basic Usage (Go 1.20+) #
Since Go 1.20, math/rand automatically uses a random seed, so you no longer need to call rand.Seed() manually. The top-level functions can be used directly:
import (
"fmt"
"math/rand"
)
// Random integer in [0, n)
fmt.Println(rand.Intn(100)) // a random number 0-99
fmt.Println(rand.Intn(6) + 1) // a dice simulation: 1-6
// Random float in [0.0, 1.0)
fmt.Println(rand.Float64()) // e.g.: 0.6046602879796196
fmt.Println(rand.Float32())
// Float in the range [min, max)
func randomRange(min, max float64) float64 {
return min + rand.Float64()*(max-min)
}
fmt.Printf("%.2f\n", randomRange(1.5, 3.5)) // e.g.: 2.37
rand.New and rand.Source — Full Control #
For needs requiring reproducibility (for example testing or simulations that must be repeatable), create a rand.Rand instance with a known seed:
// Source with a fixed seed — produces the same sequence every time
src := rand.NewSource(42)
r := rand.New(src)
fmt.Println(r.Intn(100)) // always the same if the seed is the same
fmt.Println(r.Intn(100))
fmt.Println(r.Intn(100))
// This instance is not thread-safe — create one per goroutine
// or use a sync.Mutex for concurrent access
Shuffle — Randomizing Slice Order #
// Randomize a slice's order
fruits := []string{"apple", "orange", "mango", "banana", "durian"}
rand.Shuffle(len(fruits), func(i, j int) {
fruits[i], fruits[j] = fruits[j], fruits[i]
})
fmt.Println(fruits) // random order
// Take N random elements from a slice (sampling without replacement)
func takeRandom(slice []string, n int) []string {
copy := make([]string, len(slice))
copy(copy, slice)
rand.Shuffle(len(copy), func(i, j int) {
copy[i], copy[j] = copy[j], copy[i]
})
return copy[:n]
}
sample := takeRandom(fruits, 3)
fmt.Println(sample) // 3 random fruits
Statistical Distributions #
// NormFloat64 — the standard normal distribution (mean=0, stddev=1)
value := rand.NormFloat64()
// Convert to a specific mean and stddev: X = mean + stddev * NormFloat64()
mean := 170.0 // average height (cm)
stddev := 7.0
height := mean + stddev*rand.NormFloat64()
fmt.Printf("Simulated height: %.1f cm\n", height)
// ExpFloat64 — the exponential distribution (rate=1)
// Useful for simulating inter-arrival times (queuing theory)
waitTime := rand.ExpFloat64()
fmt.Printf("Simulated wait time: %.3f\n", waitTime)
Security: math/rand vs crypto/rand #
flowchart TD
A{"What is this\nrandom number for?"} --> B{"Cryptographic\nsecurity?"}
B -- Yes --> C["crypto/rand"]
B -- No --> D{"Reproducible\nfor testing?"}
D -- Yes --> E["rand.New with\na fixed seed"]
D -- No --> F["rand.Intn / rand.Float64\ndirectly — Go 1.20+"]
C --> G["Tokens, passwords,\nencryption keys,\nCSRF tokens"]
E --> H["Simulation, unit tests,\ndummy data"]
F --> I["Games, shuffle,\nsampling, UI"]// math/rand — DON'T use for security
token := fmt.Sprintf("%d", rand.Int63()) // ✗ predictable
// crypto/rand — for security tokens
import cryptorand "crypto/rand"
import "encoding/hex"
b := make([]byte, 16)
cryptorand.Read(b)
token := hex.EncodeToString(b) // ✓ cryptographically secure
The math/big Package — Arbitrary Precision #
Go’s built-in numeric types have limits: int64 maxes out around 9.2 × 10¹⁸ and float64 loses precision for certain decimals. The math/big package provides three types to go beyond these limits.
| Type | Use |
|---|---|
big.Int | Arbitrary-precision integers — large factorials, cryptography |
big.Float | Arbitrary-precision floats — scientific computation, financial calculations |
big.Rat | Exact rational numbers (p/q) — avoid floating point errors entirely |
big.Int — Unlimited Integers #
import "math/big"
// Factorial of 100 — far beyond int64
func factorial(n int64) *big.Int {
result := big.NewInt(1)
for i := int64(2); i <= n; i++ {
result.Mul(result, big.NewInt(i))
}
return result
}
fmt.Println(factorial(20)) // 2432902008176640000
fmt.Println(factorial(100)) // 93326215443944152681699238856266700490715968264381621468592963895217...
// big.Int operations
a := big.NewInt(1000000000000) // 1 trillion
b := big.NewInt(999999999999)
sum := new(big.Int).Add(a, b)
prod := new(big.Int).Mul(a, b)
fmt.Println(sum) // 1999999999999
fmt.Println(prod) // 999999999999000000000000
// Parsing from a string
c, ok := new(big.Int).SetString("12345678901234567890", 10)
if ok {
fmt.Println(c)
}
// Comparison
cmp := a.Cmp(b) // -1 (a < b), 0 (a == b), 1 (a > b)
fmt.Println(cmp) // 1 (a > b)
big.Float — High-Precision Floats #
// Calculate π with 200-bit precision
func calculatePi() *big.Float {
// A simple implementation with the Leibniz series (illustration only)
prec := uint(200)
pi := new(big.Float).SetPrec(prec)
// ... the actual implementation using a more efficient algorithm
return pi
}
// Basic big.Float usage
a := new(big.Float).SetPrec(256).SetFloat64(1.0)
b := new(big.Float).SetPrec(256).SetFloat64(3.0)
result := new(big.Float).Quo(a, b) // 1/3 with high precision
fmt.Println(result.Text('f', 50)) // 0.33333333333333333333333333333333333333333333333333
// Compare with a regular float64
fmt.Println(1.0 / 3.0) // 0.3333333333333333 (only 16 digits)
big.Rat — Exact Rational Numbers #
big.Rat represents numbers as a fraction p/q without losing any precision. This is the best solution for financial or scientific calculations needing exact precision.
// big.Rat — no floating point errors
a := big.NewRat(1, 10) // 1/10 = 0.1
b := big.NewRat(2, 10) // 2/10 = 0.2
sum := new(big.Rat).Add(a, b)
fmt.Println(sum) // 3/10
fmt.Println(sum.FloatString(1)) // 0.3 (exact!)
// Compare with float64
fmt.Println(0.1 + 0.2) // 0.30000000000000004 (not exact)
fmt.Println(0.1 + 0.2 == 0.3) // false
// big.Rat for price calculations
price := big.NewRat(9999, 100) // Rp 99.99
tax := big.NewRat(11, 100) // 11%
taxValue := new(big.Rat).Mul(price, tax)
total := new(big.Rat).Add(price, taxValue)
fmt.Println(total.FloatString(2)) // "110.99" — exact
Safe Float Comparison #
Comparing float64 with == often produces unexpected results because the binary representation isn’t always exact. Use epsilon comparison to compare float values.
// ANTI-PATTERN: compare floats with == directly
a := 0.1 + 0.2
b := 0.3
if a == b { // false! even though mathematically equal
fmt.Println("equal")
}
// CORRECT: use an epsilon tolerance
const epsilon = 1e-9
func almostEqual(a, b float64) bool {
return math.Abs(a-b) < epsilon
}
fmt.Println(almostEqual(0.1+0.2, 0.3)) // true
// For relative comparison (more robust for large/small numbers)
func almostEqualRelative(a, b, tolerance float64) bool {
if a == b {
return true
}
diff := math.Abs(a - b)
norm := math.Max(math.Abs(a), math.Abs(b))
return diff/norm < tolerance
}
fmt.Println(almostEqualRelative(1e10+0.001, 1e10, 1e-9)) // true
fmt.Println(almostEqualRelative(1.0, 2.0, 1e-9)) // false
Real-World Patterns #
Calculating the Haversine Distance (Earth Coordinates) #
The distance between two points on the Earth’s surface can’t be calculated with ordinary Pythagoras — it needs the Haversine formula, which accounts for the Earth’s curvature:
const earthRadius = 6371.0 // km
func haversineDistance(lat1, lon1, lat2, lon2 float64) float64 {
// Convert degrees to radians
dLat := toRadians(lat2 - lat1)
dLon := toRadians(lon2 - lon1)
lat1 = toRadians(lat1)
lat2 = toRadians(lat2)
a := math.Sin(dLat/2)*math.Sin(dLat/2) +
math.Cos(lat1)*math.Cos(lat2)*
math.Sin(dLon/2)*math.Sin(dLon/2)
c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
return earthRadius * c
}
func toRadians(degrees float64) float64 {
return degrees * math.Pi / 180
}
// Jakarta to Surabaya
distance := haversineDistance(-6.2088, 106.8456, -7.2575, 112.7521)
fmt.Printf("Jakarta-Surabaya distance: %.0f km\n", distance) // ~664 km
Descriptive Statistics #
func statistics(data []float64) (mean, variance, stddev float64) {
n := float64(len(data))
if n == 0 {
return
}
// Calculate the mean
for _, v := range data {
mean += v
}
mean /= n
// Calculate the variance (Welford's method for numerical stability)
for _, v := range data {
diff := v - mean
variance += diff * diff
}
variance /= n
stddev = math.Sqrt(variance)
return
}
data := []float64{2, 4, 4, 4, 5, 5, 7, 9}
mean, variance, stddev := statistics(data)
fmt.Printf("Mean: %.2f\n", mean) // 5.00
fmt.Printf("Variance: %.2f\n", variance) // 4.00
fmt.Printf("Stddev: %.2f\n", stddev) // 2.00
Data Normalization (Min-Max Scaling) #
func normalize(data []float64) []float64 {
if len(data) == 0 {
return nil
}
minVal := data[0]
maxVal := data[0]
for _, v := range data[1:] {
minVal = math.Min(minVal, v)
maxVal = math.Max(maxVal, v)
}
span := maxVal - minVal
if span == 0 {
return make([]float64, len(data)) // all zeros if everything is the same
}
result := make([]float64, len(data))
for i, v := range data {
result[i] = (v - minVal) / span
}
return result
}
data := []float64{10, 20, 30, 40, 50}
fmt.Println(normalize(data)) // [0 0.25 0.5 0.75 1]
A Simple Unique ID Generator #
import (
cryptorand "crypto/rand"
"encoding/binary"
"fmt"
)
// A random 6-digit numeric ID (for OTP codes, PINs, etc.)
func generateOTP() string {
var b [8]byte
cryptorand.Read(b[:])
n := binary.BigEndian.Uint64(b[:])
otp := n % 1000000 // 6 digits
return fmt.Sprintf("%06d", otp)
}
fmt.Println(generateOTP()) // "047291" (always 6 digits)
When to Switch to Alternatives #
Keep using math if:
✓ Common mathematical operations: roots, powers, trigonometry, logarithms
✓ Rounding and absolute values for float64
✓ Random numbers for simulation, games, testing, or data shuffling
✓ Mathematical constants (Pi, E, Phi, etc.)
✓ Checking NaN, Inf, and the boundary values of numeric types
Consider math/big if:
✗ Integers exceeding 9.2 × 10¹⁸ (the int64 limit)
✗ Financial calculations needing exact precision (use big.Rat)
✗ Cryptography requiring high-precision modular arithmetic
✗ Scientific computation needing more than 15-16 digits of precision
Consider crypto/rand if:
✗ Creating security tokens, session IDs, or encryption keys
✗ Any security-related random number needs
Consider external packages if:
✗ Linear algebra, matrices, FFT → gonum.org/v1/gonum
✗ Advanced statistics → gonum.org/v1/gonum/stat
✗ Symbolic computation or complex scientific calculations → the gonum ecosystem
Summary #
- The
math.Pi,math.E,math.Phiconstants — already available with fullfloat64precision; no need to redefine them manually.math.IsNaNandmath.IsInf— always use these functions to check for NaN and Inf; comparingNaN == NaNis alwaysfalseand will trap you.math.Floor,math.Ceil,math.Round,math.Trunc— have different behaviors especially for negative numbers; understand the differences before using them.- Don’t use
float64for money — binary representation errors (0.1 + 0.2 ≠ 0.3) can cause financial discrepancies; use integers (cents) orbig.Rat.math.Hypot(p, q)— more accurate thanmath.Sqrt(p*p + q*q)because it avoids overflow for large values.math/randis auto-seeded since Go 1.20 — no morerand.Seed()needed; userand.New(rand.NewSource(n))only if you need reproducibility.math/randisn’t for security — usecrypto/randfor tokens, passwords, encryption keys, and all cryptographic needs.math/bigprovidesbig.Int(unlimited integers),big.Float(arbitrary precision), andbig.Rat(exact rationals) — choose according to your precision needs.- Compare floats with an epsilon, not
==— usemath.Abs(a-b) < epsilonfor comparisons robust against representation errors.