Prefer to read without ads? Become a member — from $10/month — and support the work. Already a member? Log in to read ad-free on this device.

8.3 Floating Point Support

Fast native floating point hardware is the raison d’etre for GPUs, and in many ways they are equal to or superior to CPUs in their floating point implementation. Denormals are supported at full speed10, directed rounding may be specified on a per-instruction basis, and the Special Function Units deliver high-performance approximation functions to six popular single-precision transcendentals. In contrast, x86 CPUs implement denormals in microcode that runs perhaps 100x slower than operating on normalized floating point operands; rounding direction is specified by a control word that takes dozens of clock cycles to change; and the only transcendental approximation functions in the SSE instruction set are for reciprocal and reciprocal square root, which give 12-bit approximations that must be refined with a Newton-Raphson iteration before being used.

Since GPUs’ greater core counts are offset somewhat by their lower clock frequencies, developers can expect at most a 10x (or thereabouts) speedup on a level playing field. If a paper reports a 100x or greater speedup from porting an optimized CPU implementation to CUDA, chances are one of the above-described “instruction set mismatches” played a role.

8.3.1 Formats

Figure 8-2 depicts the IEEE standard floating point formats CUDA supports, from half precision (16-bit) up to quadruple precision (128-bit). For its first decade, CUDA offered only double, single, and half; the deep learning era has since added a family of narrower formats (Section 8.3.5, Figure 8-3), while quadruple precision—new, and software-emulated—extends the range in the other direction. Each value is divided into three fields: sign, exponent, and mantissa. For half, single, double and quad, the exponent fields are 5, 8, 11 and 15 bits in size, respectively; the corresponding mantissa fields are 10, 23, 52 and 112 bits.

Figure 8-2. Floating Point Formats

The exponent field changes the interpretation of the floating point value. The most common (“normal”) representation encodes an implicit 1 bit into the mantissa, and multiplies that value by 2e-bias, where bias is the value added to the actual exponent before encoding into the floating point representation. The bias for single precision, for example, is 127.

Table 8-7 summarizes how floating-point values are encoded. For most exponent values (so-called “normal” floating-point values), the mantissa is assumed to have an implicit 1, and is multiplied by the biased value of the exponent. The maximum exponent value is reserved for infinity and Not-A-Number values. Dividing by zero (or overflowing a division) yields infinity; performing an invalid operation (such as taking the square root or logarithm of a negative number) yields a NaN. The minimum exponent value is reserved for values too small to represent with the implicit leading 1: as the so-called denormals11 get closer to zero, they lose bits of effective precision, a phenomenon known as gradual underflow.

Table 8-8 gives the encodings and values of certain extreme values for each format.

Exponent Mantissa Value Case Name
Quadruple precision
0 0 ±0 Zero
0 Nonzero ±2-16382(0.mantissa) Denormal
1 to 32766 Any ±2e-16383(1.mantissa) Normal
32767 0 ±∞ Infinity
32767 Nonzero Not a number
Double precision
0 0 ±0 Zero
0 Nonzero ±2-1022(0.mantissa) Denormal
1 to 2046 Any ±2e-1023(1.mantissa) Normal
2047 0 ±∞ Infinity
2047 Nonzero Not a number
Single precision
0 0 ±0 Zero
0 Nonzero ±2-126(0.mantissa) Denormal
1 to 254 Any ±2e-127(1.mantissa) Normal
255 0 ±∞ Infinity
255 Nonzero Not a number
Half precision
0 0 ±0 Zero
0 Nonzero ±2-14(0.mantissa) Denormal
1 to 30 Any ±2e-15(1.mantissa) Normal
31 0 ±∞ Infinity
31 Nonzero Not a number

Table 8-7. Floating Point Representations

Hexadecimal Exact value
Quadruple precision
Smallest denormal 0...01 2-16494
Largest denormal 0000 F...F 2-16382(1-2-112)
Smallest normal 0001 0...0 2-16382
1.0 3FFF 0...0 1
Maximum integer 4070 0...0 2113
Largest normal 7FFE F...F 216384(1-2-113)
Infinity 7FFF 0...0 Infinity
Double precision
Smallest denormal 0...0001 2-1074
Largest denormal 000F...F 2-1022(1-2-52)
Smallest normal 0010...0 2-1022
1.0 3FF0...0 1
Maximum integer 4340...0 253
Largest normal 7FEF...F 21024(1-2-53)
Infinity 7FF0...0 Infinity
Single precision
Smallest denormal 00000001 2-149
Largest denormal 007FFFFF 2-126(1-2-23)
Smallest normal 00800000 2-126
1.0 3F800000 1
Maximum integer 4B800000 224
Largest normal 7F7FFFFF 2128(1-2-24)
Infinity 7F800000 Infinity
Half precision
Smallest denormal 0001 2-24
Largest denormal 07FF 2-14(1-2-10)
Smallest normal 0800 2-14
1.0 3c00 1
Maximum integer 6800 211
Largest normal 7BFF 216(1-2-11)
Infinity 7C00 Infinity

