|
| 1 | +# fma128.jl — software `fma` for Float128 |
| 2 | +# |
| 3 | +# This file is written for inclusion in Quadmath.jl: |
| 4 | +# |
| 5 | +# include("fma128.jl") # add to src/Quadmath.jl, after the |
| 6 | +# # existing `@static if !Sys.iswindows()` |
| 7 | +# # fma definition |
| 8 | +# |
| 9 | +# On Windows, libquadmath's `fmaq` subtly corrupts floating-point state |
| 10 | +# (issue #31), so Quadmath deliberately leaves `fma(::Float128, ::Float128, |
| 11 | +# ::Float128)` undefined there and callers hit Base's promotion fallback, |
| 12 | +# which throws `ErrorException("fma not defined for Float128")`. This file |
| 13 | +# provides `fma_emulated`, a pure-Julia soft-float fused multiply-add that |
| 14 | +# never calls into libquadmath (bit-level `reinterpret` and integer |
| 15 | +# arithmetic only, so the FP-state corruption cannot occur), and binds it to |
| 16 | +# `fma` on Windows. It is: |
| 17 | +# |
| 18 | +# * IEEE 754 correctly rounded (round-to-nearest, ties-to-even, one |
| 19 | +# rounding), with signed zeros, gradual underflow, and overflow; |
| 20 | +# * bit-for-bit identical to libquadmath's `fmaq` — including NaN payload |
| 21 | +# propagation and invalid-operation NaN generation (glibc soft-fp x86 |
| 22 | +# semantics) — so Windows results match Linux/macOS exactly. Validated |
| 23 | +# against native `fmaq` on ~19 million cases (random all-class bit |
| 24 | +# patterns; normal, subnormal-heavy, overflow-boundary, cancellation, |
| 25 | +# and constructed-tie batteries; NaN/special fuzz; targeted |
| 26 | +# subnormal-multiplicand/addend populations) with zero mismatches, and |
| 27 | +# against an independent high-precision MPFR nearest/ties-to-even |
| 28 | +# oracle; |
| 29 | +# * allocation-free, ~30 ns/call on x86-64 (roughly 30x faster than the |
| 30 | +# `fmaq` ccall round-trip), with inferred effects consistent / |
| 31 | +# effect-free / terminating. |
| 32 | +# |
| 33 | +# Algorithm: classic soft-float mulAdd on the raw binary128 bit patterns. |
| 34 | +# Operands are decomposed into sign / unbiased exponent / 113-bit |
| 35 | +# significand (subnormals normalized); the exact 113x113 -> 226-bit product |
| 36 | +# is formed with four 64x64 -> 128-bit multiplies; product and addend are |
| 37 | +# held in a 256-bit fixed-point accumulator (a pair of UInt128) with the |
| 38 | +# leading significand bit pinned at bit 254; alignment uses shift-right-jam |
| 39 | +# (any shifted-out bit sets the LSB as a sticky). Jamming preserves correct |
| 40 | +# rounding here because a jam can only occur when the exponent gap exceeds |
| 41 | +# ~30 (product shifted) or ~142 (addend shifted), in which case catastrophic |
| 42 | +# cancellation is impossible and the jam bit stays >= 100 positions below |
| 43 | +# the guard bit; conversely, whenever cancellation can occur (gap <= 1) the |
| 44 | +# alignment shift loses no bits and the subtraction is exact. A single |
| 45 | +# round-to-nearest-even at 113 bits finishes (guard bit 141, sticky bits |
| 46 | +# 0..140), with a pre-round jam-shift onto the subnormal grid when the |
| 47 | +# result underflows (the minimum-normal rounding carry falls out of the bit |
| 48 | +# assembly automatically) and overflow to +-Inf. |
| 49 | +# |
| 50 | +# NaN semantics match libquadmath (glibc soft-fp `_FP_CHOOSENAN` for x86), |
| 51 | +# determined empirically against `fmaq`: among competing NaNs the one with |
| 52 | +# the larger raw 112-bit fraction field wins (the quiet bit participates in |
| 53 | +# the comparison); ties go to the product-stage NaN; an invalid product |
| 54 | +# (0 * Inf) or an invalid sum (Inf - Inf) generates the x86 "indefinite" |
| 55 | +# NaN 0xffff8000...0, which competes by the same rule; the product-stage |
| 56 | +# survivor is quieted BEFORE competing with the addend (this matters when |
| 57 | +# both stages hold signaling NaNs); the winner is returned with its quiet |
| 58 | +# bit set, sign and payload preserved. Floating-point exception flags are |
| 59 | +# not modeled (Julia does not expose them for Float128). |
| 60 | + |
| 61 | +# --------------------------------------------------------------------------- |
| 62 | +# 256-bit helpers on (hi::UInt128, lo::UInt128) pairs |
| 63 | +# --------------------------------------------------------------------------- |
| 64 | + |
| 65 | +# Exact 128x128 -> 256-bit product. |
| 66 | +@inline function _fma_mul256(x::UInt128, y::UInt128) |
| 67 | + m64 = UInt128(typemax(UInt64)) |
| 68 | + x0 = x & m64; x1 = x >> 64 |
| 69 | + y0 = y & m64; y1 = y >> 64 |
| 70 | + p00 = x0 * y0 # each factor < 2^64: exact in UInt128 |
| 71 | + p01 = x0 * y1 |
| 72 | + p10 = x1 * y0 |
| 73 | + p11 = x1 * y1 |
| 74 | + mid = (p00 >> 64) + (p01 & m64) + (p10 & m64) # < 3*2^64, no overflow |
| 75 | + lo = (mid << 64) | (p00 & m64) |
| 76 | + hi = p11 + (p01 >> 64) + (p10 >> 64) + (mid >> 64) |
| 77 | + return hi, lo |
| 78 | +end |
| 79 | + |
| 80 | +@inline function _fma_add256(ah::UInt128, al::UInt128, bh::UInt128, bl::UInt128) |
| 81 | + lo = al + bl |
| 82 | + hi = ah + bh + (lo < al ? one(UInt128) : zero(UInt128)) |
| 83 | + return hi, lo |
| 84 | +end |
| 85 | + |
| 86 | +# a - b, assuming a >= b. |
| 87 | +@inline function _fma_sub256(ah::UInt128, al::UInt128, bh::UInt128, bl::UInt128) |
| 88 | + lo = al - bl |
| 89 | + hi = ah - bh - (al < bl ? one(UInt128) : zero(UInt128)) |
| 90 | + return hi, lo |
| 91 | +end |
| 92 | + |
| 93 | +# Left shift by 0 <= s < 256 (callers never shift set bits past bit 255). |
| 94 | +@inline function _fma_shl256(hi::UInt128, lo::UInt128, s::Int) |
| 95 | + if s == 0 |
| 96 | + return hi, lo |
| 97 | + elseif s < 128 |
| 98 | + return (hi << s) | (lo >> (128 - s)), lo << s |
| 99 | + else |
| 100 | + return lo << (s - 128), zero(UInt128) # s == 128 gives (lo, 0) |
| 101 | + end |
| 102 | +end |
| 103 | + |
| 104 | +# Right shift by s >= 0 with "jamming": any shifted-out bit sets the LSB. |
| 105 | +@inline function _fma_shr256jam(hi::UInt128, lo::UInt128, s::Int) |
| 106 | + if s == 0 |
| 107 | + return hi, lo |
| 108 | + elseif s < 128 |
| 109 | + sticky = (lo << (128 - s)) != 0 |
| 110 | + nlo = (lo >> s) | (hi << (128 - s)) |
| 111 | + return hi >> s, nlo | (sticky ? one(UInt128) : zero(UInt128)) |
| 112 | + elseif s < 256 |
| 113 | + t = s - 128 # t == 0: hi << 128 == 0 |
| 114 | + sticky = (lo != 0) | ((hi << (128 - t)) != 0) |
| 115 | + return zero(UInt128), (hi >> t) | (sticky ? one(UInt128) : zero(UInt128)) |
| 116 | + else |
| 117 | + sticky = (hi | lo) != 0 |
| 118 | + return zero(UInt128), (sticky ? one(UInt128) : zero(UInt128)) |
| 119 | + end |
| 120 | +end |
| 121 | + |
| 122 | +@inline _fma_lz256(hi::UInt128, lo::UInt128) = |
| 123 | + hi == 0 ? 128 + leading_zeros(lo) : leading_zeros(hi) |
| 124 | + |
| 125 | +# Decompose |x| (finite, nonzero) into (sig, e) with |
| 126 | +# value == sig * 2^(e - 112) and sig in [2^112, 2^113); subnormals normalized. |
| 127 | +@inline function _fma_split(a::UInt128) |
| 128 | + ef = Int((a >> significand_bits(Float128)) % UInt16) |
| 129 | + fr = a & significand_mask(Float128) |
| 130 | + if ef == 0 # subnormal (fr != 0) |
| 131 | + s = leading_zeros(fr) - exponent_bits(Float128) # bring MSB to bit 112 |
| 132 | + return fr << s, 1 - exponent_bias(Float128) - s |
| 133 | + else |
| 134 | + return fr | (one(UInt128) << significand_bits(Float128)), |
| 135 | + ef - exponent_bias(Float128) |
| 136 | + end |
| 137 | +end |
| 138 | + |
| 139 | +# NaN choice, matching libquadmath (glibc soft-fp, x86 _FP_CHOOSENAN): |
| 140 | +# the larger raw fraction field wins, ties to the first argument. |
| 141 | +@inline _fma_choosenan(a::UInt128, b::UInt128) = |
| 142 | + (a & significand_mask(Float128)) >= (b & significand_mask(Float128)) ? a : b |
| 143 | + |
| 144 | +# --------------------------------------------------------------------------- |
| 145 | +# the fused multiply-add |
| 146 | +# --------------------------------------------------------------------------- |
| 147 | + |
| 148 | +""" |
| 149 | + fma_emulated(x::Float128, y::Float128, z::Float128) -> Float128 |
| 150 | +
|
| 151 | +Software fused multiply-add: the correctly rounded (round-to-nearest, |
| 152 | +ties-to-even) `x*y + z` with a single rounding, computed in pure Julia |
| 153 | +integer arithmetic; bit-for-bit compatible with libquadmath's `fmaq`, |
| 154 | +including NaN payload propagation. Used as the `fma` implementation on |
| 155 | +Windows, where calling `fmaq` corrupts floating-point state (issue #31). |
| 156 | +""" |
| 157 | +function fma_emulated(x::Float128, y::Float128, z::Float128) |
| 158 | + quiet = one(UInt128) << (significand_bits(Float128) - 1) |
| 159 | + # the x86 "indefinite" NaN produced by invalid operations |
| 160 | + default_nan = sign_mask(Float128) | exponent_mask(Float128) | quiet |
| 161 | + |
| 162 | + ux = reinterpret(UInt128, x) |
| 163 | + uy = reinterpret(UInt128, y) |
| 164 | + uz = reinterpret(UInt128, z) |
| 165 | + ax = ux & ~sign_mask(Float128) |
| 166 | + ay = uy & ~sign_mask(Float128) |
| 167 | + az = uz & ~sign_mask(Float128) |
| 168 | + |
| 169 | + sp = ((ux ⊻ uy) & sign_mask(Float128)) != 0 # sign of the product |
| 170 | + sz = (uz & sign_mask(Float128)) != 0 |
| 171 | + |
| 172 | + # ---- specials: NaN propagation and invalid operations --------------- |
| 173 | + expmask = exponent_mask(Float128) |
| 174 | + xn = ax > expmask; yn = ay > expmask; zn = az > expmask |
| 175 | + if xn | yn | zn || |
| 176 | + ((ax == expmask) & (ay == 0)) || ((ay == expmask) & (ax == 0)) |
| 177 | + local t::UInt128 |
| 178 | + have_t = true |
| 179 | + if xn & yn |
| 180 | + t = _fma_choosenan(ux, uy) |
| 181 | + elseif xn |
| 182 | + t = ux |
| 183 | + elseif yn |
| 184 | + t = uy |
| 185 | + elseif !zn # 0 * Inf, z not NaN |
| 186 | + return reinterpret(Float128, default_nan) |
| 187 | + elseif ((ax == expmask) & (ay == 0)) || ((ay == expmask) & (ax == 0)) |
| 188 | + t = default_nan # 0 * Inf competes with NaN z |
| 189 | + else |
| 190 | + have_t = false # only z is NaN |
| 191 | + t = zero(UInt128) |
| 192 | + end |
| 193 | + # soft-fp quiets the product-stage NaN when packing the intermediate |
| 194 | + # result, BEFORE it competes with z (whose fraction stays raw) |
| 195 | + t |= quiet |
| 196 | + r = have_t ? (zn ? _fma_choosenan(t, uz) : t) : uz |
| 197 | + return reinterpret(Float128, r | quiet) |
| 198 | + end |
| 199 | + if ax == expmask || ay == expmask # x or y infinite (no NaN) |
| 200 | + if az == expmask && sz != sp |
| 201 | + return reinterpret(Float128, default_nan) # Inf - Inf |
| 202 | + end |
| 203 | + return reinterpret(Float128, |
| 204 | + (sp ? sign_mask(Float128) : zero(UInt128)) | expmask) |
| 205 | + end |
| 206 | + az == expmask && return z # finite*finite + Inf |
| 207 | + if ax == 0 || ay == 0 # product is a zero |
| 208 | + if az == 0 |
| 209 | + # (+-0) + (+-0): same signs keep the sign, else +0 (RN) |
| 210 | + return sp == sz ? |
| 211 | + reinterpret(Float128, sp ? sign_mask(Float128) : zero(UInt128)) : |
| 212 | + reinterpret(Float128, zero(UInt128)) |
| 213 | + end |
| 214 | + return z |
| 215 | + end |
| 216 | + |
| 217 | + # ---- exact product in 256-bit fixed point --------------------------- |
| 218 | + sigx, ex = _fma_split(ax) |
| 219 | + sigy, ey = _fma_split(ay) |
| 220 | + ph, pl = _fma_mul256(sigx, sigy) # value = P * 2^(ex+ey-224), MSB at 224 or 225 |
| 221 | + msb = (ph >> 97) != 0 ? 225 : 224 # bit 225 of P == bit 97 of ph |
| 222 | + Ep = ex + ey + (msb - 224) # value = (M/2^254) * 2^Ep after the shift |
| 223 | + ph, pl = _fma_shl256(ph, pl, 254 - msb) # normalize: leading bit at 254 |
| 224 | + |
| 225 | + local Mh::UInt128, Ml::UInt128 |
| 226 | + local E::Int |
| 227 | + local sres::Bool |
| 228 | + |
| 229 | + if az == 0 |
| 230 | + Mh, Ml, E, sres = ph, pl, Ep, sp |
| 231 | + else |
| 232 | + sigz, ez = _fma_split(az) |
| 233 | + zh, zl = _fma_shl256(zero(UInt128), sigz, 142) # leading bit at 254 |
| 234 | + d = Ep - ez |
| 235 | + if d >= 0 |
| 236 | + zh, zl = _fma_shr256jam(zh, zl, min(d, 300)) # align z to product |
| 237 | + E = Ep |
| 238 | + else |
| 239 | + ph, pl = _fma_shr256jam(ph, pl, min(-d, 300)) # align product to z |
| 240 | + E = ez |
| 241 | + end |
| 242 | + if sp == sz # effective addition |
| 243 | + Mh, Ml = _fma_add256(ph, pl, zh, zl) |
| 244 | + sres = sp |
| 245 | + if (Mh & sign_mask(Float128)) != 0 # carry into bit 255 |
| 246 | + Mh, Ml = _fma_shr256jam(Mh, Ml, 1) |
| 247 | + E += 1 |
| 248 | + end |
| 249 | + else # effective subtraction |
| 250 | + if ph > zh || (ph == zh && pl >= zl) |
| 251 | + Mh, Ml = _fma_sub256(ph, pl, zh, zl) |
| 252 | + sres = sp |
| 253 | + else |
| 254 | + Mh, Ml = _fma_sub256(zh, zl, ph, pl) |
| 255 | + sres = sz |
| 256 | + end |
| 257 | + if Mh == 0 && Ml == 0 |
| 258 | + return reinterpret(Float128, zero(UInt128)) # exact cancel -> +0 (RN) |
| 259 | + end |
| 260 | + sh = _fma_lz256(Mh, Ml) - 1 # restore leading bit to 254 |
| 261 | + if sh > 0 |
| 262 | + Mh, Ml = _fma_shl256(Mh, Ml, sh) |
| 263 | + E -= sh |
| 264 | + end |
| 265 | + end |
| 266 | + end |
| 267 | + |
| 268 | + # ---- round to nearest even at 113 bits ------------------------------ |
| 269 | + be = E + exponent_bias(Float128) # tentative exponent field |
| 270 | + if be >= exponent_raw_max(Float128) # certain overflow |
| 271 | + return reinterpret(Float128, |
| 272 | + (sres ? sign_mask(Float128) : zero(UInt128)) | expmask) |
| 273 | + end |
| 274 | + if be <= 0 # subnormal range: pre-shift |
| 275 | + Mh, Ml = _fma_shr256jam(Mh, Ml, min(1 - be, 300)) |
| 276 | + be = 0 |
| 277 | + end |
| 278 | + |
| 279 | + sig = Mh >> 14 # bits 142..254 -> 113 bits |
| 280 | + guard = (Mh >> 13) & 1 # bit 141 |
| 281 | + sticky = ((Mh & ((one(UInt128) << 13) - 1)) | Ml) != 0 # bits 0..140 |
| 282 | + if guard == 1 && (sticky || (sig & 1) == 1) |
| 283 | + sig += 1 |
| 284 | + end |
| 285 | + |
| 286 | + local r::UInt128 |
| 287 | + if be == 0 |
| 288 | + r = sig # a carry to 2^112 is exactly the minimum normal |
| 289 | + else |
| 290 | + if sig == (one(UInt128) << 113) # carry out of 113 bits |
| 291 | + sig >>= 1 |
| 292 | + be += 1 |
| 293 | + be >= exponent_raw_max(Float128) && return reinterpret(Float128, |
| 294 | + (sres ? sign_mask(Float128) : zero(UInt128)) | expmask) |
| 295 | + end |
| 296 | + r = (UInt128(be) << significand_bits(Float128)) | |
| 297 | + (sig & significand_mask(Float128)) |
| 298 | + end |
| 299 | + return reinterpret(Float128, |
| 300 | + (sres ? sign_mask(Float128) : zero(UInt128)) | r) |
| 301 | +end |
| 302 | + |
| 303 | +# On Windows the fmaq binding is deliberately absent (issue #31); use the |
| 304 | +# software implementation. Elsewhere the native correctly-rounded fmaq |
| 305 | +# defined in Quadmath.jl keeps priority, and `fma_emulated` remains |
| 306 | +# available for testing. |
| 307 | +@static if Sys.iswindows() |
| 308 | + @assume_effects :foldable fma(x::Float128, y::Float128, z::Float128) = |
| 309 | + fma_emulated(x, y, z) |
| 310 | +end |
0 commit comments