. "$BST_LIB" # BST-TIMEOUT: 1200 # THREE python3 starts, and they are the whole cost. # # Everything else here reads the filesystem or dpkg with # # shell builtins, or greps the output of one of the three. # # # # They are a bare `import', a second one whose STDERR is # # the assertion (`import-quiet'), and the probe -- which # # takes ONE start for fifty-seven measurements. # # # # THE ARITHMETIC IS NOT THE COST. Measured on the build # # host against this build's own wasm32 payload under # # CPython 3.12.3: 0.47 s wall for the whole probe, of which # # the interpreter start and `import mpmath' are 0.16 s. The # # fifty digits, the two integrals and the three root finds # # together are under a third of a second at native speed, # # so this suite is three interpreter starts and a rounding # # error, exactly like the other 99-python3-* files. # # # # 1200 is 400 s per start, the same budget # # 99-python3-asyncio-throttle and 99-python3-gast set for # # their three, and roughly six times the ~60 s a python3 # # start has measured at on this arm. The margin is for the # # start, not for the mathematics. # # # # NOT MEASURED ON TARGET YET: this port's first on-target # # run is the batch verify, and the measured elapsed # # replaces this note in the test plan rather than in this # # comment, which would cost an image build to keep the # # shipped suite byte-identical. # # python3-mpmath 1.3.0 (meta-openembedded/meta-python # recipes-devtools/python/python3-mpmath_1.3.0.bb, `inherit pypi setuptools3', # PyPI sdist) on the wasm32-hwjs glibc image. # # ============================================================================ # WHAT THE PACKAGE DOES. # ============================================================================ # # Arbitrary-precision real and complex floating-point arithmetic, implemented # entirely in Python. A global context `mpmath.mp' carries a working precision # -- `mp.prec' in bits, `mp.dps' in decimal digits, each derived from the other # -- and `mpf'/`mpc' values computed under it. On top of that sit the special # functions, the quadrature (`quad') and the root finder (`findroot') this suite # exercises. # # ============================================================================ # WHY THIS PORT IS CHEAP TO ASSERT, AND WHY THAT MATTERS MORE THAN USUAL. # ============================================================================ # # pi to 50 digits is pi to 50 digits on every arm. mpmath computes it from an # integer algorithm over Python ints, so a wasm32 answer that differs from the # published one is not a matter of rounding taste -- it is an integer that came # out wrong, which would be a finding about THIS INTERPRETER rather than about # mpmath. The constants are therefore the strongest cases in this file: their # right answer was published long before this port existed and nothing in the # build gets to choose it. # # The digits pinned below are the published values # # pi 3.14159265358979323846264338327950288419716939937510582097494... # e 2.71828182845904523536028747135266249775724709369995957496696... # sqrt2 1.41421356237309504880168872420969807856967187537694806726404... # # rounded to the working precision. `e_50' is SHORTER than the other two and # that is not a typo: mpmath strips trailing zeros, and e's 50-digit rounding # ends `...7247093|6999 5' -> `...72470937', with nothing after it to print. # # ============================================================================ # PURE PYTHON, SO NO PATCH AND NO BBAPPEND. # ============================================================================ # # meta-python's recipe builds on this arm as it stands, and the wasm32 build is # a byte-compile and an install. That purity is what makes it portable here at # all: this image's python3 is ONE STATIC BINARY with an empty lib-dynload and # no dynamic loader (BRINTOS-1476), so a module carrying a compiled extension # could not be imported whatever it built to. `pkg-no-so', `pkg-only-python' and # `impure-modules' assert that from three sides -- the packaged file list, the # same list by extension, and the import closure from inside the interpreter -- # so a version bump that grew a compiled speedup fails at install or at import # rather than in some later caller. # # `backend' is the fourth side of it, and it is ASSERTED AS AN IMPLICATION # rather than pinned to a constant. mpmath uses gmpy2 where it can import one # and MPMATH_NOGMPY is unset, and Python ints otherwise; the probe reports both # of those inputs, and the two cases below assert that the backend agrees with # them. # # IT WAS PINNED TO `python' FIRST, AND THAT WAS WRONG -- worth keeping, because # the reasoning was sound and its premise had expired. gmpy2 is a C extension, # and this image's interpreter could load none BEFORE BRINTOS-2046; it now # exports its C API and the HWJS-5 dlopen service binds an extension against it. # python3-gmpy2 is on this image's EMCRAFT_TOOLS, ported with its own bbappend, # so mpmath finds it and correctly answers `gmpy'. The batch verify of # 2026-09-10 measured that, and `backend', `mpz-type' and `sum-residue-50' went # red for a port that was doing exactly the right thing. # # The implication is the stronger statement in any case: it catches a backend # that stopped agreeing with the image's package set in EITHER direction, and it # stays correct if gmpy2 is later added to or dropped from the image, where # either constant would go red for a port that had not changed. # # AND `sum-residue-50' NO LONGER MOVES WITH THE BACKEND AT ALL. Its mantissa is # an `mpz' under gmpy2 and an `int' without it -- the same number printing two # ways -- so the probe emits the triple through int() on every element and the # case asserts the arithmetic rather than the integer type under it. Every # OTHER value in this file -- the fifty digits, both integrals, all three root # finds and every error path -- was measured byte-identical across the two # backends, which says the digits are a property of the algorithm. A control run # therefore needs no `MPMATH_NOGMPY=1' forcing any more: it agrees with the # target on either backend. # # ============================================================================ # NOTHING ABOUT IT WAITS ON ANOTHER PORT. # ============================================================================ # # The recipe's one DEPENDS -- python3-setuptools-scm-native -- is native and # resolves on the host. Its two target RDEPENDS, `python3-image' and # `python3-math', are stdlib splits of the interpreter recipe itself and were # already on this image. `pkg-depends-*' pins what the packaging derived. # # ============================================================================ # str() ROUNDS TO dps, AND THAT HIDES TWO OF THE ANSWERS THIS PORT WAS ASKED # ABOUT. BOTH ARE MEASURED FROM BOTH SIDES. # ============================================================================ # # `str(mpf)' prints dps significant digits, so a value that is NOT the one you # named can still PRINT as the one you named. Two of the ticket's questions land # exactly on that seam, and asking either of them only through str() would have # answered it wrongly: # # `mpf('0.1') + mpf('0.2')' PRINTS `0.3' at dps 50 and at dps 15 alike, and # is not equal to `mpf('0.3')' at either. At dps # 50 the residue is one ulp of the working # precision, exactly 2**-170; `sum-residue-50' # pins the raw mpf triple. # # `(mpf(2) ** mpf('0.5'))**2' PRINTS `2.0' and is NOT equal to 2. # # So each value question is asked twice -- once as the string a caller sees, # once as the `==' a caller would branch on -- and the two are separate cases so # that a divergence names which of them moved. # # ============================================================================ # ONE OF THE TICKET'S CLAIMS IS TRUE ONLY AT HIGH PRECISION, AND THE SUITE # SCORES BOTH SIDES OF IT RATHER THAN THE HALF THAT WAS ASKED FOR. # ============================================================================ # # The port was asked to assert that `mpf('0.1') + mpf('0.2')' must NOT equal the # binary-float answer. At dps 50 that holds -- `sum-eq-binary-50' is `no'. # # AT dps 15 IT IS FALSE, and `sum-eq-binary-15' is `yes'. dps 15 IS 53 bits, so # both operands are exactly the doubles a C program would have, and the sum is # exactly the double a C program would get. The claim is a statement about the # PRECISION rather than about mpmath, and it is false at mpmath's own default. # Writing only the dps-50 half would have left the file asserting a property of # the library that the library does not have. # # ============================================================================ # TWO OF THE THREE ERROR PATHS RAISE. THE THIRD DOES NOT, AND THAT IS THIS # SUITE'S ONE REAL FINDING ABOUT THE PACKAGE. # ============================================================================ # # The port was asked to assert that a division by zero, an `mpf' built from a # malformed string, and a precision set to a negative value each raise the # documented exception. The first two do: ZeroDivisionError -- with NO MESSAGE, # which `err-div-zero-mpf' records as `' because a caller logging # `str(exc)' logs nothing -- and ValueError. # # A NEGATIVE PRECISION RAISES NOTHING. `mp.dps = -1' is accepted and silently # clamped to the minimum: `libmp.libmpf.dps_to_prec' is # `max(1, int(round((int(n)+1)*3.3219280948873626)))' and `prec_to_dps' is # `max(1, int(round(int(n)/3.3219280948873626)-1))', so the context lands at # prec 1, dps 1 and arithmetic continues at ONE BIT. `clamped-pi' is what that # costs: pi evaluates to `4.0', correctly rounded at one bit of mantissa, with # no diagnostic anywhere. A caller that computes a precision and gets the sign # wrong is not told, and every digit after that is wrong in silence. # # `err-neg-dps' and `err-neg-prec' therefore assert the ABSENCE of the exception # and the value that came back instead, and `clamp-dps-*' pins the clamp at -1, # 0 and 1. Asserting the exception the ticket expected would have red a package # that is behaving exactly as its source says, and would have hidden the quiet # clamp behind a failure whose cause read as "mpmath is broken here". # # ============================================================================ # UPSTREAM'S OWN TESTS ARE INSTALLED, AND THERE IS NO -ptest PACKAGE. # ============================================================================ # # `setup.cfg' says `packages = find:' and `mpmath/tests/' carries an # `__init__.py', so `mpmath.tests' is an ordinary subpackage and its 36 files # ship into site-packages with the rest. That is upstream's packaging decision, # not this recipe's, and `tests-installed' asserts it as such -- a version bump # that stopped shipping them would otherwise be invisible here. # # The recipe does not `inherit ptest', so no `${PN}-ptest' is built and there is # nothing to run them with; `pkg-no-ptest' asserts that separately. The two are # different statements and a reader who conflates them concludes this image runs # upstream's test suite, which it does not. P=python3-mpmath bst_ok $P/suite-canary require python3 $P/python3-present SITE=/usr/lib/python3.12/site-packages PKGDIR=$SITE/mpmath DI=$SITE/mpmath-1.3.0.dist-info PKGLIST=/var/lib/dpkg/info/python3-mpmath.list PROBE=$BST_DATA/python3-mpmath-probe.py tmp=${TMPDIR:-/tmp} W=$tmp/bst-mpmath.$$ mkdir -p "$W" || { bst_notok $P/workdir "cannot create $W"; exit 0; } # ------------------------------------------------------------ what shipped --- check $P/site-packages-dir test -d "$SITE" check $P/package-dir test -d "$PKGDIR" check $P/no-module-py test ! -f "$SITE/mpmath.py" check $P/init-file test -s "$PKGDIR/__init__.py" check $P/ctx-mp-py test -s "$PKGDIR/ctx_mp.py" check $P/ctx-mp-python-py test -s "$PKGDIR/ctx_mp_python.py" check $P/libmp-dir test -d "$PKGDIR/libmp" check $P/libmpf-py test -s "$PKGDIR/libmp/libmpf.py" check $P/calculus-dir test -d "$PKGDIR/calculus" check $P/functions-dir test -d "$PKGDIR/functions" check $P/matrices-dir test -d "$PKGDIR/matrices" check $P/pycache test -s "$PKGDIR/__pycache__/__init__.cpython-312.pyc" check $P/no-cli test ! -e /usr/bin/mpmath # NO PEP 561 MARKER. mpmath ships no `py.typed', so a consumer's mypy reads no # annotations from it -- asserted because its absence is what lets # `pkg-only-python' below allow nothing but .py, .pyc and dist-info. check $P/no-py-typed test ! -e "$PKGDIR/py.typed" # UPSTREAM'S TESTS ARE INSTALLED -- see the header. This is the packaging fact, # and `pkg-no-ptest' below is the separate statement that nothing runs them. check $P/tests-installed test -s "$PKGDIR/tests/test_basic_ops.py" # -f, not -s: upstream ships this marker EMPTY (0 bytes), so non-emptiness # is not a fact about it -- existence is what makes `tests' a subpackage. check $P/tests-is-package test -f "$PKGDIR/tests/__init__.py" cat "$PKGLIST" > "$W/pkgf" 2>/dev/null || : > "$W/pkgf" check $P/pkg-list-present test -s "$W/pkgf" check_eq $P/pkg-no-so "" \ "$(grep -E '\.so($|\.)' "$W/pkgf" 2>/dev/null)" # EVERY SHIPPED FILE IS PYTHON, its bytecode or its dist-info. A compiled # extension could not be imported on this arm at all (BRINTOS-1476), so this is # the packaging-side statement of what `impure-modules' says from inside the # interpreter. _bad="" while IFS= read -r _f; do [ -f "$_f" ] || continue case "$_f" in *.py|*.pyc) continue ;; */__pycache__/*) continue ;; */*.dist-info/*) continue ;; esac _bad="$_bad $_f" done < "$W/pkgf" check_eq $P/pkg-only-python "" "$_bad" check $P/dist-info-dir test -d "$DI" check $P/dist-info-metadata test -f "$DI/METADATA" check_out $P/dist-info-name "Name: mpmath" cat "$DI/METADATA" check_out $P/dist-info-version "Version: 1.3.0" cat "$DI/METADATA" check_out $P/dist-info-summary \ "Summary: Python library for arbitrary-precision floating-point arithmetic" \ cat "$DI/METADATA" check_out $P/dist-info-top-level "mpmath" cat "$DI/top_level.txt" # THE WHEEL WAS BUILT BY setuptools' bdist_wheel, which is what `setuptools3' # reduces to here -- the case that says the build class did its job -- and it is # a PURE-PYTHON wheel, which is the packaging-side form of the same claim # `impure-modules' makes from inside the interpreter. check_out $P/dist-info-generator "Generator: bdist_wheel" cat "$DI/WHEEL" check_out $P/dist-info-purelib "Root-Is-Purelib: true" cat "$DI/WHEEL" # ------------------------------------------------------------- the package --- sed -n '/^Package: python3-mpmath$/,/^$/p' /var/lib/dpkg/status > "$W/st" 2>/dev/null check $P/pkg-known test -s "$W/st" check_out $P/pkg-installed "Status: install ok installed" cat "$W/st" check_out $P/pkg-version "Version: 1.3.0" cat "$W/st" grep '^Depends:' "$W/st" > "$W/dep" 2>/dev/null || : > "$W/dep" check $P/pkg-depends-line test -s "$W/dep" # THE Depends LINE BY NAME. The recipe's two target RDEPENDS are stdlib splits # of this image's own interpreter, so nothing here waited on another port. check_out $P/pkg-depends-core "python3-core" cat "$W/dep" check_out $P/pkg-depends-image "python3-image" cat "$W/dep" check_out $P/pkg-depends-math "python3-math" cat "$W/dep" # NO -ptest PACKAGE IS BUILT -- the recipe does not `inherit ptest' at all. This # is NOT the same statement as `tests-installed' above: upstream's tests ship # into site-packages as an ordinary subpackage, and nothing on this image runs # them. check_eq $P/pkg-no-ptest "" \ "$(sed -n '/^Package: python3-mpmath-ptest$/p' /var/lib/dpkg/status 2>/dev/null)" # --------------------------------------------------------------- it imports --- check $P/import python3 -c 'import mpmath' # ...AND SAYS NOTHING WHILE DOING IT. mpmath probes for gmpy2 at import and # falls back; a fallback that warned would land on a caller's stderr on every # start. check_eq $P/import-quiet "" \ "$(python3 -c 'import mpmath' 2>&1)" if [ ! -f "$PROBE" ]; then bst_notok $P/probe-present "$PROBE did not ship" rm -rf "$W" exit 0 fi bst_ok $P/probe-present bst_probe $P/probe-run "$W/probe" 900 python3 "$PROBE" OUT="$W/probe" pv() { grep "^$1=" "$OUT" 2>/dev/null | head -n 1 | cut -d= -f2-; } if [ -s "$OUT" ]; then bst_ok $P/probe-output else bst_notok $P/probe-output "the probe emitted nothing -- python3 did not start, or died before its first line; every CASE below is unscorable" fi check_eq $P/probe-complete "yes" "$(pv probe_done)" check_eq $P/probe-count "59" "$(pv emitted_count)" # ---------------------------------------------------------------- identity --- # THE MODULE THE PROBE MEASURED IS THE PACKAGED ONE. The probe sits in # ${libdir}/brintos-tests/data and the interpreter puts a script's own directory # first on sys.path, so this is the case that would catch a probe that had # shadowed the module it measures. check_eq $P/module-file "$PKGDIR/__init__.py" "$(pv module_file)" check_eq $P/is-package "yes" "$(pv is_package)" check_eq $P/version-attr "1.3.0" "$(pv version_attr)" # THE WHOLE PACKAGE IS PULLED IN EAGERLY BY `import mpmath' -- 51 modules, which # is pinned so that a release that made a subpackage lazy is visible as itself # rather than as a missing attribute somewhere below. check_eq $P/module-count "51" "$(pv module_count)" # PURE PYTHON FROM INSIDE THE INTERPRETER: not one imported mpmath module has a # file that is anything but a .py. check_eq $P/impure-modules "" "$(pv impure_modules)" # THE BACKEND AGREES WITH WHAT DECIDES IT. mpmath uses gmpy2 when it can import # one and MPMATH_NOGMPY is unset; this image ships python3-gmpy2, so the answer # here is `gmpy'. Asserted as the implication rather than as that constant, so # that adding or dropping gmpy2 from the image moves the expectation with it # instead of reddening a port that has not changed. Every digit below is # computed by whichever code path this names. if [ "$(pv gmpy2_importable)" = yes ] && [ "$(pv nogmpy_env)" = no ]; then _want_backend=gmpy; _want_mpz=mpz else _want_backend=python; _want_mpz=int fi check_eq $P/backend "$_want_backend" "$(pv backend)" check_eq $P/mpz-type "$_want_mpz" "$(pv mpz_type)" # ------------------------------------------------------ the precision model --- check_eq $P/default-dps "15" "$(pv default_dps)" check_eq $P/default-prec "53" "$(pv default_prec)" check_eq $P/dps50-dps "50" "$(pv dps50_dps)" # 169 = max(1, round(51 * 3.3219280948873626)), which is dps_to_prec's own # arithmetic. Pinned so that a change to the digits/bits conversion shows up as # itself rather than as fifty wrong digits. check_eq $P/dps50-prec "169" "$(pv dps50_prec)" check_eq $P/dps15-dps "15" "$(pv dps15_dps)" check_eq $P/dps15-prec "53" "$(pv dps15_prec)" # ------------------------------------------------- the published constants --- # Fifty digits of each, against the published values in the header. These are # the cases with an answer nobody in this build chose. check_eq $P/pi-50 \ "3.1415926535897932384626433832795028841971693993751" "$(pv pi_50)" # SHORTER BY DESIGN -- see the header. e's 50-digit rounding ends in a 7 with # nothing printable after it, and mpmath strips the trailing zeros. check_eq $P/e-50 \ "2.7182818284590452353602874713526624977572470937" "$(pv e_50)" check_eq $P/sqrt2-50 \ "1.4142135623730950488016887242096980785696718753769" "$(pv sqrt2_50)" # THE SAME THREE AT dps 15, which is what proves the digits move BECAUSE of the # precision setting rather than being three constants that happen to be right. A # port whose `mp.dps' assignment did nothing would pass the group above and fail # this one. check_eq $P/pi-15 "3.14159265358979" "$(pv pi_15)" check_eq $P/e-15 "2.71828182845905" "$(pv e_15)" check_eq $P/sqrt2-15 "1.4142135623731" "$(pv sqrt2_15)" # -------------------------------------------------------- exact arithmetic --- # Values with an exact binary representation, so these are equalities rather # than approximations. The complex one is (1+2i)(3+4i) = -5 + 10i. check_eq $P/quarter "0.25" "$(pv quarter)" check_eq $P/quarter-exact "yes" "$(pv quarter_exact)" check_eq $P/int-product "21.0" "$(pv int_product)" check_eq $P/int-product-exact "yes" "$(pv int_product_exact)" check_eq $P/mpc-product "(-5.0 + 10.0j)" "$(pv mpc_product)" check_eq $P/mpc-real "-5.0" "$(pv mpc_real)" check_eq $P/mpc-imag "10.0" "$(pv mpc_imag)" check_eq $P/mpc-exact "yes" "$(pv mpc_exact)" check_eq $P/mpc-type "mpc" "$(pv mpc_type)" # ------------------------------ WHAT str() SHOWS AND WHAT `==' SEES --------- # See the header. Both of these PRINT the answer a caller wanted and neither IS # it, so each is measured from both sides. check_eq $P/sum-str-50 "0.3" "$(pv sum_str_50)" check_eq $P/sum-eq-three-50 "no" "$(pv sum_eq_three_50)" # THE TICKET'S OWN QUESTION, and it holds HERE: at 169 bits neither operand is a # double any more, so the sum is not the double sum. check_eq $P/sum-eq-binary-50 "no" "$(pv sum_eq_binary_50)" # ...AND THE CORRECTION, at mpmath's own default precision. dps 15 IS 53 bits. check_eq $P/sum-str-15 "0.3" "$(pv sum_str_15)" check_eq $P/sum-eq-three-15 "no" "$(pv sum_eq_three_15)" check_eq $P/sum-eq-binary-15 "yes" "$(pv sum_eq_binary_15)" # THE RESIDUE AS THE RAW mpf TRIPLE, not as a printed approximation. # (sign, man, exp, bc) = (0, 1, -170, 1) is exactly 2**-170, one ulp at prec 169 # -- the smallest the difference could be and still be non-zero. check_eq $P/sum-residue-50 "(0, 1, -170, 1)" "$(pv sum_residue_50)" # THE SQUARE-ROOT HALF OF THE SAME TRAP. check_eq $P/sqrt2-sq-str "2.0" "$(pv sqrt2_sq_str)" check_eq $P/sqrt2-sq-eq-two "no" "$(pv sqrt2_sq_eq_two)" # --------------------------------------------------- quadrature and roots --- # Each has a closed form. The `*-close' cases assert to a stated tolerance of # 1e-45 rather than digit for digit: these are ITERATIVE, so the last digit is a # property of the stopping rule, and pinning it would red on a release that # merely tightened one. 1e-45 is five decades of headroom over the working # precision -- tight enough to catch an integrator that lost digits, loose enough # not to score the stopping rule. The `*-str' cases beside them pin what this # build actually produced, so a drift is still visible as a drift. check_eq $P/quad-sin-str "2.0" "$(pv quad_sin_str)" # EXACTLY 2, not merely close: the integral of sin over [0, pi] comes back with # a zero error term at this precision. check_eq $P/quad-sin-err "0.0" "$(pv quad_sin_err)" # integral of exp(-x^2) over [0, inf) = sqrt(pi)/2 check_eq $P/quad-gauss-str \ "0.88622692545275801364908374167057259139877472806119" "$(pv quad_gauss_str)" check_eq $P/quad-gauss-close "yes" "$(pv quad_gauss_close)" # the Dottie number, the unique real root of cos(x) = x check_eq $P/findroot-dottie-str \ "0.73908513321516064165531208767387340401341175890076" "$(pv findroot_dottie_str)" check_eq $P/findroot-dottie-close "yes" "$(pv findroot_dottie_close)" # x^2 = 2, whose root is the sqrt2 `sqrt2-50' pinned independently above -- so # this case ties the root finder to a constant with a published answer. check_eq $P/findroot-sqrt2-str \ "1.4142135623730950488016887242096980785696718753769" "$(pv findroot_sqrt2_str)" check_eq $P/findroot-sqrt2-close "yes" "$(pv findroot_sqrt2_close)" # ------------------------------------------------------------ error paths --- # TWO OF THE THREE RAISE, and the message is pinned as well as the type. The # ZeroDivisionError carries NO message -- `' is the probe's token for # that -- which a caller logging `str(exc)' would log as nothing at all. check_eq $P/err-div-zero-mpf "ZeroDivisionError: " "$(pv err_div_zero_mpf)" check_eq $P/err-div-zero-int "ZeroDivisionError: " "$(pv err_div_zero_int)" check_eq $P/err-bad-string \ "ValueError: could not convert string to float: 'not-a-number'" \ "$(pv err_bad_string)" check_eq $P/err-bad-string-two-dots \ "ValueError: could not convert string to float: '1.2.3'" \ "$(pv err_bad_string_two_dots)" # ...AND THE THIRD DOES NOT -- see the header. A negative precision is accepted # and silently clamped, so these two cases assert the ABSENCE of the exception # and the (prec, dps) pair that came back instead. check_eq $P/err-neg-dps ": (1, 1)" "$(pv err_neg_dps)" check_eq $P/err-neg-prec ": (1, 1)" "$(pv err_neg_prec)" # THE CLAMP ITSELF, at -1, 0 and 1, as `prec/dps'. dps_to_prec(-1) = max(1, 0), # dps_to_prec(0) = round(1 * 3.3219) = 3, dps_to_prec(1) = round(2 * 3.3219) = 7; # prec_to_dps floors all three at 1. check_eq $P/clamp-dps-minus1 "1/1" "$(pv clamp_dps_minus1)" check_eq $P/clamp-dps-zero "3/1" "$(pv clamp_dps_zero)" check_eq $P/clamp-dps-one "7/1" "$(pv clamp_dps_one)" # WHAT THE SILENCE COSTS: arithmetic still runs at the clamp, and pi at one bit # of mantissa is 4.0 -- correctly rounded, entirely useless, and undiagnosed. check_eq $P/clamped-pi "4.0" "$(pv clamped_pi)" # THE CONTEXT IS RESTORABLE. A clamp that could not be undone would leave every # later caller in the same interpreter at one bit. check_eq $P/restored-dps "15" "$(pv restored_dps)" check_eq $P/restored-prec "53" "$(pv restored_prec)" rm -rf "$W"