Table 8-8. Floating Point Extreme Values

Quadruple precision (__float128) is the newest and most unusual of these formats. GPUs have no 128-bit floating point hardware, so CUDA emulates it in software: the __nv_fp128_ routines in crt/device_fp128_functions.h__nv_fp128_sqrt(), __nv_fp128_sin(), and the rest—build IEEE binary128 arithmetic out of integer operations. It is far slower than the hardware formats and is not meant for bulk computation; its purpose is the occasional extended-precision calculation—a trustworthy reference result, or an ill-conditioned reduction—where double’s 53 bits of significand are not enough. With 15 exponent bits and 112 mantissa bits (bias 16383), it spans roughly ±1.19×104932 and carries about 34 decimal digits of precision.

Rounding

The IEEE standard provides for four (4) round modes:

Round-to-nearest, where intermediate values are rounded to the nearest representable floating point value after each operation, is by far the most commonly used round mode. Round up and round down (the “directed rounding modes”) are used for interval arithmetic, where a pair of floating point values are used to bracket the intermediate result of a computation. To correctly bracket a result, the lower and upper values of the interval must be rounded toward negative infinity (“down”) and toward positive infinity (“up”), respectively.

The C language does not provide any way to specify round modes on a per-instruction basis, and CUDA hardware does not provide a control word to implicitly specify rounding modes. Consequently, CUDA provides a set of intrinsics to specify the round mode of an operation, as summarized in Table 8-9.

Intrinsic Operation
__fadd_[rn|rz|ru|rd] Addition
__fmul_[rn|rz|ru|rd] Multiplication
__fmaf_[rn|rz|ru|rd] Fused multiply-add
__frcp_[rn|rz|ru|rd] Reciprocal
__fdiv_[rn|rz|ru|rd] Division
__fsqrt_[rn|rz|ru|rd] Square root
__dadd_[rn|rz|ru|rd] Addition
__dmul_[rn|rz|ru|rd] Multiplication
__fma_[rn|rz|ru|rd] Fused multiply-add
__drcp_[rn|rz|ru|rd] Reciprocal
__ddiv_[rn|rz|ru|rd] Division
__dsqrt_[rn|rz|ru|rd] Square root

Table 8-9. Intrinsics for Rounding

Conversion

In general, developers can convert between different floating-point representations and/or integers using standard C constructs: implicit conversion or explicit typecasts. If necessary, however, developers can use the intrinsics listed in Table 8-10 to perform conversions that are not in the C language specification, such as ones with directed rounding.

Intrinsic Operation
__float2int_[rn|rz|ru|rd] float to int
__float2uint_[rn|rz|ru|rd] float to unsigned int
__int2float_[rn|rz|ru|rd] int to float
__uint2float_[rn|rz|ru|rd] unsigned int to float
__float2ll_[rn|rz|ru|rd] float to 64-bit int
__ll2float_[rn|rz|ru|rd] 64-bit int to float
__ull2float_[rn|rz|ru|rd] 64-bit unsigned int to float
__double2float_[rn|rz|ru|rd] double to float
__double2int_[rn|rz|ru|rd] double to int
__double2uint_[rn|rz|ru|rd] double to unsigned int
__double2ll_[rn|rz|ru|rd] double to 64-bit int
__double2ull_[rn|rz|ru|rd] double to 64-bit unsigned int
__int2double_rn int to double
__uint2double_rn unsigned int to double
__ll2double_[rn|rz|ru|rd] 64-bit int to double
__ull2double_[rn|rz|ru|rd] 64-bit unsigned int to double

Table 8-10. Intrinsics for conversion

When this book was first written, half was not a type in the C language, so CUDA expressed these conversions in terms of unsigned short. Since CUDA 7.5, the __half type in cuda_fp16.h (Section 8.3.4) has given the operands a proper type:

