// Written in the D programming language. /** This is a submodule of $(MREF std, math). It contains hardware support for floating point numbers. Copyright: Copyright The D Language Foundation 2000 - 2011. License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). Authors: $(HTTP digitalmars.com, Walter Bright), Don Clugston, Conversion of CEPHES math library to D by Iain Buclaw and David Nadlinger Source: $(PHOBOSSRC std/math/hardware.d) */ /* NOTE: This file has been patched from the original DMD distribution to * work with the GDC compiler. */ module std.math.hardware; static import core.stdc.fenv; version (X86) version = X86_Any; version (X86_64) version = X86_Any; version (PPC) version = PPC_Any; version (PPC64) version = PPC_Any; version (MIPS32) version = MIPS_Any; version (MIPS64) version = MIPS_Any; version (AArch64) version = ARM_Any; version (ARM) version = ARM_Any; version (S390) version = IBMZ_Any; version (SPARC) version = SPARC_Any; version (SPARC64) version = SPARC_Any; version (SystemZ) version = IBMZ_Any; version (RISCV32) version = RISCV_Any; version (RISCV64) version = RISCV_Any; version (D_InlineAsm_X86) version = InlineAsm_X86_Any; version (D_InlineAsm_X86_64) version = InlineAsm_X86_Any; version (InlineAsm_X86_Any) version = InlineAsm_X87; version (InlineAsm_X87) { static assert(real.mant_dig == 64); version (CRuntime_Microsoft) version = InlineAsm_X87_MSVC; } version (X86_64) version = StaticallyHaveSSE; version (X86) version (OSX) version = StaticallyHaveSSE; version (StaticallyHaveSSE) { private enum bool haveSSE = true; } else version (X86) { static import core.cpuid; private alias haveSSE = core.cpuid.sse; } version (D_SoftFloat) { // Some soft float implementations may support IEEE floating flags. // The implementation here supports hardware flags only and is so currently // only available for supported targets. } else version (X86_Any) version = IeeeFlagsSupport; else version (PPC_Any) version = IeeeFlagsSupport; else version (RISCV_Any) version = IeeeFlagsSupport; else version (MIPS_Any) version = IeeeFlagsSupport; else version (ARM_Any) version = IeeeFlagsSupport; // Struct FloatingPointControl is only available if hardware FP units are available. version (D_HardFloat) { // FloatingPointControl.clearExceptions() depends on version IeeeFlagsSupport version (IeeeFlagsSupport) version = FloatingPointControlSupport; } version (GNU) { // The compiler can unexpectedly rearrange floating point operations and // access to the floating point status flags when optimizing. This means // ieeeFlags tests cannot be reliably checked in optimized code. // See https://github.com/ldc-developers/ldc/issues/888 } else { version = IeeeFlagsUnittest; version = FloatingPointControlUnittest; } version (IeeeFlagsSupport) { /** IEEE exception status flags ('sticky bits') These flags indicate that an exceptional floating-point condition has occurred. They indicate that a NaN or an infinity has been generated, that a result is inexact, or that a signalling NaN has been encountered. If floating-point exceptions are enabled (unmasked), a hardware exception will be generated instead of setting these flags. */ struct IeeeFlags { nothrow @nogc: private: // The x87 FPU status register is 16 bits. // The Pentium SSE2 status register is 32 bits. // The ARM and PowerPC FPSCR is a 32-bit register. // The SPARC FSR is a 32bit register (64 bits for SPARC 7 & 8, but high bits are uninteresting). // The RISC-V (32 & 64 bit) fcsr is 32-bit register. uint flags; version (CRuntime_Microsoft) { // Microsoft uses hardware-incompatible custom constants in fenv.h (core.stdc.fenv). // Applies to both x87 status word (16 bits) and SSE2 status word(32 bits). enum : int { INEXACT_MASK = 0x20, UNDERFLOW_MASK = 0x10, OVERFLOW_MASK = 0x08, DIVBYZERO_MASK = 0x04, INVALID_MASK = 0x01, EXCEPTIONS_MASK = 0b11_1111 } // Don't bother about subnormals, they are not supported on most CPUs. // SUBNORMAL_MASK = 0x02; } else { enum : int { INEXACT_MASK = core.stdc.fenv.FE_INEXACT, UNDERFLOW_MASK = core.stdc.fenv.FE_UNDERFLOW, OVERFLOW_MASK = core.stdc.fenv.FE_OVERFLOW, DIVBYZERO_MASK = core.stdc.fenv.FE_DIVBYZERO, INVALID_MASK = core.stdc.fenv.FE_INVALID, EXCEPTIONS_MASK = core.stdc.fenv.FE_ALL_EXCEPT, } } static uint getIeeeFlags() @trusted pure { version (GNU) { version (X86_Any) { ushort sw; asm pure nothrow @nogc { "fstsw %0" : "=a" (sw); } // OR the result with the SSE2 status register (MXCSR). if (haveSSE) { uint mxcsr; asm pure nothrow @nogc { "stmxcsr %0" : "=m" (mxcsr); } return (sw | mxcsr) & EXCEPTIONS_MASK; } else return sw & EXCEPTIONS_MASK; } else version (ARM) { version (ARM_SoftFloat) return 0; else { uint result = void; asm pure nothrow @nogc { "vmrs %0, FPSCR; and %0, %0, #0x1F;" : "=r" (result); } return result; } } else version (RISCV_Any) { version (D_SoftFloat) return 0; else { uint result = void; asm pure nothrow @nogc { "frflags %0" : "=r" (result); } return result; } } else assert(0, "Not yet supported"); } else version (InlineAsm_X86_Any) { ushort sw; asm pure nothrow @nogc { fstsw sw; } // OR the result with the SSE2 status register (MXCSR). if (haveSSE) { uint mxcsr; asm pure nothrow @nogc { stmxcsr mxcsr; } return (sw | mxcsr) & EXCEPTIONS_MASK; } else return sw & EXCEPTIONS_MASK; } else version (SPARC) { /* int retval; asm pure nothrow @nogc { st %fsr, retval; } return retval; */ assert(0, "Not yet supported"); } else version (ARM) { assert(false, "Not yet supported."); } else version (RISCV_Any) { mixin(` uint result = void; asm pure nothrow @nogc { "frflags %0" : "=r" (result); } return result; `); } else assert(0, "Not yet supported"); } static void resetIeeeFlags() @trusted { version (GNU) { version (X86_Any) { asm nothrow @nogc { "fnclex"; } // Also clear exception flags in MXCSR, SSE's control register. if (haveSSE) { uint mxcsr; asm nothrow @nogc { "stmxcsr %0" : "=m" (mxcsr); } mxcsr &= ~EXCEPTIONS_MASK; asm nothrow @nogc { "ldmxcsr %0" : : "m" (mxcsr); } } } else version (ARM) { version (ARM_SoftFloat) return; else { uint old = FloatingPointControl.getControlState(); old &= ~0b11111; // http://infocenter.arm.com/help/topic/com.arm.doc.ddi0408i/Chdfifdc.html asm nothrow @nogc { "vmsr FPSCR, %0" : : "r" (old); } } } else version (RISCV_Any) { version (D_SoftFloat) return; else { uint newValues = 0x0; asm nothrow @nogc { "fsflags %0" : : "r" (newValues); } } } else assert(0, "Not yet supported"); } else version (InlineAsm_X86_Any) { asm nothrow @nogc { fnclex; } // Also clear exception flags in MXCSR, SSE's control register. if (haveSSE) { uint mxcsr; asm nothrow @nogc { stmxcsr mxcsr; } mxcsr &= ~EXCEPTIONS_MASK; asm nothrow @nogc { ldmxcsr mxcsr; } } } else version (RISCV_Any) { mixin(` uint newValues = 0x0; asm pure nothrow @nogc { "fsflags %0" : : "r" (newValues); } `); } else { /* SPARC: int tmpval; asm pure nothrow @nogc { st %fsr, tmpval; } tmpval &=0xFFFF_FC00; asm pure nothrow @nogc { ld tmpval, %fsr; } */ assert(0, "Not yet supported"); } } public: /** * The result cannot be represented exactly, so rounding occurred. * Example: `x = sin(0.1);` */ @property bool inexact() @safe const { return (flags & INEXACT_MASK) != 0; } /** * A zero was generated by underflow * Example: `x = real.min*real.epsilon/2;` */ @property bool underflow() @safe const { return (flags & UNDERFLOW_MASK) != 0; } /** * An infinity was generated by overflow * Example: `x = real.max*2;` */ @property bool overflow() @safe const { return (flags & OVERFLOW_MASK) != 0; } /** * An infinity was generated by division by zero * Example: `x = 3/0.0;` */ @property bool divByZero() @safe const { return (flags & DIVBYZERO_MASK) != 0; } /** * A machine NaN was generated. * Example: `x = real.infinity * 0.0;` */ @property bool invalid() @safe const { return (flags & INVALID_MASK) != 0; } } /// version (IeeeFlagsUnittest) @safe unittest { import std.math.traits : isNaN; static void func() { int a = 10 * 10; } pragma(inline, false) static void blockopt(ref real x) {} real a = 3.5; // Set all the flags to zero resetIeeeFlags(); assert(!ieeeFlags.divByZero); blockopt(a); // avoid constant propagation by the optimizer // Perform a division by zero. a /= 0.0L; assert(a == real.infinity); assert(ieeeFlags.divByZero); blockopt(a); // avoid constant propagation by the optimizer // Create a NaN a *= 0.0L; assert(ieeeFlags.invalid); assert(isNaN(a)); // Check that calling func() has no effect on the // status flags. IeeeFlags f = ieeeFlags; func(); assert(ieeeFlags == f); } version (IeeeFlagsUnittest) @safe unittest { import std.meta : AliasSeq; static struct Test { void delegate() @trusted action; bool function() @trusted ieeeCheck; } static foreach (T; AliasSeq!(float, double, real)) {{ T x; /* Needs to be here to trick -O. It would optimize away the calculations if x were local to the function literals. */ auto tests = [ Test( () { x = 1; x += 0.1L; }, () => ieeeFlags.inexact ), Test( () { x = T.min_normal; x /= T.max; }, () => ieeeFlags.underflow ), Test( () { x = T.max; x += T.max; }, () => ieeeFlags.overflow ), Test( () { x = 1; x /= 0; }, () => ieeeFlags.divByZero ), Test( () { x = 0; x /= 0; }, () => ieeeFlags.invalid ) ]; foreach (test; tests) { resetIeeeFlags(); assert(!test.ieeeCheck()); test.action(); assert(test.ieeeCheck()); } }} } /// Set all of the floating-point status flags to false. void resetIeeeFlags() @trusted nothrow @nogc { IeeeFlags.resetIeeeFlags(); } /// @safe unittest { pragma(inline, false) static void blockopt(ref real x) {} resetIeeeFlags(); real a = 3.5; blockopt(a); // avoid constant propagation by the optimizer a /= 0.0L; blockopt(a); // avoid constant propagation by the optimizer assert(a == real.infinity); assert(ieeeFlags.divByZero); resetIeeeFlags(); assert(!ieeeFlags.divByZero); } /// Returns: snapshot of the current state of the floating-point status flags @property IeeeFlags ieeeFlags() @trusted pure nothrow @nogc { return IeeeFlags(IeeeFlags.getIeeeFlags()); } /// @safe nothrow unittest { import std.math.traits : isNaN; pragma(inline, false) static void blockopt(ref real x) {} resetIeeeFlags(); real a = 3.5; blockopt(a); // avoid constant propagation by the optimizer a /= 0.0L; assert(a == real.infinity); assert(ieeeFlags.divByZero); blockopt(a); // avoid constant propagation by the optimizer a *= 0.0L; assert(isNaN(a)); assert(ieeeFlags.invalid); } } // IeeeFlagsSupport version (FloatingPointControlSupport) { /** Control the Floating point hardware Change the IEEE754 floating-point rounding mode and the floating-point hardware exceptions. By default, the rounding mode is roundToNearest and all hardware exceptions are disabled. For most applications, debugging is easier if the $(I division by zero), $(I overflow), and $(I invalid operation) exceptions are enabled. These three are combined into a $(I severeExceptions) value for convenience. Note in particular that if $(I invalidException) is enabled, a hardware trap will be generated whenever an uninitialized floating-point variable is used. All changes are temporary. The previous state is restored at the end of the scope. Example: ---- { FloatingPointControl fpctrl; // Enable hardware exceptions for division by zero, overflow to infinity, // invalid operations, and uninitialized floating-point variables. fpctrl.enableExceptions(FloatingPointControl.severeExceptions); // This will generate a hardware exception, if x is a // default-initialized floating point variable: real x; // Add `= 0` or even `= real.nan` to not throw the exception. real y = x * 3.0; // The exception is only thrown for default-uninitialized NaN-s. // NaN-s with other payload are valid: real z = y * real.nan; // ok // The set hardware exceptions and rounding modes will be disabled when // leaving this scope. } ---- */ struct FloatingPointControl { nothrow @nogc: alias RoundingMode = uint; /// version (StdDdoc) { enum : RoundingMode { /** IEEE rounding modes. * The default mode is roundToNearest. * * roundingMask = A mask of all rounding modes. */ roundToNearest, roundDown, /// ditto roundUp, /// ditto roundToZero, /// ditto roundingMask, /// ditto } } else version (CRuntime_Microsoft) { // Microsoft uses hardware-incompatible custom constants in fenv.h (core.stdc.fenv). enum : RoundingMode { roundToNearest = 0x0000, roundDown = 0x0400, roundUp = 0x0800, roundToZero = 0x0C00, roundingMask = roundToNearest | roundDown | roundUp | roundToZero, } } else { enum : RoundingMode { roundToNearest = core.stdc.fenv.FE_TONEAREST, roundDown = core.stdc.fenv.FE_DOWNWARD, roundUp = core.stdc.fenv.FE_UPWARD, roundToZero = core.stdc.fenv.FE_TOWARDZERO, roundingMask = roundToNearest | roundDown | roundUp | roundToZero, } } /*** * Change the floating-point hardware rounding mode * * Changing the rounding mode in the middle of a function can interfere * with optimizations of floating point expressions, as the optimizer assumes * that the rounding mode does not change. * It is best to change the rounding mode only at the * beginning of the function, and keep it until the function returns. * It is also best to add the line: * --- * pragma(inline, false); * --- * as the first line of the function so it will not get inlined. * Params: * newMode = the new rounding mode */ @property void rounding(RoundingMode newMode) @trusted { initialize(); setControlState((getControlState() & (-1 - roundingMask)) | (newMode & roundingMask)); } /// Returns: the currently active rounding mode @property static RoundingMode rounding() @trusted pure { return cast(RoundingMode)(getControlState() & roundingMask); } alias ExceptionMask = uint; /// version (StdDdoc) { enum : ExceptionMask { /** IEEE hardware exceptions. * By default, all exceptions are masked (disabled). * * severeExceptions = The overflow, division by zero, and invalid * exceptions. */ subnormalException, inexactException, /// ditto underflowException, /// ditto overflowException, /// ditto divByZeroException, /// ditto invalidException, /// ditto severeExceptions, /// ditto allExceptions, /// ditto } } else version (ARM_Any) { enum : ExceptionMask { subnormalException = 0x8000, inexactException = 0x1000, underflowException = 0x0800, overflowException = 0x0400, divByZeroException = 0x0200, invalidException = 0x0100, severeExceptions = overflowException | divByZeroException | invalidException, allExceptions = severeExceptions | underflowException | inexactException | subnormalException, } } else version (PPC_Any) { enum : ExceptionMask { inexactException = 0x0008, divByZeroException = 0x0010, underflowException = 0x0020, overflowException = 0x0040, invalidException = 0x0080, severeExceptions = overflowException | divByZeroException | invalidException, allExceptions = severeExceptions | underflowException | inexactException, } } else version (RISCV_Any) { enum : ExceptionMask { inexactException = 0x01, divByZeroException = 0x02, underflowException = 0x04, overflowException = 0x08, invalidException = 0x10, severeExceptions = overflowException | divByZeroException | invalidException, allExceptions = severeExceptions | underflowException | inexactException, } } else version (HPPA) { enum : ExceptionMask { inexactException = 0x01, underflowException = 0x02, overflowException = 0x04, divByZeroException = 0x08, invalidException = 0x10, severeExceptions = overflowException | divByZeroException | invalidException, allExceptions = severeExceptions | underflowException | inexactException, } } else version (MIPS_Any) { enum : ExceptionMask { inexactException = 0x0080, divByZeroException = 0x0400, overflowException = 0x0200, underflowException = 0x0100, invalidException = 0x0800, severeExceptions = overflowException | divByZeroException | invalidException, allExceptions = severeExceptions | underflowException | inexactException, } } else version (SPARC_Any) { enum : ExceptionMask { inexactException = 0x0800000, divByZeroException = 0x1000000, overflowException = 0x4000000, underflowException = 0x2000000, invalidException = 0x8000000, severeExceptions = overflowException | divByZeroException | invalidException, allExceptions = severeExceptions | underflowException | inexactException, } } else version (IBMZ_Any) { enum : ExceptionMask { inexactException = 0x08000000, divByZeroException = 0x40000000, overflowException = 0x20000000, underflowException = 0x10000000, invalidException = 0x80000000, severeExceptions = overflowException | divByZeroException | invalidException, allExceptions = severeExceptions | underflowException | inexactException, } } else version (X86_Any) { enum : ExceptionMask { inexactException = 0x20, underflowException = 0x10, overflowException = 0x08, divByZeroException = 0x04, subnormalException = 0x02, invalidException = 0x01, severeExceptions = overflowException | divByZeroException | invalidException, allExceptions = severeExceptions | underflowException | inexactException | subnormalException, } } else static assert(false, "Not implemented for this architecture"); version (ARM_Any) { static bool hasExceptionTraps_impl() @safe { auto oldState = getControlState(); // If exceptions are not supported, we set the bit but read it back as zero // https://sourceware.org/ml/libc-ports/2012-06/msg00091.html setControlState(oldState | divByZeroException); immutable result = (getControlState() & allExceptions) != 0; setControlState(oldState); return result; } } /// Returns: true if the current FPU supports exception trapping @property static bool hasExceptionTraps() @safe pure { version (X86_Any) return true; else version (PPC_Any) return true; else version (MIPS_Any) return true; else version (ARM_Any) { // The hasExceptionTraps_impl function is basically pure, // as it restores all global state auto fptr = ( () @trusted => cast(bool function() @safe pure nothrow @nogc)&hasExceptionTraps_impl)(); return fptr(); } else assert(0, "Not yet supported"); } /// Enable (unmask) specific hardware exceptions. Multiple exceptions may be ORed together. void enableExceptions(ExceptionMask exceptions) @trusted { assert(hasExceptionTraps); initialize(); version (X86_Any) setControlState(getControlState() & ~(exceptions & allExceptions)); else setControlState(getControlState() | (exceptions & allExceptions)); } /// Disable (mask) specific hardware exceptions. Multiple exceptions may be ORed together. void disableExceptions(ExceptionMask exceptions) @trusted { assert(hasExceptionTraps); initialize(); version (X86_Any) setControlState(getControlState() | (exceptions & allExceptions)); else setControlState(getControlState() & ~(exceptions & allExceptions)); } /// Returns: the exceptions which are currently enabled (unmasked) @property static ExceptionMask enabledExceptions() @trusted pure { assert(hasExceptionTraps); version (X86_Any) return (getControlState() & allExceptions) ^ allExceptions; else return (getControlState() & allExceptions); } /// Clear all pending exceptions, then restore the original exception state and rounding mode. ~this() @trusted { clearExceptions(); if (initialized) setControlState(savedState); } private: ControlState savedState; bool initialized = false; version (ARM_Any) { alias ControlState = uint; } else version (HPPA) { alias ControlState = uint; } else version (PPC_Any) { alias ControlState = uint; } else version (RISCV_Any) { alias ControlState = uint; } else version (MIPS_Any) { alias ControlState = uint; } else version (SPARC_Any) { alias ControlState = ulong; } else version (IBMZ_Any) { alias ControlState = uint; } else version (X86_Any) { alias ControlState = ushort; } else static assert(false, "Not implemented for this architecture"); void initialize() @safe { // BUG: This works around the absence of this() constructors. if (initialized) return; clearExceptions(); savedState = getControlState(); initialized = true; } // Clear all pending exceptions static void clearExceptions() @safe { version (IeeeFlagsSupport) resetIeeeFlags(); else static assert(false, "Not implemented for this architecture"); } // Read from the control register package(std.math) static ControlState getControlState() @trusted pure { version (GNU) { version (X86_Any) { ControlState cont; asm pure nothrow @nogc { "fstcw %0" : "=m" (cont); } return cont; } else version (AArch64) { ControlState cont; asm pure nothrow @nogc { "mrs %0, FPCR;" : "=r" (cont); } return cont; } else version (ARM) { ControlState cont; version (ARM_SoftFloat) cont = 0; else { asm pure nothrow @nogc { "vmrs %0, FPSCR" : "=r" (cont); } } return cont; } else version (RISCV_Any) { version (D_SoftFloat) return 0; else { ControlState cont; asm pure nothrow @nogc { "frcsr %0" : "=r" (cont); } return cont; } } else assert(0, "Not yet supported"); } else version (D_InlineAsm_X86) { short cont; asm pure nothrow @nogc { xor EAX, EAX; fstcw cont; } return cont; } else version (D_InlineAsm_X86_64) { short cont; asm pure nothrow @nogc { xor RAX, RAX; fstcw cont; } return cont; } else version (RISCV_Any) { mixin(` ControlState cont; asm pure nothrow @nogc { "frcsr %0" : "=r" (cont); } return cont; `); } else assert(0, "Not yet supported"); } // Set the control register package(std.math) static void setControlState(ControlState newState) @trusted { version (GNU) { version (X86_Any) { asm nothrow @nogc { "fclex; fldcw %0" : : "m" (newState); } // Also update MXCSR, SSE's control register. if (haveSSE) { uint mxcsr; asm nothrow @nogc { "stmxcsr %0" : "=m" (mxcsr); } /* In the FPU control register, rounding mode is in bits 10 and 11. In MXCSR it's in bits 13 and 14. */ mxcsr &= ~(roundingMask << 3); // delete old rounding mode mxcsr |= (newState & roundingMask) << 3; // write new rounding mode /* In the FPU control register, masks are bits 0 through 5. In MXCSR they're 7 through 12. */ mxcsr &= ~(allExceptions << 7); // delete old masks mxcsr |= (newState & allExceptions) << 7; // write new exception masks asm nothrow @nogc { "ldmxcsr %0" : : "m" (mxcsr); } } } else version (AArch64) { asm nothrow @nogc { "msr FPCR, %0;" : : "r" (newState); } } else version (ARM) { version (ARM_SoftFloat) return; else { asm nothrow @nogc { "vmsr FPSCR, %0" : : "r" (newState); } } } else version (RISCV_Any) { version (D_SoftFloat) return; else { asm nothrow @nogc { "fscsr %0" : : "r" (newState); } } } else assert(0, "Not yet supported"); } else version (InlineAsm_X86_Any) { asm nothrow @nogc { fclex; fldcw newState; } // Also update MXCSR, SSE's control register. if (haveSSE) { uint mxcsr; asm nothrow @nogc { stmxcsr mxcsr; } /* In the FPU control register, rounding mode is in bits 10 and 11. In MXCSR it's in bits 13 and 14. */ mxcsr &= ~(roundingMask << 3); // delete old rounding mode mxcsr |= (newState & roundingMask) << 3; // write new rounding mode /* In the FPU control register, masks are bits 0 through 5. In MXCSR they're 7 through 12. */ mxcsr &= ~(allExceptions << 7); // delete old masks mxcsr |= (newState & allExceptions) << 7; // write new exception masks asm nothrow @nogc { ldmxcsr mxcsr; } } } else version (RISCV_Any) { mixin(` asm pure nothrow @nogc { "fscsr %0" : : "r" (newState); } `); } else assert(0, "Not yet supported"); } } /// version (FloatingPointControlUnittest) @safe unittest { import std.math.rounding : lrint; FloatingPointControl fpctrl; fpctrl.rounding = FloatingPointControl.roundDown; assert(lrint(1.5) == 1.0); fpctrl.rounding = FloatingPointControl.roundUp; assert(lrint(1.4) == 2.0); fpctrl.rounding = FloatingPointControl.roundToNearest; assert(lrint(1.5) == 2.0); } @safe unittest { void ensureDefaults() { assert(FloatingPointControl.rounding == FloatingPointControl.roundToNearest); if (FloatingPointControl.hasExceptionTraps) assert(FloatingPointControl.enabledExceptions == 0); } { FloatingPointControl ctrl; } ensureDefaults(); { FloatingPointControl ctrl; ctrl.rounding = FloatingPointControl.roundDown; assert(FloatingPointControl.rounding == FloatingPointControl.roundDown); } ensureDefaults(); if (FloatingPointControl.hasExceptionTraps) { FloatingPointControl ctrl; ctrl.enableExceptions(FloatingPointControl.divByZeroException | FloatingPointControl.overflowException); assert(ctrl.enabledExceptions == (FloatingPointControl.divByZeroException | FloatingPointControl.overflowException)); ctrl.rounding = FloatingPointControl.roundUp; assert(FloatingPointControl.rounding == FloatingPointControl.roundUp); } ensureDefaults(); } version (FloatingPointControlUnittest) @safe unittest // rounding { import std.meta : AliasSeq; static T addRound(T)(uint rm) { pragma(inline, false) static void blockopt(ref T x) {} pragma(inline, false); FloatingPointControl fpctrl; fpctrl.rounding = rm; T x = 1; blockopt(x); // avoid constant propagation by the optimizer x += 0.1L; return x; } static T subRound(T)(uint rm) { pragma(inline, false) static void blockopt(ref T x) {} pragma(inline, false); FloatingPointControl fpctrl; fpctrl.rounding = rm; T x = -1; blockopt(x); // avoid constant propagation by the optimizer x -= 0.1L; return x; } static foreach (T; AliasSeq!(float, double, real)) {{ /* Be careful with changing the rounding mode, it interferes * with common subexpressions. Changing rounding modes should * be done with separate functions that are not inlined. */ { T u = addRound!(T)(FloatingPointControl.roundUp); T d = addRound!(T)(FloatingPointControl.roundDown); T z = addRound!(T)(FloatingPointControl.roundToZero); assert(u > d); assert(z == d); } { T u = subRound!(T)(FloatingPointControl.roundUp); T d = subRound!(T)(FloatingPointControl.roundDown); T z = subRound!(T)(FloatingPointControl.roundToZero); assert(u > d); assert(z == u); } }} } }