Not every operation in a Java program has a corresponding machine instruction. Some operations can be lowered directly to hardware, while others require a software algorithm. For library methods, the API contract determines which results are acceptable, and that contract in turn determines which implementation substitutions the runtime may perform.
1.2.1. Machine Support and Software Algorithms
Programmers are used to thinking of arithmetic operations as processor operations. For basic floating-point arithmetic, that model largely holds: addition, subtraction, multiplication, and division map directly to machine instructions. Square root is the clearest further example.
Transcendental functions break the model. Sine, cosine, exponential, logarithm, and general power are normally computed by software algorithms assembled from ordinary arithmetic. Those algorithms perform argument reduction, evaluate polynomial or table approximations, reconstruct the result, and handle special values such as NaN, infinities, and signed zero.
|
Historical Exception: x87 x86 still includes historical x87 instructions for transcendental functions, among them The x87 instruction set relies on an outdated stack-based register model and processes one value at a time. Its transcendental instructions also have limited accuracy. Current implementations therefore use software algorithms built from ordinary floating-point instructions rather than the x87 transcendental instructions. |
On current instruction sets, the operations relevant here fall into three groups.
| Operation | Typical machine support | How the result is obtained |
|---|---|---|
|
|
Direct |
One instruction per operation, with the rounding required by Java |
|
Square root ( |
Direct |
One correctly rounded square-root instruction |
|
Fused multiply-add ( |
Architecture-dependent |
One instruction with a single final rounding where the processor provides FMA; otherwise a software fallback |
|
Rounding operations ( |
Architecture-dependent |
Typically lowered to one or a small number of instructions on current instruction sets |
|
Exponential and logarithmic functions ( |
No generally used direct instruction |
Argument reduction followed by polynomial or table approximation |
|
General power ( |
No generally used direct instruction |
Special-case handling followed by logarithm and exponential computation |
|
Sine, cosine, and tangent ( |
No generally used direct instruction |
Argument reduction followed by polynomial approximation |
|
Inverse trigonometric functions ( |
No generally used direct instruction |
Software algorithm throughout |
|
Hyperbolic functions ( |
No generally used direct instruction |
Software algorithms built largely on the exponential function |
|
Cube root and hypotenuse ( |
No generally used direct instruction |
Software algorithms; |
The processor is not short of specialized instructions; they simply apply to different operations. Methods such as Integer.bitCount(…), numberOfLeadingZeros(…), and reverseBytes(…) can each lower to a single instruction, while Math.sin(…) remains a library operation.
That difference has a cost. A floating-point multiplication takes only a few cycles, whereas a transcendental function usually requires many instructions. Math.sin(…) becomes more expensive still when a large argument requires the slow path of argument reduction. A machine instruction is therefore only one possible implementation mechanism. Where no suitable instruction exists, an implementation must provide an algorithm. Where an instruction does exist, the runtime may still need a fallback for platforms that do not provide it. The cost of software algorithms also gives the runtime an incentive to replace a default implementation with a faster one, but only as far as the contract of the method allows.
1.2.2. Contracts: Math and StrictMath
Machine support does not determine the API contract. For the numerical library methods discussed here, Math and StrictMath define two different contractual models. Java once distinguished between strict and non-strict evaluation of floating-point expressions. JEP 306[1] removed that distinction in Java 17. Floating-point expressions are now always evaluated strictly, and the strictfp modifier has become a no-op for which javac issues a warning.
This change did not make Math and StrictMath equivalent. The two mechanisms address different concerns:
-
strictfpgoverned the evaluation of floating-point expressions. -
StrictMathgoverns the results returned by selected library methods.
1.2.2.1. StrictMath: A Specified Result
For a defined set of functions, StrictMath requires the result produced by the specified fdlibm algorithm when executed according to Java’s arithmetic rules. The name fdlibm stands for Freely Distributable Math Library, a collection of algorithms for IEEE 754 floating-point arithmetic developed at Sun in the 1990s.
For the affected StrictMath methods, the relationship to fdlibm is not an OpenJDK implementation choice. The class documentation says so outright, and pins the binding down in remarkable detail, right down to the file naming convention of the C sources.
To help ensure portability of Java programs, the definitions of some of the numeric functions in this package require that they produce the same results as certain published algorithms. These algorithms are available from the well-known network library
netlibas the package „Freely Distributable Math Library,“fdlibm. These algorithms, which are written in the C programming language, are then to be understood to be transliterated into Java and executed with all floating-point and integer operations following the rules of Java arithmetic. The following transformations are used in the transliteration: […]The Java math library is defined with respect to
fdlibmversion 5.3 with a fix topowso that its error bounds conform to the quality of implementation criteria forpow. Wherefdlibmprovides more than one definition for a function (such asacos), use the „IEEE 754 core function“ version (residing in a file whose name begins with the lettere). The methods which requirefdlibmsemantics aresin,cos,tan,asin,acos,atan,exp,log,log10,cbrt,atan2,pow,sinh,cosh,tanh,asinh,acosh,atanh,hypot,expm1, andlog1p.
java.lang.StrictMathThe API specification is one of the three documents that make up the Java SE platform specification, alongside the language specification and the virtual machine specification. The fdlibm requirement therefore binds every conforming implementation.
What is required is a result, not a construction. The documentation describes the C algorithms as transliterated into Java and executed under Java’s arithmetic rules. This constrains what leaves the method without prescribing how a particular implementation must be organized internally. OpenJDK implements this contract through Java ports in the package-private java.lang.FdLibm class. A call such as StrictMath.sin(…) therefore follows this default source-level path:
public static double sin(double a) {
return FdLibm.Sin.compute(a);
}
|
History In older JDK releases, several The Java ports remain closely based on fdlibm 5.3. The original library is no longer maintained as an independent project, but descendants of its code survive in FreeBSD’s msun, musl libc, Julia’s openlibm, and the Go standard library. |
A virtual machine may compile or inline this code, but it may not substitute an algorithm that produces a different result.
StrictMath therefore guarantees reproducibility of the specified result. It does not necessarily guarantee a result that is closer to the exact mathematical value than the result returned by Math.
1.2.2.2. Math: Constrained Implementation Freedom
For many transcendental functions, Math specifies an error bound and, where applicable, monotonicity requirements. It does not require the fdlibm result. IEEE 754-2019 defines functions such as sine, cosine, exponential, logarithm, and power as recommended operations with correctly rounded semantics, but it does not require every programming environment to provide them. Java generally specifies weaker, method-specific guarantees for these functions.[2]
The Java source implementation commonly delegates to StrictMath:
@IntrinsicCandidate
public static double sin(double a) {
return StrictMath.sin(a);
}
This delegation defines the default implementation. It does not restrict the runtime to that implementation. A virtual machine may substitute another algorithm whenever the replacement continues to satisfy the Math contract.
Square root is the important exception. IEEE 754 requires square root to be correctly rounded. For a given input and rounding mode, there is exactly one admissible floating-point result. Java reflects that requirement in the contract of Math.sqrt(…): the result must be correctly rounded rather than merely remain within an ulp bound. Math.sqrt(…) and StrictMath.sqrt(…) therefore return the same result. The documented list of methods bound to fdlibm semantics shows the same fact from the other side: sqrt does not appear on it.
For functions such as sin(…), cos(…), and pow(…), no comparable requirement eliminates the implementation freedom.
1.2.3. Runtime Implementation
A Java method body describes the default source-level path, but not necessarily the code executed after compilation. As discussed earlier, HotSpot may recognize selected methods as intrinsics and replace their Java-level invocation during JIT compilation. The source code extract above shows that Math.sin(…) is marked with @IntrinsicCandidate; whether HotSpot actually provides an intrinsic for the method is determined by its internal table of known intrinsics. For a supported call, the JIT compiler may emit compiler-generated code or invoke a runtime stub instead of following the Java-level path.
HotSpot provides an intrinsic for Math.sin(…), but not for StrictMath.sin(…). For Math.sin(…), HotSpot may therefore replace the default delegation path with a runtime stub that implements the function using platform-specific instruction sequences. On x86-64, the relevant stubs are based on routines contributed from Intel’s scalar Compiler Math Library (LIBM)[3] and implement the function using SSE and AVX instruction sequences. The implementation is incorporated directly into HotSpot; the JVM does not dynamically link against the external Intel library at runtime. This substitution is permitted because Math.sin(…) does not require the fdlibm result. It requires only a result within the specified error bound and with the required monotonicity properties.
A direct call to StrictMath.sin(…), by contrast, remains bound to the specified result. The JIT compiler may compile and inline FdLibm.Sin.compute(…), but it may not substitute an algorithm that changes that result.
Square root illustrates how the contract distinction remains separate from the hardware/software distinction. Both Math.sqrt(…) and StrictMath.sqrt(…) carry @IntrinsicCandidate, and HotSpot defines an intrinsic for each. A hardware square-root instruction and a conforming software implementation must agree bit for bit, so a single sqrtsd instruction can replace either method body without weakening either contract.
An intrinsic is therefore not hardware taking over from software. It is one implementation replacing another under the constraints of the API contract. The complete path is therefore:
operation → API contract → permitted implementation → generated machine code
|
Choosing Between Math and StrictMath
For methods with specified fdlibm semantics, What separates the two classes is implementation freedom on one side and a specified numerical result on the other, not speed on one side and accuracy on the other. |