__half __float2half( float );
float  __half2float( __half );

All four IEEE rounding modes are available for the float-to-half direction through the __float2half_[rn|rz|ru|rd] family; the plain __float2half() rounds to nearest even.

8.3.2 Single Precision (32-bit)

Single precision floating point support is the workhorse of GPU computation: GPUs have been optimized to natively deliver high performance on this data type12, not only for core standard IEEE operations such as addition and multiplication, but also for non-standard operations such as approximations to transcendentals such as sin() and log(). The 32-bit values are held in the same register file as integers, so coercion between single precision floating point values and 32-bit integers (with __float_as_int() and __int_as_float()) is free.

Addition, Multiplication, and Multiply-Add

The compiler automatically translates +, - and *operators on floating point values into addition, multiplication, and multiply-add instructions. The __fadd_rn() and __fmul_rn() intrinsics may be used to suppress fusion of addition and multiplication operations into multiply-add instructions.

Reciprocal and Division

On all supported hardware, CUDA’s single-precision division operator is correctly rounded and IEEE 754-compliant by default. Correct rounding costs performance, so CUDA also offers a faster, approximate divide, __fdividef(x,y), which forms the quotient from the SFU’s reciprocal (Table 8-12). The compiler chooses between them with --prec-div: the default --prec-div=true emits the compliant divide, while --prec-div=false—set implicitly by --use_fast_math—routes division through the fast path.

Beyond its reduced precision, __fdividef() also gives up range: for 2126<y<2128 it flushes the result to zero, and it returns NaN rather than INF when x is infinite, whereas the compliant divide is correct across the entire range. Square root is governed the same way, by the --prec-sqrt flag.

Transcendentals (SFU)

The Special Function Units (SFUs) in the SMs implement fast versions of six common transcendental functions:

The SFUs do not implement full 32-bit precision, but they are reasonably good approximations of these functions (22-24 bits precision – See Table 8-11), and they are fast. For CUDA ports that are significantly faster than an optimized CPU equivalent (say, 25x or more), the code most likely relies on the SFUs.

Function Accuracy (good bits) Ulp error
\(1/x\) 24.02 0.98
\(1/\sqrt{x}\) 23.40 1.52
\(2^x\) 22.51 1.41
\(\log_2 x\) 22.57 n/a
\(\sin/\cos\) 22.47 n/a

Table 8-11. SFU accuracy13

The SFUs are accessed with the intrinsics given in Table 8-12.

Intrinsic Operation
__cosf(x) \(\cos x\)
__exp10f(x) \(10^{x}\)
__expf(x) \(e^{x}\)
__fdividef(x,y) \(\frac{x}{y}\)
__logf(x) \(\ln x\)
__log2f(x) \(\log_{2}x\)
__log10f(x) \(\log_{10}x\)
__powf(x,y) \(x^{y}\)
__sinf(x) \(\sin x\)
__sincosf(x,s,c) *s=sin(x); *c=cos(x);
__tanf(x) \(\tan x\)

Table 8-12. SFU Intrinsics.

Specifying the --use_fast_math compiler option will cause the compiler to substitute conventional C runtime calls with the corresponding SFU intrinsics listed above.

A companion flag, --ftz=true, flushes denormals to zero: every denormal input or result of a single-precision operation becomes a correctly-signed zero. Because GPUs already handle denormals at full speed (Section 8.3.1), flushing improves performance less here than the same flag does on a CPU, but it too folds into --use_fast_math, and each of these switches can also be set on its own. Enabling fast math therefore makes three separate accuracy trades at once: reciprocal-based division, approximate square root, and flushed denormals.

Miscellaneous

__saturate(x) returns 0 if x<0, 1 if x>1, and x otherwise.

8.3.3 Double Precision (64-bit)

Double precision floating point support was added to CUDA with SM 1.2 (first implemented in the GeForce GTX 280), and much improved double precision support (both functionality and performance) became available with SM 2.0.

CUDA’s hardware support for double precision features full-speed denormals and, starting in SM 2.x, a native fused multiply-add instruction (FMAD), compliant with IEEE 754 c. 2008, that performs only one rounding step. Besides being an intrinsically useful operation, FMAD enables full accuracy on certain functions that are converged with Newton-Raphson iteration.

As with single precision operations, the compiler automatically translates standard C operators into multiplication, addition and multiply-add instructions. The __dadd_rn() and __dmul_rn() intrinsics may be used to suppress fusion of addition and multiplication operations into multiply-add instructions.

8.3.4 Half Precision (16-bit)

With 5 bits of exponent and 10 bits of significand, half values have enough precision for HDR (high dynamic range) images, and can be used to hold other types of values that don’t require float precision, such as angles.

When this book was first written, half precision values were intended for storage, not computation, and the hardware provided only instructions to convert to/from 32-bit14. That changed with SM 5.3, which introduced native half arithmetic (most efficient in the paired half2 form); CUDA 7.5 added the __half and __half2 types in cuda_fp16.h to replace the older unsigned short convention; and since Volta, Tensor Cores have included native support for half-precision matrices, including accumulation to float-valued outputs.

Today cuda_fp16.h exposes a full complement of operations on the __half and __half2 types: conversions to and from float (__float2half() and __half2float(), from Section 8.3.1), arithmetic (__hadd(), __hmul(), __hfma(), and their packed __hadd2()/__hmul2()/__hfma2() counterparts), comparisons, and half-precision math functions such as hsqrt(), hrsqrt(), hsin(), and hexp(). The paired half2 forms are the ones to reach for when performance matters, since a single instruction operates on both 16-bit lanes at once.

Converting float to half in software is a useful way to illustrate how round-to-nearest rounding works—sign propagation, exponent rebiasing, mantissa rounding, and the INF/NaN/denormal special cases—in a single unary operation. Appendix B works through such a converter in detail.

8.3.5 Reduced-Precision Formats for Deep Learning

Deep learning upended the old assumption that less than 32 bits of precision is a compromise. Neural network training and inference not only tolerate reduced precision, they scale with it: halving the width of a format doubles the arithmetic throughput of the Tensor Cores that consume it, halves the memory footprint, and doubles effective memory and interconnect bandwidth. Beginning with the Ampere generation, each hardware generation has added narrower formats, always following the same pattern: narrow inputs, wide accumulation – the products are computed from the narrow format, but summed into float (or wider) accumulators.

bfloat16 (__nv_bfloat16, in cuda_bf16.h; Ampere, CUDA 11.0) is simply float with the bottom 16 mantissa bits truncated: 1 sign bit, 8 bits of exponent, 7 of mantissa. It trades half’s precision for float’s dynamic range, and that trade is exactly right for training: gradients that would underflow half’s 5-bit exponent (requiring the loss-scaling machinery that makes mixed-precision training with half fiddly) sit comfortably in bfloat16’s range, and conversion from float is a truncation. It has largely displaced half as the default training format.

TF32 (Ampere) is not a storage format at all, but a Tensor Core operating mode: float inputs are rounded to a 19-bit internal representation (8 bits of exponent, 10 of mantissa – half’s precision with float’s range) before multiplication, with accumulation in full float. It exists so that unmodified single-precision matrix code can be accelerated by Tensor Cores.

FP8 (__nv_fp8_e4m3 and __nv_fp8_e5m2, in cuda_fp8.h; Hopper and Ada, CUDA 11.8) comes in two encodings, and the split is instructive. E4M3 (1/4/3) spends its bits on precision: it reaches only ±448, dispenses with infinities, and reserves a single bit pattern for NaN. E5M2 (1/5/2) spends them on range, reaching ±57344 with IEEE-style infinities and NaNs. The convention in training is E4M3 for weights and activations on the forward pass, E5M2 for the gradients of the backward pass, with software-managed per-tensor scale factors keeping values centered in the representable range.

FP6 (__nv_fp6_e2m3 and __nv_fp6_e3m2, in cuda_fp6.h; Blackwell, CUDA 12.8) sits between FP8 and FP4 and, like FP8, offers a precision-versus-range choice. E2M3 (1/2/3) keeps three mantissa bits but spends only two on the exponent, reaching ±7.5; E3M2 (1/3/2) trades a mantissa bit for an exponent bit, reaching ±28. Neither reserves any encoding for infinities or NaNs—every bit pattern is a finite number, which is how so few bits still cover a usable range. Like FP4, FP6 is deployed block-scaled, and Blackwell’s Tensor Cores accept it in both the NVFP4-style and OCP microscaling (MXFP6) layouts.

FP4 (__nv_fp4_e2m1, in cuda_fp4.h; Blackwell, CUDA 12.8) takes the progression to its logical extreme: 1 sign bit, 2 bits of exponent, 1 of mantissa, for exactly eight representable magnitudes (0, 0.5, 1, 1.5, 2, 3, 4, and 6). A format this coarse is useless on its own, so it is deployed block-scaled: in NVIDIA’s NVFP4 scheme, every 16 consecutive values share an FP8 (E4M3) scale factor, with a float scale per tensor above that; the competing OCP “microscaling” MXFP4 format uses 32-element blocks with power-of-two (E8M0) scales. Blackwell Tensor Cores support both; NVFP4’s smaller blocks and fractional scales buy measurably better accuracy. Its home today is inference on quantized weights.

Figure 8-3. Reduced-Precision Formats for Deep Learning

Format Bits (s/e/m) Largest finite CUDA type Introduced
half 1/5/10 65504 __half (see Section 8.3.4)
bfloat16 1/8/7 3.39×1038 __nv_bfloat16 Ampere, CUDA 11.0
TF32 1/8/10 (internal) Tensor Core mode Ampere
FP8 E4M3 1/4/3 448 __nv_fp8_e4m3 Hopper/Ada, CUDA 11.8
FP8 E5M2 1/5/2 57344 __nv_fp8_e5m2 Hopper/Ada, CUDA 11.8
FP6 E2M3 1/2/3 7.5 (block-scaled) __nv_fp6_e2m3 Blackwell, CUDA 12.8
FP6 E3M2 1/3/2 28 (block-scaled) __nv_fp6_e3m2 Blackwell, CUDA 12.8
FP4 E2M1 1/2/1 6 (block-scaled) __nv_fp4_e2m1 Blackwell, CUDA 12.8

Table 8-13. Reduced-precision floating point formats.

Two closing observations. First, these formats are not general-purpose arithmetic formats: they are purpose-built for processing by Tensor Cores, and a program reaches that processing not by emitting matrix-multiply instructions by hand but by handing the operands and their per-block scale factors to a library GEMM – cuBLASLt (cublasLtMatmul) or CUTLASS. Second, the conversion walkthrough in Appendix B applies to every row of the table – each format is the same sign/exponent/mantissa story with different bit budgets, and working through its float-to-half converter (Listing B-1) equips you to reason about any of them.

Format Largest finite Smallest normal Smallest subnormal Inf / NaN
half (E5M10) 65504 2-14 2-24 yes
bfloat16 (E8M7) 3.39×1038 2-126 2-133 yes
FP8 E4M3 448 2-6 2-9 NaN only
FP8 E5M2 57344 2-14 2-16 yes
FP6 E2M3 7.5 1 2-3 none
FP6 E3M2 28 2-2 2-4 none
FP4 E2M1 6 1 2-1 none

Table 8-14. Extreme values of the reduced-precision formats. TF32, an internal Tensor Core mode rather than a stored format, has half’s precision with float’s range.

8.3.6 Math Library

CUDA includes a built-in math library modeled on the C runtime library, with a few small differences: CUDA hardware does not include a rounding mode register (instead, the round mode is encoded on a per-instruction basis15), so functions such as rint() that reference the current rounding mode always round-to-nearest. Additionally, the hardware does not raise floating point exceptions; results of aberrant operations, such as taking the square root of a negative number, are encoded as NaNs.

Table 8-15 gives the math library functions and the maximum error in ulps for each function. Most functions that operate on float have an “f” appended to the function name – for example, the functions that compute the sine function are as follows:

double sin( double angle );
float sinf( float angle );

These are denoted in Table 8-15 as e.g. sin[f].

Ulp error
Function Operation Expression 32 64
x+y Addition x+y 01 0
x*y Multiplication x*y 01 0
x/y Division x/y 22 0
1/x Reciprocal 1/x 12 0
acos[f](x) Inverse cosine \(\cos^{- 1}x\) 3 2
acosh[f](x) Inverse hyperbolic cosine \(\ln\left( x + \sqrt{x^{2} + 1} \right)\) 4 2
asin[f](x) Inverse sine \(\sin^{- 1}x\) 4 2
asinh[f](x) Inverse hyperbolic sine \(sign(x)\ln\left( |x| + \sqrt{1 + x^{2}} \right)\) 3 2
atan[f](x) Inverse tangent \(\tan^{- 1}x\) 2 2
atan2[f](y,x) Inverse tangent of y/x \(\tan^{- 1}\left( \frac{y}{x} \right)\) 3 2
atanh[f](x) Inverse hyperbolic tangent \(\tanh^{- 1}x\) 3 2
cbrt[f](x) Cube root: \(\sqrt[3]{x}\) 1 1
ceil[f](x) “Ceiling”, nearest integer greater than or equal to x \(\left\lceil x \right\rceil\) 0
copysign[f](x,y) Sign of y, magnitude of x n/a
cos[f](x) Cosine \(\cos x\) 2 1
cosh[f](x) Hyperbolic cosine \(\frac{e^{x} + e^{- x}}{2}\) 2
cospi[f](x) Cosine, scaled by π \(\cos{\pi x}\) 2
erf[f](x) Error function \(\frac{2}{\pi}\int_{0}^{x}{e^{{- t}^{2}}dt}\) 3 2
erfc[f](x) Complementary error function \(1 - \frac{2}{\pi}\int_{0}^{x}{e^{{- t}^{2}}dt}\) 6 4
erfcinv[f](y) Inverse complementary error function Return x for which y=1-erff(x) 7 8
erfcx[f](x) Scaled error function \(e^{x^{2}}\)(erff(x)) 6 3
erfinv[f](y) Inverse error function Return x for which y=erff(x) 3 5
exp[f](x) Natural exponent \(e^{x}\) 2 1
exp2[f](x) Exponent (base 2) \(2^{x}\) 2 1
exp10[f](x) Exponent (base 10) \(10^{x}\) 2 1
expm1[f](x) Natural exponent, minus one \(e^{x} - 1\) 1 1
fabs[f](x) Absolute value \(|x|\) 0 0
fdim[f](x,y) Positive difference \(\left\{ \begin{array}{r} x - y,x > y \\ + 0,\ x \leq y \\ NAN,x\ or\ y\ NaN \end{array} \right.\) 0 0
floor[f](x) “Floor”, nearest integer less than or equal to x \(\left\lfloor x \right\rfloor\) 0 0
fma[f](x,y,z) Multiply-add \(xy + z\) 0 0
fmax[f](x,y) Maximum \(\left\{ \begin{array}{r} x,x > y\ or\ isNaN(y) \\ y,otherwise \end{array} \right.\) 0 0
fmin[f](x,y) Minimum \(\left\{ \begin{array}{r} x,x < y\ or\ isNaN(y) \\ y,otherwise \end{array} \right.\) 0 0
fmod[f](x,y) Floating point remainder 0 0
frexp[f](x,exp) Fractional component 0 0
hypot[f](x,y) Length of hypotenuse \(\sqrt{x^{2} + y^{2}}\) 3 2
ilogb[f](x) Get exponent 0 0
isfinite(x) Nonzero if x is not  ±INF n/a
isinf(x) Nonzero if x is  ±INF n/a
isnan(x) Nonzero if x is a NaN n/a
j0[f](x) Bessel function of the first kind (n=0) \(J_{0}(x)\) 93 73
j1[f](x) Bessel function of the first kind (n=1) \(J_{1}(x)\) 93 73
jn[f](n,x) Bessel function of the first kind \(J_{n}(x)\) *
ldexp[f](x,exp) Scale by power of 2 \(x2^{\exp}\) 0 0
lgamma[f](x) Logarithm of gamma function \(\ln\left( \Gamma(x) \right)\) 64 44
llrint[f](x) Round to long long 0 0
llround[f](x) Round to long long 0 0
lrint[f](x) Round to long 0 0
lround[f](x) Round to long 0 0
log[f](x) Natural logarithm \(\ln x\) 1 1
log10[f](x) Logarithm (base 10) \(\log_{10}x\) 3 1
log1p[f](x) Natural logarithm of x+1 \(\ln(x + 1)\) 2 1
log2[f](x) Logarithm (base 2) \(\log_{2}x\) 3 1
logb[f](x) Get exponent 0 0
modff(x,iptr) Split fractional and integer parts 0 0
nan[f](cptr) Returns NaN. NaN n/a
nearbyint[f](x) Round to integer. 0 0
nextafter[f](x,y) Returns the FP value closest to x in the direction of y. n/a
normcdf[f](x) Normal cumulative distribution 6 5
normcdinv[f](x) Inverse normal cumulative distribution 5 8
pow[f](x,y) Power function \(x^{y}\) 8 2
rcbrt[f](x) Inverse cube root \(\frac{1}{\sqrt[3]{x}}\) 2 1
remainder[f](x,y) Remainder 0 0
remquo[f](x,y,iptr) Remainder (also returns quotient) 0 0
rsqrt[f](x) Reciprocal \(\frac{1}{\sqrt{x}}\) 2 1
rint[f](x) Round to nearest int 0 0
round[f](x) Round to nearest int 0 0
scalbln[f](x,n) Scale x by 2n (n is long int) \(x2^{n}\) 0 0
scalbn[f](x,n) Scale x by 2n (n is int) \(x2^{n}\) 0 0
signbit(x) Nonzero if x is negative n/a 0
sin[f](x) Sine \(\sin x\) 2 1
sincos[f](x,s,c) Sine and cosine

*s=sin(x);

*c=cos(x);

2 1
sincospi[f](x,s,c) Sine and cosine

*s=sin(πx);

*c=cos(πx);

2 1
sinh[f](x) Hyperbolic sine \(\frac{e^{x} - e^{- x}}{2}\) 3 1
sinpi[f](x) Sine, scaled by π \(\sin{\pi x}\) 2 1
sqrt[f](x) Square root \(\sqrt{x}\) 35 0
tan[f](x) Tangent \(\tan x\) 4 2
tanh[f](x) Hyperbolic tangent \(\frac{\sinh x}{\cosh x}\) 2 1
tgamma[f](x) True gamma function \(\Gamma(x)\) 11 8
trunc[f](x) Truncate (round to integer toward zero) 0 0
y0[f](x) Bessel function of the second kind (n=0) \(Y_{0}(x)\) 93 73
y1[f](x) Bessel function of the second kind (n=1) \(Y_{1}(x)\) 93 73
yn[f](n,x) Bessel function of the second kind \(Y_{n}(x)\) **

* For the Bessel functions jnf(n,x) and jn(n,x), for n=128 the maximum absolute error is 2.2×10-6 and 5×10-12, respectively.

** For the Bessel function ynf(n,x), the error is \(\left\lceil 2 + 2.5n \right\rceil\) for \(|x| < n\); otherwise the maximum absolute error is 2.2×10-6 for n=128. For yn(n,x), the maximum absolute error is 5×10-12.

1 On SM 1.x class hardware, the precision of addition and multiplication operation that are merged into FMAD instructions will suffer, due to truncation of the intermediate mantissa.

2 On SM 2.x and later hardware, developers can reduce this error rate to 0 ulps by specifying --prec-div=true.

3 For float, the error is 9 ulps for |x|<8, otherwise the maximum absolute error is 2.2×10-6. For double, the error is 7 ulps for |x|<8, otherwise the maximum absolute error is 5×10-12.

4 The error for lgammaf() is greater than 6 inside the interval -10.001, -2.264. The error for lgamma() is greater than 4 inside the interval -11.001, -2.2637.

5 On SM 2.x and later hardware, developers can reduce this error rate to 0 ulps by specifying --prec-sqrt=true.

Table 8-15. Math Library

Conversion to Integer

According to the C runtime library definition, the nearbyint() and rint() functions round a floating point value to the nearest integer using the “current rounding direction,” which in CUDA is always round-to-nearest-even. In the C runtime, nearbyint() and rint() differ only in their handling of the INEXACT exception; but since CUDA does not raise floating point exceptions, the functions behave identically.

round() implements “elementary school” style rounding: for floating point values halfway between integers, the input is always rounded away from zero. NVIDIA recommends against using this function because it expands to eight (8) instructions as opposed to one for rint() and its variants.

trunc() truncates or “chops” the floating point value, rounding toward zero. It compiles to a single instruction.

Fractions and Exponents

float frexpf(float x, int *eptr);

frexpf() breaks the input into a floating point significand in the range [0.5, 1.0) and an integral exponent for 2, such that:

\[x = Significand \bullet 2^{Exponent}\]

float logbf( float x );

logbf() extracts the exponent from x and returns it as a floating-point value. It is equivalent to floorf(log2f(x)), except it is faster. If x is a denormal, logbf() returns the exponent that x would have if it were normalized.

float ldexpf( float x, int exp );
float scalbnf( float x, int n );
float scalblnf( float x, long n );

ldexpf(), scalbnf() and scalblnf() all compute \(x2^{n}\) by direct manipulation of floating point exponents.

Floating point remainder

modff() breaks the input into fractional and integer parts:

float modff( float x, float *intpart );

The return value is the fractional part of x, with the same sign.

remainderf(x,y) computes the floating point remainder of dividing x by y. The return value is x-n*y, where n is x/y, rounded to the nearest integer. If \(|x - ny| = 0.5\), n is chosen to be even.

float remquof(float x, float y, int *quo);

Computes the remainder and passes back the lower bits of the integral quotient x/y, with the same sign as x/y.

Bessel Functions

The Bessel functions of order n relate to the differential equation:

\[x^{2}\frac{d^{2}y}{dx^{2}} + x\frac{dy}{dx} + \left( x^{2} - n^{2} \right)y = 0\]

n can be a real number, but for purposes of the C runtime, it is a nonnegative integer.

The solution to this second-order ordinary differential equation combines Bessel functions of the first kind and of the second kind:

\[y(x) = c_{1}J_{n}(x) + c_{2}Y_{n}(x)\]

The math runtime functions jn[f]() and yn[f]() compute \(J_{n}(x)\ \)and \(Y_{n}(x)\), respectively. j0f(), j1f(), y0f(), and y1f() compute these functions for the special cases of n=0 and n=1.

Gamma Function

The gamma function Γ is an extension of the factorial function, with its argument shifted down by 1, to real numbers. It has a variety of definitions, one of which is as follows:

\[\Gamma(x) = \int_{0}^{\infty}{e^{- t}t^{x - 1}dt}\]

The function grows so quickly that the return value loses precision for relatively small input values, so the library provides the lgamma() function, which returns the natural logarithm of the gamma function, in addition to the tgamma() (“true gamma”) function.

8.3.6 Additional Reading

Goldberg’s survey (with the captivating title “What Every Computer Scientist Should Know About Floating Point Arithmetic”) is a good introduction to the topic.

http://download.oracle.com/docs/cd/E19957-01/806-3568/ncg_goldberg.html

Nathan Whitehead and Alex Fit-Florea of NVIDIA have co-authored a white paper entitled “Precision & Performance: Floating Point and IEEE 754 Compliance for NVIDIA GPUs.”

http://developer.download.nvidia.com/assets/cuda/files/NVIDIA-CUDA-Floating-Point.pdf

Increasing Effective Precision

Dekker and Kahan developed methods to almost-double the effective precision of floating-point hardware using pairs of numbers, in exchange for a slight reduction in exponent range (due to intermediate underflow and overflow at the far ends of the range). Some papers on this topic include:

Dekker, T.J. A floating-point technique for extending the available precision. Numer. Math. 18 (1971), pp. 224-242.

Linnainmaa, S. Software for doubled-precision floating point computations. ACM TOMS 7, 272-283 (1981).

Shewchuk, J.R. Adaptive precision floating-point arithmetic and fast robust geometric predicates. Discrete & Computational Geometry 18:305-363, 1997.

Some GPU-specific work on this topic has been done by Andrew Thall and Da Graça and Defour:

Da Graça Guillaume and David Defour. Implementation of float-float operators on graphics hardware, 7th Conference on Real Numbers and Computers, RNC7 (2006).

http://hal.archives-ouvertes.fr/docs/00/06/33/56/PDF/float-float.pdf

Thall, Andrew. Extended-precision floating-point numbers for GPU computation. 2007.

http://andrewthall.org/papers/df64_qf128.pdf


  1. With the exception that single-precision denormals are not supported on SM 1.x hardware.↩︎

  2. Sometimes called subnormals.↩︎

  3. In fact, GPUs had full 32-bit floating point support before they had full 32-bit integer support. As a result, some early GPU computing literature explained how to emulate integers with floating point hardware!↩︎

  4. Lindholm, Erik, John Nickolls, Stuart Oberman, and John Montrym. NVIDIA Tesla: A unified graphics and computing architecture. IEEE Micro, March-April 2008, p 47.↩︎

  5. Note: half floating point values are supported as a texture format, in which case the TEX intrinsics return float vectors and the conversion is automatically performed by the texture hardware.↩︎

  6. Encoding a round mode per instruction and keeping it in a control register are not irreconcilable. The Alpha processor had a 2-bit encoding to specify the round mode per instruction, one setting of which was to use the rounding mode specified in a control register! CUDA hardware just uses a 2-bit encoding for the four round modes specified in the IEEE specification.↩︎