#! /usr/bin/python3 # File paranoia.py drives the Paranoia test. Execute this fie. from __future__ import division # use // for integer division import math # ~~~~~~~ 095-open_comment # # William Kahan's Floating Point Test "Paranoia" recast in Python # # Jerome Coonen, v. 0.8 2020 # # For an extended discussion of this program, please visit # the Paranoia Study at www.arithmazium.org. # # Versions of Paranoia can be found on www.netlib.org: # BASIC -- original source by William Kahan, 1982 # Pascal -- first translation, by Brian A. Wichmann, with # David M. Gay and David G. Hough, 1986 # C -- adapted from Pascal by Thos Sumner, with # David M. Gay, 1986 # Modula-2 -- adapted from Pascal by K. Y. Tan, 1986 # FORTRAN - single and double precision versions, 1986 # Forth -- adapted from C by Krishna Myneni, 2009 # # BASIC version (C) 1983 by: # Professor William M. Kahan, # EECS Dept., Soda Hall, Room 441 # University of California # Berkeley, California 94720 USA # # You may copy this program freely if you acknowledge its source. # Please send comments on the Python version to # Jerome Coonen at jcoonen@gmail.com # # ~~~~~~~ 100-initializations # ----START CODE---- # Number of trials in half a dozen random and sequential tests. NUM_TRIALS = 32 # Set all three flags to False for classic Paranoia flow. verbose = False ultra_verbose = True hasty = True # When True, skips all user interaction # ~~~~~~~ 110-globals # Declarations of global variables other than CONSTANTS err_failure = 0 # Indexes into the count and type lists below err_serious = 1 err_defect = 2 err_flaw = 3 error_count = [0, 0, 0, 0] error_type = ["Failure", "Serious", "Defect", "Flaw"] fpecount = 0 # int count of exceptioins detected milestone = 0 # int progress through the code page_num = 1 # int vestige of glass tty pages pow_err_cnt = 0 # int for testing x**y save_pow_err_cnt = 0 # int for testing x**y min_sqrt_error = math.nan max_sqrt_error = math.nan rand_seed = math.nan rand_next = math.nan # ~~~~~~~ 115-flags flags = { "mult_guard_digit": False, "div_guard_digit": False, "add_sub_guard_digit": False, "mult_rounding": "other", # rounded and chopped are the alternatives "div_rounding": "other", "add_sub_rounding": "other", "sqrt_rounding": "other", "uses_sticky_bit": False, "IEEE": False } # ~~~~~~~ 150-print_msgs # Opening messages -- printed as execution begins. def print_msgs(s): """Print a list of strings.""" for m in s: print(m) return # ~~~~~~~ 155-instructions def instructions(): """Self-explanatory user instructions""" instr = [ "Lest this program stop prematurely, i.e. before displaying\n", " `END OF TEST',\n", "" "try to persuade the computer NOT to terminate execution when an", "error like Over/Underflow or Division by Zero occurs, but rather", "to persevere with a surrogate value after, perhaps, displaying some", "warning. If persuasion avails naught, don't despair but run this", "program anyway to see how many milestones it passes, and then", "amend it to make further progress.\n" ] print_msgs(instr) return # ~~~~~~~ 160-heading def heading(): """Gives user a place to report issues.""" head = [ "Users are invited to help debug and augment this program so it will", "cope with unanticipated and newly uncovered arithmetic pathologies.\n", "Please send suggestions and interesting results to", "\tJerome Coonen", "\tjcoonen_at_gmail_dot_com\n\n", "\t*(C) 1983 Prof. William M. Kahan", "\t* You may copy this program freely if you acknowledge its source." ] print_msgs(head) return # ~~~~~~~ 165-charcteristics def characteristics(): """Explain whtat the program reveals""" chars = [ "Running this program should reveal these characteristics:", " Radix B = 1, 2, 4, 8, 10, 16, 100, 256 ...", " PRECISION = number of significant digits carried.", " ULP_OF_ONE_PLUS = B/B^PRECISION = One Ulp", " (Unit in the Last Place) of 1.000xxx .", " ULP_OF_ONE_MINUS = 1/B^PRECISION = One Ulp", " of numbers a little less than 1.0 .", " Presence of guard digits for Mult., Div. and Subt.", " Whether arithmetic is chopped, correctly rounded, or", " something else for Mult., Div., Add/Subt. and Sqrt.", " Whether a sticky bit is used correctly in rounding.", " underflow_threshold = an underflow threshold.", " min_positive and too_tiny_x tell whether underflow is", " abrupt, gradual, or fuzzy.", " overflow_threshold = an overflow threshold, roughly.", " pos_big_B tells, roughly, whether Infinity is represented.", " Comparisons are checked for consistency with subtraction", " and for contamination with pseudo-zeros.", " Sqrt is tested. Y^X is tested for integer X.", " Extra-precise subexpressions are revealed but NOT YET tested.", " Decimal-Binary conversion is NOT YET tested for accuracy." ] print_msgs(chars) return # ~~~~~~~ 170-history def history(): """Present a bit of historical context and set user expectations.""" hist = [ "The program attempts to discriminate among", " FLAWs, like lack of a sticky bit,", " Serious DEFECTs, like lack of a guard digit, and", " FAILUREs, like 2+2 == 5 .", "Failures may confound subsequent diagnoses.", "\nThe diagnostic capabilities of this program go beyond an earlier", "program called `MACHAR', which can be found at the end of the", "book `Software Manual for the Elementary Functions' (1980) by", "W. J. Cody and W. Waite. Although both programs try to discover", "the radix, precision, and range (over/underflow thresholds)", "of the arithmetic, this program tries to cope with a wider variety", "of pathologies, and to say how well the arithmetic is implemented.", "\nThe program is based upon a conventional radix representation for", "floating-point numbers, but also allows logarithmic encoding", "(radix B=1) as used by certain early WANG machines.\n" ] print_msgs(hist) return # ~~~~~~~ 200-utilities # ----START UTILITIES---- def notify(s): """When the impossible happens, send up a flare.""" print("{:s} test appears to be inconsistent...".format(s)) print(" PLEASE NOTIFY THE AUTHOR!\n") return # ~~~~~~~ 210-maths def fabs(x): return math.fabs(x) def floor(x): return math.floor(x) def log(x): """Return the natural log of the argument.""" return math.log(x) def pow(x, y): """Return x**y.""" return math.pow(x, y) def sqrt(x): return math.sqrt(x) # ~~~~~~~ 220-alt_input def alt_input(s): """Local version of input allows hasty execution.""" if hasty: print(s) reply = "y" else: reply = input(s) return reply # ~~~~~~~ 222-pause def pause(): """Wait until the user hits RETURN.""" global milestone, page_num s = alt_input("\nTo continue, press RETURN ") print("\nDiagnosis resumes after milestone Number ", milestone, " Page: ", page_num, "\n") milestone += 1 # the only incremental change to milestone page_num += 1 return # ~~~~~~~ 225-test_cond def test_cond(k, v, t): """Respond to test v of severity k, with error message t.""" if not v: bad_cond(k, t) print(".\n") return # ~~~~~~~ 230-bad_cond def bad_cond(k, t): """Record and report error t of severity k. Basic 920, for example. """ error_count[k] += 1 print(error_type[k] + ": " + t, end="") return # ~~~~~~~ 235-rand_frac def rand_frac(): """Generate random fractions. Compute x = (rand_next + rand_seed)^5 rand_next = x - floor(x) + 0.000005 * x Globals rand_next and rand_seed are initialized when needed. Returns the new value of rand_next """ # ~~~~~~~ 237-rand_frac2 global rand_next, rand_seed x = rand_next + rand_seed y = x * x y = y * y x = x * y y = x - math.floor(x) rand_next = y + x * 0.000005 return rand_next # ~~~~~~~ 240-print_if_err_cnt_positive def print_if_err_cnt_positive(): '''Print an error message from a global count.''' global pow_err_cnt if pow_err_cnt > 0: print("Similar discrepancies have occurred {:d} times." .format(pow_err_cnt)) return # ~~~~~~~ 250-find_big_two_to_the_nth # ============================================= # Computations of fundamental system constants like radix and precisioon. # ============================================= def find_big_2_to_nth(): """Compute 2**n satisfying |((2**n + 1)-2**n)-1| >= 1.""" x = ONE while True: x = x + x y = x + ONE z = y - x y = z - ONE if MINUS_ONE + fabs(y) >= ZERO: break # ... now x is just big enough that |((x+1)-x)-1| >= 1 ... return x # ~~~~~~~ 255-find_radix_from_2_nth def find_radix_from_big_2_to_nth(big): """The radix is marked by a change in the 'tens' place. Args: big: smallest value satisfying |((big + 1) - big) - 1| >= 1 Returns: radix, which may not be integral in log arithmetic """ # ~~~~~~~ 257-find_radix_from_2_nth2 y = ONE while True: b = big + y y = y + y b = b - big if b != ZERO: break return b # ~~~~~~~ 260-find_precision_big_B_to_nth def find_precision_big_B_to_nth(b): """Compute the number of B-digits in the arithmetic and the power of B sufficient to have the ones place fall off the right. Args: b: the global radix B, accepted as an argument Returns: precision: number of B digits in arithmetic power of B such that the low-order digit is the B's place """ # ~~~~~~~ 262-find_precision_big_B_to_nth2 big_b = ONE precision = ZERO while True: precision = precision + ONE big_b = big_b * b y = big_b + ONE if y - big_b != ONE: break return precision, big_b # ~~~~~~~ 265-find_ulp_of_one_plus def find_ulp_of_one_plus(guess): """Compute u such at 1+u is the next larger value than 1.""" x = FOUR / THREE # rounds in ulps of 1 third = x - ONE sixth = ONE_HALF - third x = sixth + sixth x = fabs(x - third) # sums two rounding errors if x < guess: x = guess # Now x = (unknown no.) ulps of 1+. # ~~~~~~~ 267-find_ulp_of_one_plus2 while True: u = x x = ONE_HALF * u + THIRTY_TWO * u * u x = x + ONE x = x - ONE if u <= x or x <= ZERO: break return u # ~~~~~~~ 270-find_ulp_of_one_minus def find_ulp_of_one_minus(guess): """Compute u such at 1-u is the next smaller value than 1.""" x = TWO / THREE sixth = x - ONE_HALF third = sixth + sixth x = third - ONE_HALF x = fabs(x + sixth) if (x < guess): x = guess # Now x == (unknown no.) ulps of 1-. # ~~~~~~~ 272-find_ulp_of_one_minus2 while True: u = x y = ONE_HALF * u + THIRTY_TWO * u * u y = ONE_HALF - y x = ONE_HALF + y y = ONE_HALF - x x = ONE_HALF + y if u <= x or x <= ZERO: break return u # ~~~~~~~ 275-compute_A_and_inverse def compute_A_and_inverse(radix): """Return value for constants A and ONE_OVER_A, based on radix. a is 2 if radix is 1 (logarithmic) or a power of 2. a is 10 if radix is a power of 10. Otherwise a is the radix. There are two tests that "a" behaves well in division. Args: radix: passed from B Returns: a, 1 / a """ # ~~~~~~~ 277-compute_A_and_inverse2 a = max(TWO, radix) # Default, picking up log radix=1 for z in [TWO, NINE + ONE]: y = radix while True: x = y y = y / z if floor(y) != y: break if x == ONE: a = z # Have found a power of 2 or 10 break # ~~~~~~~ 279-compute_A_and_inverse3 one_over_a = ONE / a x = a for x in [a, B]: y = ONE / x z = x * y - ONE_HALF test_cond(err_failure, z == ONE_HALF, "x * (1/x) differs from 1") return a, one_over_a # ~~~~~~~ 280-compute_C_and_inverse def compute_C_and_inverse(): """Return untility constant C = 1 / B**k, safely above the underflow threshold, and its reciprocal. C is a useful starting point for investigation of underflow. It has the form 1 / B**k with k chosen so that C is farther than a factor of 1 / B**PRECISION of underflowing to zero. C is a normal number. It's reciprocal is a large power of B. Returns: C, 1 / C Basic 4370-4400 """ # ~~~~~~~ 282-compute_C_and_inverse2 d = ULP_OF_ONE_MINUS # default value for integer precision if PRECISION != floor(PRECISION): # Compute 1 / B**ceiling(PRECISION) d = ONE_OVER_B for _ in range(floor(PRECISION)): d = d * ONE_OVER_B # ~~~~~~~ 283-compute_C_and_inverse3 # d = 1 / B**k for k = PRECISION or floor(PRECISION)+1 # Compute 1/d * 1/d**2 * 1/d**4 * ... until the multiplier # tapers off (due to underflow). This is the quick route to # a very tiny 1 / B**k in ever-inceasing steps. y = ONE z = d while True: c = y y = z z = y * y if y <= z or z + z <= z: break # ~~~~~~~ 284-compute_C_and_inverse4 # Finally, step toward the minimum normal number by multiples of d. # c stays one step behind y, so that at termination # 1 >> c > B**(min+PRECISION) > y > B**mnin > z (underflows) y = c z = y * d while True: c = y y = z z = y * d if y <= z or z + z <= z: break return c, ONE / c # ~~~~~~~ 285-compute_safe_ulps_of_one def compute_safe_ulps_of_one(): """Return a value useful in underflow and square root testing. Given the constant C, a power of B not far above the underflow threshold, find x such that (1+x)*C > C. Try x = 1 ulp, but back off to B ulps of one. C is large enough that underflow is not an issue, but if multiplication lacks a guard digit, a single ulp might be lost. Returns: 1 or B ulps of one Basic: 4450-4460 """ # ~~~~~~~ 287-compute_safe_ulps_of_one2 x = ULP_OF_ONE_PLUS s = ONE + x d = C * s if d <= C: # Fall back to B digits. A later test determines # whether multiplication is just too awful. x = B * ULP_OF_ONE_PLUS return x # ~~~~~~~ 290-compute_H_and_inverse def compute_H_and_inverse(): """Return min(1/B, 1/2) and reciprocal, noting H is a fraction.""" if B < TWO: h_inv = TWO else: h_inv = B return ONE / h_inv, h_inv # ~~~~~~~ 295-standardize_fractional_precision def standardize_fractional_precision(ulp, b): """Return the precision standardized so that any fraction part is in units of 1/240. The factors of 240 = 3*5*16 ensures that fractional octal, decimal, or hex digits will be handled properly. Args: ulp: unit in the last place of a value just less than 1 b: radix Returns: sss """ # ~~~~~~~ 297-standardize_fractional_precision2 # Intuitively, ulp = b**(-precisioon), so the negative of the # ratio of the logs retrieves the precision. But there may be # extra bits in octal or hex or even decimal, making up a partial # lowest-order digit. The factor 240 is a multiple of 8, 10, and # 16 with a bit extra. The value y rounds off noise from the logs. # TODO: resolve the apparent redundancy between the big factor 240 # and the check for being with a quarter. x = - TWOFORTY * log(ulp) / log(b) y = floor(ONE_HALF + x) if (fabs(x - y) * FOUR < ONE): x = y p = x / TWOFORTY y = floor(ONE_HALF + p) if (fabs(p - y) * TWOFORTY < ONE_HALF): p = y return p # ~~~~~~~ 300-test_for_extra_precise_subepressions # ============================================= # Test for extra precison. # ============================================= def test_for_extra_precise_subepressions(): """Print messages if wider intermediate values detected. """ # ~~~~~~~ 302-test_for_extra_precise_subepressionsc2 # (4/3 -1) - 1/4 = 1/12 with rounding error in values bigger than 1. # 3 * rounded value - 1/4 should be some rounding error in 1+. # z2 should be an ulp of 1+, unless the computation of x below enjoys # extra precision, allowing it to be even smaller. x = fabs(((FOUR / THREE - ONE) - ONE / FOUR) * THREE - ONE / FOUR) doit = True while doit: z2 = x x = (ONE + (ONE_HALF * z2 + THIRTY_TWO * z2 * z2)) - ONE doit = (z2 > x) and (x > ZERO) # ~~~~~~~ 304-test_for_extra_precise_subepressions4 # Now look at ulps of 1- by starting with rounding noise in 3/4. # x, y, and z are used in the three following loops. x = y = z = fabs((THREE / FOUR - TWO / THREE) * THREE - ONE / FOUR) while True: z1 = z z = (ONE / TWO - ((ONE / TWO - (ONE_HALF * z1 + THIRTY_TWO * z1 * z1)) + ONE / TWO)) + ONE / TWO if z1 <= z or z <= ZERO: break # ~~~~~~~ 306-test_for_extra_precise_subepressions6 # BUG in BASIC and C version -- the two loops below are not nested, # but simply in sequence with the z1 loop just above. # See lines 1730 and 1740 in the BASIC, the latter line should branch # back to itself. while True: y1 = y y = (ONE_HALF - ((ONE_HALF - (ONE_HALF * y1 + THIRTY_TWO * y1 * y1)) + ONE_HALF)) + ONE_HALF print("1730: y1 = {:.17e} y = {:.17e}".format(y1, y)) if y1 <= y or y <= ZERO: break while True: x1 = x x = ((ONE_HALF * x1 + THIRTY_TWO * x1 * x1) - ONE_MINUS_ULP) + ONE_MINUS_ULP print("1740: x1 = {:.17e} x = {:.17e}".format(x1, x)) if x1 <= x or x <= ZERO: break # ~~~~~~~ 308-test_for_extra_precise_subepressions8 if x1 != y1 or x1 != z1: bad_cond(err_serious, "Disagreements among the values x1, y1, z1,") print("respectively {:0.7e}, {:0.7e}, {:0.7e},".format(x1, y1, z1)) print("are symptoms of inconsistencies introduced") print("by extra-precise evaluation of arithmetic subexpressions.") notify("Possibly some part of this") if (x1 == ULP_OF_ONE_MINUS or y1 == ULP_OF_ONE_MINUS or (z1 == ULP_OF_ONE_MINUS)): print("That feature is not tested further by this program.\n") # ~~~~~~~ 310-test_for_extra_precise_subepressions10 elif z1 != ULP_OF_ONE_MINUS or z2 != ULP_OF_ONE_PLUS: if z1 >= ULP_OF_ONE_MINUS or z2 >= ULP_OF_ONE_PLUS: bad_cond(err_failure, "") notify("PRECISION") print("\tULP_OF_ONE_MINUS = {:0.7e}, z1 - ULP_OF_ONE_MINUS = {:0.7e}".format(ULP_OF_ONE_MINUS, z1 - ULP_OF_ONE_MINUS)) print("\tULP_OF_ONE_PLUS = {:0.7e}, z2 - ULP_OF_ONE_PLUS = {:0.7e}".format(ULP_OF_ONE_PLUS, z2 - ULP_OF_ONE_PLUS)) # ~~~~~~~ 312-test_for_extra_precise_subepressions12 else: if (z1 <= ZERO or z2 <= ZERO): print("Because of unusual B = {:0.f}".format(B)) print(", or exact rational arithmetic a result") print("z1 = {:0.7e}, or z2 = {:0.7e} ".format(z1, z2)) notify("of an extra-precision") # ~~~~~~~ 314-test_for_extra_precise_subepressions14 if z1 != z2 or z1 > ZERO: x = z1 / ULP_OF_ONE_MINUS y = z2 / ULP_OF_ONE_PLUS if y > x: x = y q = - log(x) print("Some subexpressions appear to be calculated extra") print("precisely with about {:.g} extra B-digits, i.e.", (q / log(B))) print("roughly {:.g} extra significant decimals." .format(q / log(10.))) print("That feature is not tested further by this program.") # ~~~~~~~ 320-test_normalized_subtraction # ============================================= # Tests for normalization, guard digit, rounding, use of a sticky bit. # ============================================= def test_normalized_subtraction(): """Test for renormalization on magnitude subtraction. Computes 1.0 via cancellation of two large values. Then adds a tiny increment that would be rounded off were the value 1.0 not normalized after the subtraction. """ # ~~~~~~~ 322-test_normalized_subtraction2 if (B >= TWO): x = BIG_B_NTH / (B * B) y = x + ONE z = y - x t = z + ULP_OF_ONE_PLUS x = t - z if x == ULP_OF_ONE_PLUS: print("Subtraction appears to be normalized, as it should be.") else: test_cond(err_failure, False, "Subtraction is not normalized x=y,x+z != y+z!") return # ~~~~~~~ 325-does_mult_have_guard_digit def does_mult_have_guard_digit(): """Test for a guard digit in mulitiplicaiton. Run three tests, 1*X and X*1 for X with nonzero low-order digit, and a product known to require one extra digit, before normalization. Returns: boolean answer to the question """ # ~~~~~~~ 327-does_mult_have_guard_digit2 print("\nChecking for guard digit in *, /, and -.") y = ONE_MINUS_ULP * ONE z = ONE * ONE_MINUS_ULP x = ONE_MINUS_ULP - ONE_HALF y = (y - ONE_HALF) - x z = (z - ONE_HALF) - x x = ONE + ULP_OF_ONE_PLUS t = x * B r = B * x x = t - B x = x - B * ULP_OF_ONE_PLUS t = r - B t = t - B * ULP_OF_ONE_PLUS x = x * (B - ONE) t = t * (B - ONE) gmult = (x == ZERO) and (y == ZERO) and (z == ZERO) and (t == ZERO) test_cond(err_serious, gmult, "* lacks a Guard Digit, so 1*x != x") return gmult # ~~~~~~~ 330-test_mult_low_digits def test_mult_low_digits(): """Try two tests of the form (1 + tiny)**2.""" z = B * ULP_OF_ONE_PLUS x = ONE + z y = fabs((x + z) - x * x) - ULP_OF_ONE_PLUS # ~~~~~~~ 332-mullowdig2 x = ONE - ULP_OF_ONE_PLUS z = fabs((x - ULP_OF_ONE_PLUS) - x * x) - ULP_OF_ONE_MINUS test_cond(err_failure, y <= ZERO and z <= ZERO, "* gets too many final digits wrong.\n") # ~~~~~~~ 335-does_div_have_guard_digit def does_div_have_guard_digit(): """Check for guard digit in division. Returns: boolean reply to question """ y = ONE - ULP_OF_ONE_PLUS x = ONE + ULP_OF_ONE_PLUS z = ONE / y y = z - x # ~~~~~~~ 337-does_div_have_guard_digit2 x = ONE / THREE z = THREE / NINE x = x - z # ~~~~~~~ 339-does_div_have_guard_digit3 t = NINE / TWENTY_SEVEN z = z - t gdiv = (x == ZERO) and (y == ZERO) and (z == ZERO) test_cond(err_defect, gdiv, "Division lacks a Guard Digit, so error can exceed 1 ulp\n" + "or 1/3 and 3/9 and 9/27 may disagree") # ~~~~~~~ 341-does_div_have_guard_digit6 y = ONE_MINUS_ULP / ONE x = ONE_MINUS_ULP - ONE_HALF y = (y - ONE_HALF) - x x = ONE + ULP_OF_ONE_PLUS t = x / ONE x = t - x # ~~~~~~~ 343-does_div_have_guard_digit7 # JTC: gdiv now includes all 5 tests not just last 4 like original. gdiv &= (x == ZERO) and (y == ZERO) test_cond(err_serious, gdiv, "Division lacks a Guard Digit, so x/1 != x") return gdiv # ~~~~~~~ 345-does_add_sub_have_guard_digit def does_add_sub_have_guard_digit(): """Check for guard digit in add and subtract. Returns: boolean reply to question """ x = ONE / (ONE + ULP_OF_ONE_PLUS) y = x - ONE_HALF - ONE_HALF test_cond(err_serious, y < ZERO, "Computed value of 1/1.000..1 >= 1") # ~~~~~~~ 347-does_add_sub_have_guard_digit2 x = ONE - ULP_OF_ONE_PLUS y = ONE + B * ULP_OF_ONE_PLUS z = x * B t = y * B r = z / B s = t / B x = r - x y = s - y test_cond(err_failure, x == ZERO and y == ZERO, "* and/or / gets too many last digits wrong") # ~~~~~~~ 349-does_add_sub_have_guard_digit3 y = ONE - ULP_OF_ONE_MINUS x = ONE - ONE_MINUS_ULP y = ONE - y t = B - ULP_OF_ONE_PLUS z = B - B_MINUS_ULP t = B - t gaddsub = ((x == ULP_OF_ONE_MINUS) and (y == ULP_OF_ONE_MINUS) and (z == ULP_OF_ONE_PLUS) and (t == ULP_OF_ONE_PLUS)) test_cond(err_serious, gaddsub, "- lacks Guard Digit, so cancellation is obscured") return gaddsub # ~~~~~~~ 350-how_does_mult_round def how_does_mult_round(): """Check type of rounding in multiplication. Returns: "rounded", "chopped", "other" """ # ~~~~~~~ 352-how_does_mult_round2 rmult = "other" y2 = ONE + ULP_OF_ONE_PLUS y1 = ONE - ULP_OF_ONE_PLUS x = ONE_AND_HALF - ULP_OF_ONE_PLUS y = ONE_AND_HALF + ULP_OF_ONE_PLUS z = (x - ULP_OF_ONE_PLUS) * y2 t = y * y1 z = z - x t = t - x # ~~~~~~~ 354-how_does_mult_round4 x = x * y2 y = (y + ULP_OF_ONE_PLUS) * y1 x = x - ONE_AND_HALF y = y - ONE_AND_HALF # ~~~~~~~ 356-how_does_mult_round6 if ((x == ZERO) and (y == ZERO) and (z == ZERO) and (t <= ZERO)): x = (ONE_AND_HALF + ULP_OF_ONE_PLUS) * y2 y = ONE_AND_HALF - ULP_OF_ONE_PLUS - ULP_OF_ONE_PLUS z = ONE_AND_HALF + ULP_OF_ONE_PLUS + ULP_OF_ONE_PLUS t = (ONE_AND_HALF - ULP_OF_ONE_PLUS) * y1 # ~~~~~~~ 358-how_does_mult_round8 x = x - (z + ULP_OF_ONE_PLUS) s1 = y * y1 s = z * y2 t = t - y y = (ULP_OF_ONE_PLUS - y) + s1 z = s - (z + ULP_OF_ONE_PLUS + ULP_OF_ONE_PLUS) # ~~~~~~~ 360-how_does_mult_round10 s1 = (y2 + ULP_OF_ONE_PLUS) * y1 y1 = y2 * y1 s1 = s1 - y2 y1 = y1 - ONE_HALF # ~~~~~~~ 362-how_does_mult_round12 if ((x == ZERO) and (y == ZERO) and (z == ZERO) and (t == ZERO) and (s1 == ZERO) and (y1 == ONE_HALF)): rmult = "rounded" print("Multiplication appears to round correctly. ") # ~~~~~~~ 364-how_does_mult_round14 elif ((x + ULP_OF_ONE_PLUS == ZERO) and (y < ZERO) and (z + ULP_OF_ONE_PLUS == ZERO) and (t < ZERO) and (s1 + ULP_OF_ONE_PLUS == ZERO) and (y1 < ONE_HALF)): rmult = "chopped" print("Multiplication appears to chop. ") # ~~~~~~~ 366-how_does_mult_round16 else: print("* is neither chopped nor correctly rounded. ") if (rmult == "rounded" and not flags["mult_guard_digit"]): notify("Multiplication") # ~~~~~~~ 368-how_does_mult_round18 else: print("* is neither chopped nor correctly rounded. ") return rmult # ~~~~~~~ 370-how_does_div_round def how_does_div_round(): """Check for rounding in division. Returns: "rounded", "chopped", "other" """ # ~~~~~~~ 372-how_does_div_round2 rdiv = "other" y2 = ONE + ULP_OF_ONE_PLUS y1 = ONE - ULP_OF_ONE_PLUS z = ONE_AND_HALF + ULP_OF_ONE_PLUS + ULP_OF_ONE_PLUS x = z / y2 t = ONE_AND_HALF - ULP_OF_ONE_PLUS - ULP_OF_ONE_PLUS y = (t - ULP_OF_ONE_PLUS) / y1 z = (z + ULP_OF_ONE_PLUS) / y2 # ~~~~~~~ 374-how_does_div_round4 x = x - ONE_AND_HALF y = y - t t = t / y1 z = z - (ONE_AND_HALF + ULP_OF_ONE_PLUS) t = (ULP_OF_ONE_PLUS - ONE_AND_HALF) + t # ~~~~~~~ 376-how_does_div_round6 if not ((x > ZERO) or (y > ZERO) or z > ZERO or t > ZERO): x = ONE_AND_HALF / y2 y = ONE_AND_HALF - ULP_OF_ONE_PLUS z = ONE_AND_HALF + ULP_OF_ONE_PLUS x = x - y t = ONE_AND_HALF / y1 y = y / y1 t = t - (z + ULP_OF_ONE_PLUS) # ~~~~~~~ 378-how_does_div_round8 y = y - z z = z / y2 y1 = (y2 + ULP_OF_ONE_PLUS) / y2 z = z - ONE_AND_HALF y2 = y1 - y2 y1 = (ONE_MINUS_ULP - ULP_OF_ONE_MINUS) / ONE_MINUS_ULP # ~~~~~~~ 380-how_does_div_round10 if ((x == ZERO) and (y == ZERO) and (z == ZERO) and (t == ZERO) and (y2 == ZERO) # BUG in C version: duplicate test and (y1 - ONE_HALF == ONE_MINUS_ULP - ONE_HALF)): rdiv = "rounded" print("Division appears to round correctly. ") if not flags["div_guard_digit"]: notify("Division") # ~~~~~~~ 382-how_does_div_round12 elif ((x < ZERO) and (y < ZERO) and (z < ZERO) and (t < ZERO) and (y2 < ZERO) and (y1 - ONE_HALF < ONE_MINUS_ULP - ONE_HALF)): rdiv = "chopped" print("Division appears to chop. ") # ~~~~~~~ 384-how_does_div_round15 if rdiv == "other": print("/ is neither chopped nor correctly rounded. ") return rdiv # ~~~~~~~ 385-how_does_add_sub_round def how_does_add_sub_round(): """Check rounding on add/subtract. Returns: "rounded", "chopped", "other" """ # ~~~~~~~ 387-how_does_add_sub_round2 round_add_sub = "other" x = ONE - ULP_OF_ONE_MINUS * ULP_OF_ONE_MINUS y = ONE + ULP_OF_ONE_PLUS * (ONE - ULP_OF_ONE_PLUS) z = ONE_MINUS_ULP - ONE_HALF x = (x - ONE_HALF) - z y = y - ONE # ~~~~~~~ 389-how_does_add_sub_round4 if (x == ZERO) and (y == ZERO): round_add_sub = "chopped" print("Add/Subtract appears to be chopped. ") # ~~~~~~~ 391-how_does_add_sub_round6 while flags["add_sub_guard_digit"]: # JTC TODO jibe with Basic x = (ONE_HALF + ULP_OF_ONE_PLUS) * ULP_OF_ONE_PLUS y = (ONE_HALF - ULP_OF_ONE_PLUS) * ULP_OF_ONE_PLUS x = ONE + x y = ONE + y x = (ONE + ULP_OF_ONE_PLUS) - x y = ONE - y if x != ZERO or y != ZERO: break # ~~~~~~~ 393-how_does_add_sub_round8 x = (ONE_HALF + ULP_OF_ONE_PLUS) * ULP_OF_ONE_MINUS y = (ONE_HALF - ULP_OF_ONE_PLUS) * ULP_OF_ONE_MINUS x = ONE - x y = ONE - y x = ONE_MINUS_ULP - x y = ONE - y if x != ZERO or y != ZERO: break # ~~~~~~~ 395-how_does_add_sub_round10 if round_add_sub == "chopped": # ***JTC: BASIC 2690 sets R3 = F1 - O2*R3 triggering 3 # messages when boch chopped and rounded are detected. round_add_sub = "other" else: round_add_sub = "rounded" print("Addition/Subtraction appears to round correctly. ") # ~~~~~~~ 397-how_does_add_sub_round12 if not flags["add_sub_guard_digit"]: # ***JTC: BUG, annot happen, guard digit is checked above. notify("Add/Subtract") break # Execute this block just once, with early exit possible. # ~~~~~~~ 399-how_does_add_sub_round14 if round_add_sub == "other": # pick up all the odd cases print("Addition/Subtraction neither rounds nor chops.") return round_add_sub # ~~~~~~~ 400-rounding_symmetry_test def rounding_symmetry_test(): """Test sign symmetry in simple rounding case. Returns: True if passes, False otherwise """ # ~~~~~~~ 402-rounding_symmetry_test2 verdict = True x = ONE + ONE_HALF * (ONE + ONE_HALF) y = (ONE + ULP_OF_ONE_PLUS) * ONE_HALF z = x - y t = y - x s = z + t if (s != ZERO): verdict = False bad_cond(err_flaw, "(x - y) + (y - x) is non zero!\n") return verdict # ~~~~~~~ 405-has_sticky_bit def has_sticky_bit(): """Create a rounding situation designed to require a sticky bit. Returns: True if sticky detected, False otherwise """ # ~~~~~~~ 407-has_sticky_bit2 verdict = False x = (ONE_HALF + ULP_OF_ONE_MINUS) * ULP_OF_ONE_PLUS y = ONE_HALF * ULP_OF_ONE_PLUS z = ONE + y t = ONE + x # ~~~~~~~ 409-has_sticky_bit4 if (z - ONE <= ZERO) and (t - ONE >= ULP_OF_ONE_PLUS): z = t + y y = z - x # ~~~~~~~ 411-has_sticky_bit6 if (z - t >= ULP_OF_ONE_PLUS) and (y - t == ZERO): x = (ONE_HALF + ULP_OF_ONE_MINUS) * ULP_OF_ONE_MINUS y = ONE_HALF * ULP_OF_ONE_MINUS z = ONE - y t = ONE - x # ~~~~~~~ 413-has_sticky_bit8 if (z - ONE == ZERO) and (t - ONE_MINUS_ULP == ZERO): z = (ONE_HALF - ULP_OF_ONE_MINUS) * ULP_OF_ONE_MINUS t = ONE_MINUS_ULP - z q = ONE_MINUS_ULP - y # ~~~~~~~ 415-has_sticky_bit10 if ((t - ONE_MINUS_ULP == ZERO) and (ONE_MINUS_ULP - ULP_OF_ONE_MINUS - q == ZERO)): z = (ONE + ULP_OF_ONE_PLUS) * ONE_AND_HALF t = (ONE_AND_HALF + ULP_OF_ONE_PLUS) - z + ULP_OF_ONE_PLUS x = ONE + ONE_HALF / B y = ONE + B * ULP_OF_ONE_PLUS z = x * y # ~~~~~~~ 417-has_sticky_bit12 if (t == ZERO) and (x + B * ULP_OF_ONE_PLUS - z == ZERO): if (B != TWO): x = TWO + ULP_OF_ONE_PLUS y = x / TWO if (y - ONE) == ZERO: verdict = True else: verdict = True # ~~~~~~~ 419-has_sticky_bit14 return verdict # ~~~~~~~ 425-test_mult_commutivity def test_mult_commutivity(count): """Run some random tests to check that x*y = y*x. """ global rand_next, rand_seed rand_seed = sqrt(3.0) rand_next = ONE / THREE # ~~~~~~~ 425a-test_mult_commutivity2 i = 0 doit = True while doit: x = rand_frac() y = rand_frac() yx = y * x xy = x * y xyyx = xy - yx i = i + 1 doit = (i < count) and (xyyx == ZERO) # ~~~~~~~ 425b-test_mult_commutivity2 if (xyyx == ZERO): # One rather less random test lays out the operands, products, # and the computed difference. x = ONE + ONE_HALF / THREE y = (ULP_OF_ONE_PLUS + ULP_OF_ONE_MINUS) + ONE xy = x * y yx = y * x xyyx = ((ONE + ONE_HALF / THREE) * ((ULP_OF_ONE_PLUS + ULP_OF_ONE_MINUS) + ONE) - ((ULP_OF_ONE_PLUS + ULP_OF_ONE_MINUS) + ONE) * (ONE + ONE_HALF / THREE)) # ~~~~~~~ 427-test_mult_commutivity2 # In the usual case, count random tests and 1 extra test are run. # xyyx is the final arbiter of success. if xyyx != ZERO: print("x * y == y * x fails for x = {:0.17e} y = {:0.17e}" .format(x, y)) print(" x * y = {:0.17e} y * x = {:0.17e}".format(xy, yx)) print(" and x * y - y * x = {:0.17e}".format(xyyx)) bad_cond(err_defect, "x * y == y * x trial fails.") else: print(" No failures found in {:d} integer pairs.".format(count + 1)) # ~~~~~~~ 430-sqrt_single_bound_test # ============================================= # Tests of square root accuracy and monotonicity. # ============================================= def sqrt_single_bound_test(tlist): """Test x = sqrt(x*x), raise error if not, and track max and min signed error in units of ulp. Uses an artful decomposition of x into xa + xb to facilitate test for equality. ***TODO: elaborate that this decomposition is exact -- because the values of x all have trailing 0s? Args: tlist - a list of tuples of: x - value to be tested ulp - 1/2 ulp of argument x error_kind - level of alarm to raise Returns: True if all tests pass, False otherwise """ # ~~~~~~~ 432-sqrt_single_bound_test2 global min_sqrt_error, max_sqrt_error success = True for (x, ulp, error_kind) in tlist: xb = x * ONE_OVER_B xa = x - xb err = ((sqrt(x * x) - xb) - xa) / ulp # ~~~~~~~ 434-sqrt_single_bound_test4 if err != ZERO: success = False if err < min_sqrt_error: min_sqrt_error = err if err > max_sqrt_error: max_sqrt_error = err bad_cond(error_kind, "\n") print("sqrt( {:0.17e}) - {:0.17e} = {:0.17e}".format(x * x, x, ulp * err)) print("\tinstead of correct value 0 .") return success # ~~~~~~~ 433-test_sqrt_int def test_sqrt_int(): """Test sqrt(x**2) for consecutive integer in a B-ade.""" x = TWO # Default start point for log arithmetic y = B if B != ONE: # ***TODO: log arithmetic issue while True: x = y y = B * y if y - x >= NUM_TRIALS: break ulp = x * ULP_OF_ONE_PLUS # ***TODO -- why was Py _MINUS, with others _PLUS? for _ in range(NUM_TRIALS): x = x + ONE # when NUM_TRIALS is size of a binade, x hits higher B**k if not sqrt_single_bound_test([(x, ulp, err_defect)]): break return # ~~~~~~~ 435-test_sqrt_monotonicity def test_sqrt_monotonicity(): """Test for monotonicity around the radix B.""" i = - 1 x = B_MINUS_ULP y = B z = B + B * ULP_OF_ONE_PLUS # B plus 1 ulp not_monot = False monot = False # ~~~~~~~ 435a-test_sqrt_monotonicity2 while not (not_monot or monot): i = i + 1 # CASE: y = B x = sqrt(x) q = sqrt(y) z = sqrt(z) if x > q or q > z: not_monot = True # ~~~~~~~ 435b-test_sqrt_monotonicity4 else: q = floor(q + ONE_HALF) # B = q*q only if radix B is 16 or 4 if (i <= 0) and (B != q * q): monot = True # exit early unless B is 16 or 4 # ~~~~~~~ 435c-test_sqrt_monotonicity6 elif i <= 0: # CASE: y = sqrt(B), if B = 4, 16 or other square y = q x = y - ULP_OF_ONE_PLUS z = y + ULP_OF_ONE_PLUS # ~~~~~~~ 435d-test_sqrt_monotonicity8 elif i == 1: # CASE: 7 = 1/sqrt(B), if B = 4, 16, or other square y = y * ONE_OVER_B x = y - ULP_OF_ONE_MINUS z = y + ULP_OF_ONE_MINUS else: # Have survived three cases monot = True # ~~~~~~~ 435e-test_sqrt_monotonicity10 if monot: print("sqrt has passed a test for Monotonicity.") else: bad_cond(err_defect, "") print("sqrt(x) is non-monotonic for x near {:0.7e} .".format(y)) # ~~~~~~~ 440-update_sqrt_errors def update_sqrt_errors(down, up): """Update the sqrt global min and max errors.""" global min_sqrt_error, max_sqrt_error if up > max_sqrt_error: max_sqrt_error = up if down < min_sqrt_error: min_sqrt_error = down return # ~~~~~~~ 446-num_theory_sqrt_test def num_theory_sqrt_test(x, D, y, z2): """Test utility.""" # ~~~~~~~ 446a-num_theory_sqrt_test1 test = 0 if x - B >= z2 - B and x - z2 <= BIG_B_NTH - z2: test = 1 if ultra_verbose: print("\t\tnum_theory_sqrt_test x = {:.6f} D = {:.6f}".format(x, D)) X2 = sqrt(x * D) if ultra_verbose: print("\t\t X2 = {:.6f} = sqrt( {:.6f} )" .format(X2, x * D)) y2 = (X2 - z2) - (y - z2) X2 = X8 / (y - ONE_HALF) X2 = X2 - ONE_HALF * X2 * X2 # ~~~~~~~ 446b-num_theory_sqrt_test2 down_err = (y2 + ONE_HALF) + (ONE_HALF - X2) up_err = y2 - X2 if ultra_verbose: print("\t\t y2 = {:.5e} X2 = {:.5e}".format(y2, X2)) print("\t\t dn = {:.5e} up = {:.5e}" .format(down_err, up_err)) update_sqrt_errors(down_err, up_err) if ultra_verbose: print("\tnew X2 = {:.17e} y2 = {:.17e}".format(X2, y2)) elif verbose: print("\thasty exit from num_theory_sqrt_test()") return test # ~~~~~~~ 448-num_theory_sqrt_value def num_theory_sqrt_value(x, D, Q, z, z1): """Test utility.""" x = z1 * Q x = floor(ONE_HALF - x / B) * B + x Q = (Q - x * z) / B + x * x * (D / B) z = z - TWO * x * D # ~~~~~~~ 448a-num_theory_sqrt_value1 if z <= ZERO: if ultra_verbose: print("\t\t\tnum_theory_sqrt_value: z = {:.17} <= ZERO".format(z)) z = - z z1 = - z1 # ~~~~~~~ 448b-num_theory_sqrt_value2 D = B * D if ultra_verbose: print("\tnew D = {:.17e} Q = {:.17e}".format(D, Q)) return x, D, Q, z, z1 # ~~~~~~~ 450-test_power_result # ============================================= # Tests of X**K for integer X and K. # ============================================= def test_power_result(x, z, q): """Check that x equals z**q, usually for exact integer cases.""" global pow_err_cnt y = pow(z, q) if ultra_verbose: print("\t{:.17e} = {:.17e} ^ {:.17e}".format(y, z, q)) if y != x: # ~~~~~~~ 452-test_power_result2 if pow_err_cnt <= 0: if z == ZERO and q <= ZERO: print("WARNING: computing") else: bad_cond(err_defect, "computing") print("\t({:0.17e}) ^ ({:0.17e})".format(z, q)) print("\tyielded {:0.17e};\n".format(y)) print("\twhich compared unequal to correct {:0.17e} ;" .format(x)) print("\t\tthey differ by {:0.17e} .".format(y - x)) pow_err_cnt += 1 # ... count discrepancies. return # ~~~~~~~ 455-test_integer_powers def test_integer_powers(x, z, i, max_i): """Test z**k for k = i .. max_i.""" while True: q = float(i) if ultra_verbose: print("test_int_pow with x, z, q = {:.17e} {:.17e} {:.4f}" .format(x, z, q)) test_power_result(x, z, q) # ~~~~~~~ 457-test_integer_powers2 i += 1 x = z * x # Stay within the range of exactly representable integers. if x >= BIG_B_NTH or i > max_i: break return # ~~~~~~~ 520-tiny_powers_of_B # ============================================= # Tests for underflow behavior. # ============================================= def tiny_powers_of_B(c): """Return tiny values, stepping toward zero by factors of radix or 2. Starting with a value like C = 1/B**k close to the underflow threshold, walk down by factors of H = min(1/B, 1/2). Compute less_tiny, less_tiny, and z differing by factors of H. Value less_tiny avoids anomalies. Value z typically falls to zero. The factor H protects against B=1. Args: c: 1/B**k, expected to be the constant C or comparable Returns: less_tiny: next tiniest positive value, by a factor of H tiny: tiniest positive value z: too tiny value, that underflows to zero or a pseudo-zero Basic 4430-4440 """ # ~~~~~~~ 521-tiny_powers_of_B2 tiny = c z = tiny * H while True: less_tiny = tiny tiny = z z = z * H # Terminate if z pins at a nonzero minimal value or # if z falls to an epsilon or 0. if tiny <= z or z + z <= z: break print("***Approaching underflow by powers of the radix:") print("*** less_tiny = {:.7e}, tiny = {:.7e}," + "z = {:.7e}".format(less_tiny, tiny, z)) return less_tiny, tiny, z # ~~~~~~~ 522-tiny_values_and_difference def tiny_values_and_difference(one_plus): """Compute the tiniest values from a starting value with nonzero low-order bits. This function is parallel to tiny_powers_of_B(), but looks for nonzero differences of tiny values, which are evidence of gradual underflow. If multiplication is too inaccurate, raise an error and return default values, shown in parens under Returns. Args: one_plus: 1 plus enough ulps that they shouldn't be lost when multiplied by a power of B # ~~~~~~~ 523-tiny_values_and_difference2 Returns: tiny: smallest well-behaved value (0) *** why not tiny_B? tinier: first value to exhibit underflow (tiny_B) too_tiny: underflows to 0 or an epsilon value (too_tiny_B) u_threshold: smallest well-behaved value, if underflow degrades into nonzero values, or 0 if underflow flushes to 0 (tiny_B) tiny_delta: difference between two values caused by underflow (0) save_tiny1: value of tiny where tiny_delta computed (0) save_tiny2: recomputed tiny where tiny_delta computed (0) Basic 4450-4520 """ # ~~~~~~~ 524-tiny_values_and_difference3 d = C * one_plus # 1 + SAFE_ULPS_OF_ONE should be enough ulps of 1 that d > C. if d <= C: # But if not, we punt with default values. bad_cond(err_failure, "multiplication gets too many last digits wrong.") pause() return 0, tiny_B, too_tiny_B, tiny_B, 0, 0, 0 # ~~~~~~~ 525-tiny_values_and_difference4 else: tinier = d too_tiny = tinier * H u_threshold = ZERO tiny_delta = ZERO # indicates no value computed yet save_tiny1 = ZERO save_tiny2 = ZERO while True: tiny = tinier tinier = too_tiny # This loop mirrors the computation in tiny_powers_of_B(), # except that the launch point is C*s with nonzero low-order # bits, versus C, which is just a power of the radix. if (tiny_delta + tiny_delta) <= tiny_delta: if ultra_verbose: print("\tUnderflow loop: within if, tinier = {:.7e}" .format(tinier)) save_tiny2 = tinier * ONE_OVER_H tiny_delta = fabs(tiny - save_tiny2) # may underflow to 0 save_tiny1 = tiny if (u_threshold == ZERO) and (tiny != save_tiny2): # Test for 0 captures first time through, in case # tiny_delta underflows to 0. u_threshold = tiny # Placing this statement after the (tiny*H)/H test above # emphasizes that only nonzero values of tinier qualify for # values degraded by underflow. too_tiny = too_tiny * H if ultra_verbose: print("\tUnderflow loop: too_tiny = {:.7e}".format(too_tiny)) if (tinier <= too_tiny or too_tiny + too_tiny <= too_tiny): break return (tiny, tinier, too_tiny, u_threshold, tiny_delta, save_tiny1, save_tiny2) # ~~~~~~~ 526-does_tiny_value_misbehave def does_tiny_value_misbehave(z): """Check for abnormal behavior of a value presumed to be tiny or zero. Given a value z, check that (z + z)/z is 2.0 or very close, and that 1.0*z = z*1.0 = z/1.0 = z. This is especially interesting near the underflow threshold, but the function should work unless z is so huge that doubling it overflows. Args: z - test value Returns: boolean reply to the question Basic 4640-4680 """ # ~~~~~~~ 527-does_tiny_value_misbehave2 reply = False if z == ZERO: # nothing complex to test in the easy case return reply print("Since comparison denies z = 0, evaluating ", end="") print("(z + z) / z should be safe.") z_quo = ONE try: z_quo = (z + z) / z except ZeroDivisionError: reply = True error_count[err_serious] = error_count[err_serious] + 1 print("But the division triggered an exception.") print("This is a VERY SERIOUS DEFECT!") print("What the machine gets for (z + z) / z is {:0.17e} ." .format(z_quo)) if fabs(z_quo - TWO) < B * ULP_OF_ONE_PLUS: print("This is O.K., provided Over/Underflow", end="") print(" has NOT just been signaled.") else: reply = True if z_quo < ONE or z_quo > TWO: error_count[err_serious] = error_count[err_serious] + 1 print("This is a VERY SERIOUS DEFECT!") else: error_count[err_defect] = error_count[err_defect] + 1 print("This is a DEFECT!") # ~~~~~~~ 528-does_tiny_value_misbehave3 r1 = z * ONE r2 = ONE * z r3 = z / ONE if (z == r1) and (z == r2) and (z == r3): if reply: pause() else: reply = True bad_cond(err_defect, "What prints as z = ", end="") print("{:0.17e}\n\tcompares different from ".format(z)) if z != r1: print("z * 1 = {:0.17e} ".format(r1)) if (z != r2) and (r2 != r1): print("1 * z == {:0.17e}".format(r2)) if z != r3: print("z / 1 = {:0.17e}".format(r3)) if r2 != r1: error_count[err_defect] = error_count[err_defect] + 1 bad_cond(err_defect, "Multiplication does not commute!\n") print("\tComparison alleges that 1 * z = {:0.17e}" .format(r2)) print("\tdiffers from z * 1 = {:0.17e}".format(r1)) pause() return reply # ~~~~~~~ 529-is_gradual_underflow_IEEE def is_gradual_underflow_IEEE(t): """Test gradual underflow for IEEE compatibility. Args: t - tiniest nonzero number discovered Returns: boolean reply to question Basic 5150 """ # ~~~~~~~ 530-is_gradual_underflow_IEEE2 print("Underflow is gradual; it incurs Absolute Error =") print("(roundoff in underflow_threshold) < min_positive.") # Test for IEEE rounding at the very bottom of the range. # The test looks for a double rounding, first to a # higher precision, then the target. The computation is just # under half an ulp, but an extra rounding will push it to a # half-way case that will round up. # ~~~~~~~ 531-is_gradual_underflow_IEEE2 # y = t/C * (3/2 + u) # x = 1/C * (1 + u) # y/x = t * (3/2 + u) * (1 - u + u**2 - ...) # = t * (1 + 1/2 - u/2 + u**2/2 - ...) # = t, barely rounding down y = t * ONE_OVER_C y = y * (ONE_AND_HALF + ULP_OF_ONE_PLUS) x = ONE_OVER_C * (ONE + ULP_OF_ONE_PLUS) y = y / x return (y == t) # ~~~~~~~ 532-test_tiny_differences def test_tiny_differences(): """"With no gradual underflow, look for x != z but x-z = 0. This function is called after cases 1, 2, and 3 of underflow triage, where case 4 captures gradual underflow. The idea is to show how the nonzero differences between tiny numbers are not representable in the arithmetic. Usually, base_tiny == underflow_threshold but if it's smaller, then r * underflow_threshold is the geometric mean of the two. In the typical non-gradual-underflow cases when tiny_x and underflow_threshold differ by a factor of 1 or H, the else clause below is taken. Basic 5000-5080 """ # ~~~~~~~ 533-test_tiny_differences2 print("") try: r = sqrt(base_tiny / underflow_threshold) except ZeroDivisionError: print("base_tiny / underflow_threshold failed!") r = H + H # fall through to else case below if r <= H: z = r * underflow_threshold x = z * (ONE + r * H * (ONE + H)) else: z = underflow_threshold x = z * (ONE + H * H * (ONE + H)) # ~~~~~~~ 533a-test_tiny_differences3 if (x != z) and (x - z == ZERO): bad_cond(err_flaw, "") print("x = {:0.17e}\n\tis not equal to z = {:0.17e} ." .format(x, z)) w = x - z print("yet x - z yields {:0.17e} .".format(w)) print(" Should this NOT signal Underflow, ") print("this is a SERIOUS DEFECT\nthat causes ") print("confusion when innocent statements like"); print(" if (x == z) ... else") print(" ... (f(x) - f(z)) / (x - z) ...") print("encounter Division by ZERO although actually") try: print("x / z = 1 + {:g} .".format((x / z - ONE_HALF) - ONE_HALF)) except ZeroDivisionError: print("x / z fails!") return # ~~~~~~~ 534-test_for_narrow_range def test_for_narrow_range(): """Test that the 5th power of half an ulp of 1 doesn't underflow. Basic 5170-5210 """ # ~~~~~~~ 535-test_for_narrow_range2 y2 = ULP_OF_ONE_MINUS * ULP_OF_ONE_MINUS y = y2 * y2 y2 = y * ULP_OF_ONE_MINUS if y2 <= underflow_threshold: # ***ISSUE: Should this be > underflow_threshold, given message? if y > min_positive: bad_cond(err_defect, "") i = 5 else: bad_cond(err_serious, "") i = 4 print("Range is too narrow; ULP_OF_ONE_MINUS^{:d} Underflows." .format(i)) return # ~~~~~~~ 536-test_extreme_underflow def test_extreme_underflow(ut): """Test the behavior of underflow_threshold**2. The code computes ut+ = ut * (1 + eps) ut++ = ut * B * (1 + eps) and then determines where the ultra-small product lies on the line ---------- 0 ---------- ut+ ---------- ut++ ---------- serious OK defect serious Args: ut: expect underflow_threshold, but works with any tiny value Basic 5230-5300 """ # ~~~~~~~ 537-test_extreme_underflow2 # Compute log(ut) base 1/H, where H=min(1/B, 1/2), so it's # log base 2, 8, 10 or 16. Use the 240 factor to pick up # fractional digits base 8, 10, or 16. y = (-floor(ONE_HALF - TWOFORTY * log(ut) / log(ONE_OVER_H)) / TWOFORTY) y2 = y + y # ~~~~~~~ 538-test_extreme_underflow3 print("Since underflow occurs below the threshold") print("threshold = ({:0.17e}) ^ ({:0.17e})\nonly underflow " .format(ONE_OVER_H, y), end="") print("should afflict the expression\n\t({:0.17e}) ^ ({:0.17e});" .format(ONE_OVER_H, y2)) print("actually calculating yields:", end="") # Python has no underflow exception, so no try/except here. ultra = pow(ONE_OVER_H, y2) # Can't happen -- bad_cond(err_serious, " trap on underflow.\n") print(" {:0.17e} .".format(ultra)) # ~~~~~~~ 539-test_extreme_underflow4 # Note use of SAFE_ULPS_OF_ONE versus ULP_OF_ONE_PLUS to ensure # multiplication doesn't lose the low bits, for lack of a # guard digit or worse. if ultra < ZERO or ultra > (B + B * SAFE_ULPS_OF_ONE) * ut: bad_cond(err_serious, "this is not between 0 and underflow\n") print(" threshold = {:0.17e} .".format(ut)) elif not (ultra > ut * (ONE + SAFE_ULPS_OF_ONE)): print("This computed value is O.K.") else: bad_cond(err_defect, "this is not between 0 and underflow\n") print(" threshold = {:0.17e} .".format(ut)) return # ~~~~~~~ 540-test_for_pseudo_zero def test_for_pseudo_zero(pseudo): """Test for a nonzero value that violates basic numerical laws. Args: pseudo: tiny value that might misbehave Basic 4580-4630 """ # ~~~~~~~ 541-test_for_pseudo_zero2 if (pseudo == ZERO): return print("") # Test pseudo for "phoney-zero" behavior, violating either # pseudo < tiny_x or pseudo < pseudo + pseudo # These are the loop termination tests in tiny_values_and_difference(). if (pseudo <= ZERO): bad_cond(err_failure, "Positive expressions can underflow to an " + "allegedly negative value\n") print("pseudo that prints out as: {:g} .".format(pseudo)) x = -pseudo if x <= ZERO: print("But -pseudo, which should be", end="") print("positive, isn't; it prints out as {:g} ." .format(x)) # ~~~~~~~ 542-test_for_pseudo_zero3 else: bad_cond(err_flaw, "Underflow can stick at an allegedly positive\n") print("value pseudo that prints out as {:g} ." .format(pseudo)) # One final test for strangeness around the underflow threshold. # The test is made just for the side-effects, not the reulst value. discard = does_tiny_value_misbehave(pseudo) return # ~~~~~~~ 548-e_squared def e_squared(): """Compute exp(2) from the Taylor series for exp(x). We compute exp(1/2) and square it twice. exp(1/2) = 1 + 1/2 + 1/8 + 1/48 + 1/(48*2*4) + 1/(48*4*4*5) + ... = 13/8 + 1/48 * (1 + 1/8 + 1/80 + 1/960 + ...) Compute the sum as a two-piece hi/lo accumulation and then evaluate 13/8 + sum/48. Basic 5320-5350 """ # ~~~~~~~ 550-e_squared2 # Initialize the loop values so that the first term is 1, addied # into the initial sum of zero. x = ZERO k = 2 term = TWO * THREE x_lo = ZERO if verbose: print("Loop to compute 1/48th the sum of terms 3 to k") print("of the Taylor series of exp(1/2).") while True: old_x = x k = k + 1 term = term / (k + k) inc = x_lo + term x = old_x + inc # After adding the next term into the sum, capture x_lo, the # amount of inc that is not captured by x. x_lo = (old_x - x) + inc if verbose: print("\texp2 x={:.6e} x_lo={:.6e} term={:.6e}" .format(x, x_lo, term)) # Exit the loop when the value x is no longer changing. if x <= old_x: break # ~~~~~~~ 550a-e_squared3 e12 = (ONE_AND_HALF + ONE / EIGHT) + x / (ONE_AND_HALF * THIRTY_TWO) e = e12 * e12 e2 = e * e if verbose: print("exp(1/2) = {:.12f} exp(1) = {:.12f} exp(2) = {:.12f}" .format(e12, e, e2)) return e2 # ~~~~~~~ 554-test_x_to_xp1_over_xm1 def test_x_to_xp1_over_xm1(e2): """ Compare x^(x+1)/(x-1) to exp(2) for x above and below 1. Args: e2: computed value of exp(2) Basic 5360-5440 """ print("Testing x^((x + 1) / (x - 1)) vs. " + "exp(2) = {:0.12f} as x -> 1.".format(exp2)) # BUG: x runs away from 1, perhaps "x near 1"? # Run tests below 1, then above 1, with a start value # and a next value one ulp away. if verbose: print("Looping on values just below and just above 1.") for x, next_x in [(ONE_MINUS_ULP, ONE_MINUS_ULP - ULP_OF_ONE_MINUS), (ONE + ULP_OF_ONE_PLUS, ONE + ULP_OF_ONE_PLUS + ULP_OF_ONE_PLUS)]: # ~~~~~~~ 554a-test_x_to_xp1_over_xm12 test_cnt = 1 while True: z = x - ONE_OVER_B z = (x + ONE) / (z - (ONE - ONE_OVER_B)) dev = pow(x, z) - exp2 if verbose: print("\tx = {:.17f} z = {:.6e} deviation of".format(x, z) + " x^z = {:.3e}".format(dev)) # ~~~~~~~ 554b-test_x_to_xp1_over_xm13 if (fabs(dev) > TWOFORTY * ULP_OF_ONE_PLUS): delta_x = (x - ONE_OVER_B) - (ONE - ONE_OVER_B) bad_cond(err_defect, "Calculated") print(" {:0.17e} for".format(pow(x, z))) print("\t(1 + ({:0.17e}) ^ ({:0.17e});".format(delta_x, z)) print("\tdiffers from correct value by {:0.17e} ." .format(dev)) print("\tThis much error may spoil financial") print("\tcalculations involving tiny interest rates.") return # ~~~~~~~ 554c-test_x_to_xp1_over_xm14 else: # Jump by 1 ulp, then 2, then 4, etc. z = (next_x - x) * TWO + next_x x = next_x next_x = z z = ONE + (x - ONE_MINUS_ULP) * (x - ONE_MINUS_ULP) # *** BUG: P has (z > ONE), which means test done once only. # Keep testing until too close to 1 or count too big. if (z <= ONE) and (test_cnt < NUM_TRIALS): test_cnt += 1 else: break # BUG: removed unneeded error flag if N == 0: print("Accuracy seems adequate.") return # ~~~~~~~ 556-extreme_z_to_q def extreme_z_to_q(): """Test A and 1/A to powers reaching 1/C and C. A is 2 or 10. C is 1/B**k, tiny but well away from underflow. C is an integer power of 1/A, so try the four possibilities of A and 1/A producing C and 1/C. Basic 5460-5490 """ global pow_err_cnt # var used by power tests print("Testing powers z^q at four nearly extreme values.") pow_err_cnt = 0 # this test resets the global value to 0 without restore. # ~~~~~~~ 556a-test_x_to_xp1_over_xm14 q = floor(ONE_HALF - log(C) / log(A)) # C is a power of B and thus A for z in [A, ONE_OVER_A]: x = ONE_OVER_C test_power_result(x, z, q) q = -q x = C test_power_result(x, z, q) print_if_err_cnt_positive() if pow_err_cnt == 0: print(" ... no discrepancies found.") return # ~~~~~~~ 560-big_powers_of_B def big_powers_of_B(): """Searching for the overflow threshold, return the NEGATIVES of the largest powers ofB. With a strategy similar to underflow, use three values in step to get up to and just beyond the overflow limit. Returns: bigger: value that saturates like infinity or triggers an exception big: down a factor of 1/H less_big: down a factor of 1/H from big no_except: boolean indicating no exception triggered Basic 5530-5530 TODO: Resolve that we use negatives because of possible 2's complement representation, that could allow the largest magnitude to be negative. """ # ~~~~~~~ 560a-big_powers_of_B2 big = -ONE_OVER_C bigger = ONE_OVER_H * big no_except = True try: while True: less_big = big big = bigger bigger = ONE_OVER_H * big if verbose: print("\tbigger = {:.12e} big = {:.12e}" .format(bigger, big)) if bigger >= big: break # remember, these values < 0 except OverflowError: # Python does not catch floating point overflow no_except = False bigger = big if verbose: print("\tEncountered overflow exception.") return bigger, big, less_big, no_except # ~~~~~~~ 564-no_trap_overflow_threshold def no_trap_overflow_threshold(big, less_big): """ Determine the threshold when there is no trap on overflow. Typically, the search for the threshold will result in bigger_B and big_B pinned at Infinity or some huge finite value. Value less_big_B is guaranteed to be a finite, unexceptional value. This routine either returns big_B as a finite saturating value or fills out less_big_B with significant digits to reach right up to big_B. Args: big: power of 1/H that may be infinite or saturating finite value less_big: tame value a nominal factor of H smaller than big Returns: oflo: the overflow threshold """ # ~~~~~~~ 564a-no_trap_overflow_threshold2 oflo = -less_big # default start pos_big = -big y = less_big * (ONE_OVER_H * ULP_OF_ONE_PLUS - ONE_OVER_H) z = y + ((ONE - ONE_OVER_H) * ULP_OF_ONE_PLUS) * less_big if z < pos_big: oflo = z if verbose: print("Try full-precision overflow threshold.") elif y < pos_big: oflo = y if verbose: print("Try reduced precision overflow threshold.") if pos_big - oflo < pos_big: oflo = pos_big if verbose: print("Finite saturating power of B overflow threshold.") return oflo # ~~~~~~~ 568-trap_overflow_threshold def trap_overflow_threshold(big): """Determine the threshold when overflow traps. The difference from the non-trap case is that the middle value, big, returned from the saerch is the greatest power of the radix, so the task is to fill it out to max value without increasing the exponent. Argunment: big: biggest finite power of the radix Returns: oflo: overflow threshold """ oflo = big * (ONE_OVER_H * ULP_OF_ONE_PLUS - ONE_OVER_H) oflo = oflo + ((ONE - ONE_OVER_H) * ULP_OF_ONE_PLUS) * big return oflo # ~~~~~~~ 572-test_sqrt_tiny def test_sqrt_tiny(tlist): """Check sqrt(x)**2 for values near the underflow threshold.""" for z in tlist: if z != ZERO: sqrt_z = sqrt(z) y = sqrt_z * sqrt_z if ((y / (ONE - B * SAFE_ULPS_OF_ONE) < z) or y > (ONE + B * SAFE_ULPS_OF_ONE) * z): if sqrt_z > ULP_OF_ONE_MINUS: bad_cond(err_serious, "") else: bad_cond(err_defect, "") print("Comparison alleges that what prints as z = {:0.17e}" .format(z)) print(" is too far from sqrt(z) ^ 2 = {:0.17e} .".format(y)) return # ~~~~~~~ 576-test_sqrt_big def test_sqrt_big(tlist): """Check sqrt(x)**2 for values near the overflow threshold.""" for z in tlist: sqrt_z = sqrt(z) x = (ONE - B * SAFE_ULPS_OF_ONE) * sqrt_z y = sqrt_z * x if ((y < (ONE - TWO * B * SAFE_ULPS_OF_ONE) * z) or (y > z)): if x < BIG_B_NTH: bad_cond(err_serious, "") else: bad_cond(err_defect, "") print("Comparison alleges that z = {:0.17e}".format(z)) print(" is too far from sqrt(z) ^ 2 ({:0.17e}) .".format(y)) # ~~~~~~~ 580-check_range_balance def check_range_balance(): """Check balnce of overflow and underflow thresholds.""" x = underflow_threshold * overflow_threshold y = B * B if verbose: print("\tThe product of the overflow and" + "underflow thresholds is {:.4e}".format(x)) if x * y < ONE or x > y: if x * y < ULP_OF_ONE_MINUS or x > y / ULP_OF_ONE_MINUS: bad_cond(err_defect, "Badly") else: bad_cond(err_flaw, "") print((" unbalanced range; underflow_threshold * overflow_threshold" + "= {:0.17e}\n\tis too far from 1.").format(x)) return # ~~~~~~~ 584-test_unit_ratios def test_unit_ratios(tlist): """Try x/x for interesting values.""" for x in tlist: y = x # Beware values near over/underflow thresholds. try: diff = (y / x - ONE_HALF) - ONE_HALF except ZeroDivisionError: print(" x / x traps when x = {:g}", x) if diff != ZERO: if diff == (-ULP_OF_ONE_MINUS and (fabs(x) < 1 or fabs(x) > B)): bad_cond(err_flaw, "") else: bad_cond(err_serious, "") print(" x / x differs from 1 when x = {:0.17e}", x) print(" instead, x / x - 1/2 - 1/2 = {:0.17e} .", diff) return # ~~~~~~~ 588-test_div_by_zero def test_div_by_zero(): """Try 1/0 and 0/0.""" my_zero = ZERO print("") print("What message and/or values does Division by ZERO produce?") print("This can interupt your program. You can ", end="") print("skip this part if you wish.") s = alt_input("Do you wish to compute 1 / 0?\n") if s.startswith("N") or s.startswith("n"): print("O.K. skipping") else: print("\tTrying to compute 1 / 0 produces ...") try: print(" \t{:0.7e} .".format(ONE / my_zero)) except ZeroDivisionError: print("\tComputing 1 / 0 triggered an exception.") s = alt_input("\nDo you wish to compute 0 / 0?\n") if s.startswith("N") or s.startswith("n"): print("O.K. skipping") else: print("\tTrying to compute 0 / 0 produces ...") try: print("\t{:0.7e} .", ZERO / my_zero) except ZeroDivisionError: print("\tComputing 0 / 0 triggered an exception.") return # ~~~~~~~ 700-start_execution # PARANOIA EXECUTION BEGINS verbose |= ultra_verbose # if it's ultra, it's verbose, too # Small floating point constants built from coerced integers. ZERO = float(0) ONE = float(1) TWO = ONE + ONE THREE = TWO + ONE FOUR = THREE + ONE FIVE = FOUR + ONE EIGHT = FOUR + FOUR NINE = THREE * THREE TWENTY_SEVEN = NINE * THREE THIRTY_TWO = FOUR * EIGHT TWOFORTY = FOUR * FIVE * THREE * FOUR MINUS_ONE = -ONE ONE_HALF = ONE / TWO ONE_AND_HALF = ONE + ONE_HALF # ~~~~~~~ 710-start_messages milestone = 0 # ============================== instructions() pause() heading() pause() characteristics() pause() history() pause() # ~~~~~~~ 715-zero_one milestone = 7 # ============================== # Basic 880-950 print("Program is now RUNNING tests on small integers:") test_cond(err_failure, (ZERO + ZERO == ZERO) and (ONE - ONE == ZERO) and (ONE > ZERO) and (ONE + ONE == TWO), "0+0 != 0, 1-1 != 0, 1 <= 0, or 1+1 != 2") # ~~~~~~~ 717-zero_one2 z = -ZERO if (z != 0.0): error_count[err_failure] += 1 print("Comparison alleges that Minus Zero, obtained by setting") print(" X = 0 and then Z = -X , is nonzero!") # The next two lines artificially set two global constants for the test. ULP_OF_ONE_PLUS = 0.001 B = 1 discard = does_tiny_value_misbehave(z) # ~~~~~~~ 720-small_ints test_cond(err_failure, (THREE == TWO + ONE) and (FOUR == THREE + ONE) and (FOUR + TWO * (-TWO) == ZERO) and (FOUR - THREE - ONE == ZERO), "3 != 2+1, 4 != 3+1, 4+2*(-2) != 0, or 4-3-1 != 0") test_cond(err_failure, (MINUS_ONE == (0 - ONE)) and (MINUS_ONE + ONE == ZERO) and (ONE + MINUS_ONE == ZERO) and (MINUS_ONE + fabs(MINUS_ONE) == ZERO) and (MINUS_ONE + MINUS_ONE * MINUS_ONE == ZERO), "-1 != 0-1, -1+1 != 0, 1+(-1) != 0, " + "(-1)+abs(-1) != 0, or -1+(-1)*(-1) != 0") test_cond(err_failure, ONE_HALF + MINUS_ONE + ONE_HALF == ZERO, "1/2 + (-1) + 1/2 != 0") # ~~~~~~~ 725-med_ints milestone = 10 # ============================== test_cond(err_failure, (NINE == THREE * THREE) and (TWENTY_SEVEN == NINE * THREE) and (EIGHT == FOUR + FOUR) and (THIRTY_TWO == EIGHT * FOUR) and (THIRTY_TWO - TWENTY_SEVEN - FOUR - ONE == ZERO), "9 != 3*3, 27 != 9*3, 32 != 8*4, or 32-27-4-1 != 0") test_cond(err_failure, (FIVE == FOUR + ONE) and (TWOFORTY == FOUR * FIVE * THREE * FOUR) and (TWOFORTY / THREE - FOUR * FOUR * FIVE == ZERO) and (TWOFORTY / FOUR - FIVE * THREE * FOUR == ZERO) and (TWOFORTY / FIVE - FOUR * THREE * FOUR == ZERO), "5 != 4+1, 240 != 4*5*3*4, 240/3 != 80, 240/4 != 60, or 240/5 != 48") if (error_count[err_failure] == 0): print("-1, 0, 1/2, 1, 2, 3, 4, 5, 9, 27, 32 & 240 are O.K.") # ~~~~~~~ 730-radix # Basic 1160-1230 print("Searching for radix B and PRECISION.") big_2_to_nth = find_big_2_to_nth() B = find_radix_from_big_2_to_nth(big_2_to_nth) if B < TWO: # TODO: log case B = ONE print("B = {:0.4f} .\n".format(B)) # ~~~~~~~ 735-precision # Compute PRECISION and BIG_B_NTH = B**PRECISION barely too big # to satisfy (BIG_B_NTH + 1) - BIG_B_NTH == 1 . # Basic 1240-1270 if B == ONE: # defer to the secondary calculation below PRECISION = ZERO BIG_B_NTH = big_2_nth else: PRECISION, BIG_B_NTH = find_precision_big_B_to_nth(B) # ~~~~~~~ 737-precision2 guess_ulp_of_one_minus = ONE / BIG_B_NTH guess_ulp_of_one_plus = B * guess_ulp_of_one_minus print("Closest relative separation found is ULP_OF_ONE_MINUS = {:0.7e} ." .format(guess_ulp_of_one_minus)) # ~~~~~~~ 740-recalc_radix print("Recalculating radix and precision") ULP_OF_ONE_PLUS = find_ulp_of_one_plus(guess_ulp_of_one_plus) ULP_OF_ONE_MINUS = find_ulp_of_one_minus(guess_ulp_of_one_minus) if (ULP_OF_ONE_MINUS == guess_ulp_of_one_minus): print("confirms closest relative separation ULP_OF_ONE_MINUS ") else: print("gets better closest relative separation ULP_OF_ONE_MINUS = " + "{:0.7e} .".format(ULP_OF_ONE_MINUS)) # ~~~~~~~ 742-recalc_radix2 BIG_B_NTH = ONE / ULP_OF_ONE_MINUS ONE_MINUS_ULP = (ONE_HALF - ULP_OF_ONE_MINUS) + ONE_HALF save_b = B B = floor(0.01 + ULP_OF_ONE_PLUS / ULP_OF_ONE_MINUS) B_OVER_TWO = B / TWO ONE_OVER_B = ONE / B if (B == save_b): print("B confirmed.") else: print("MYSTERY: recalculated B = {:0.7e} .".format(B)) test_cond(err_defect, B <= EIGHT + EIGHT, "B is too big: roundoff problems") test_cond(err_flaw, (B == TWO) or (B == 10) or (B == ONE), "B is not as good as 2 or 10") # ~~~~~~~ 745-cmp_ulps_of_one milestone = 20 # ============================== test_cond(err_failure, ONE_MINUS_ULP - ONE_HALF < ONE_HALF, "(1-ULP_OF_ONE_MINUS)-1/2 < 1/2 is FALSE, prog. fails?") # BUG in C version. The point is to try two cases of x. for x in [ONE_MINUS_ULP, ONE + ULP_OF_ONE_PLUS]: y = x - ONE_HALF z = y - ONE_HALF test_cond(err_failure, (x != ONE) or (z == ZERO), "Comparison is fuzzy,x=1 but x-1/2-1/2 != 0") # ~~~~~~~ 750-the_constants milestone = 25 # ============================== # ... B_MINUS_ULP = nextafter(B, 0) B_MINUS_ULP = B - ONE B_MINUS_ULP = (B_MINUS_ULP - ULP_OF_ONE_PLUS) + ONE # A = 2 if B = 2**k or arithmetic is logarithmic; it's 10 if B=10 # C = 1/B**k not too close to underflow, useful for over/underflow analysis. # H = min(1/B, 1/2). A, ONE_OVER_A = compute_A_and_inverse(B) C, ONE_OVER_C = compute_C_and_inverse() H, ONE_OVER_H = compute_H_and_inverse() SAFE_ULPS_OF_ONE = compute_safe_ulps_of_one() # 1 or B ulps of one # ~~~~~~~ 755-frac_prec # Purify Integers was the original comment. The following test assumes # B=1 for log arithmetic, before any fractional part of the precision # is "purified". if (B != ONE): PRECISION = standardize_fractional_precision(ULP_OF_ONE_MINUS, B) if (PRECISION != floor(PRECISION)) or (B == ONE): print("PRECISION cannot be characterized by an Integer number") print("of significant digits but, by itself, this is a minor flaw.") if B == ONE: print("logarithmic encoding has precision characterized solely " + "by ULP_OF_ONE_MINUS.") else: print("The number of significant digits of the B is {:0.4f} ." .format(PRECISION)) # ~~~~~~~ 760-check_prec test_cond(err_serious, ULP_OF_ONE_PLUS * NINE * NINE * TWOFORTY < ONE, "PRECISION worse than 5 decimal figures ") # ~~~~~~~ 765-first_prec_tests milestone = 30 # ============================== test_for_extra_precise_subepressions() pause() milestone = 35 # ============================== test_normalized_subtraction() # ~~~~~~~ 770-guard_digit flags["mult_guard_digit"] = does_mult_have_guard_digit() test_mult_low_digits() flags["div_guard_digit"] = does_div_have_guard_digit() flags["add_sub_guard_digit"] = does_add_sub_have_guard_digit() # ~~~~~~~ 772-guard2 if (ONE_MINUS_ULP != ONE and ONE_MINUS_ULP - ONE >= ZERO): bad_cond(err_serious, "comparison alleges (1-ULP_OF_ONE_MINUS) < 1 although") print(" subtraction yields (1-ULP_OF_ONE_MINUS) - 1 = 0 , thereby vitiating") print(" such precautions against division by zero as") print(" ... if (x == 1.0) {.....} else {.../(x-1.0)...}") # ~~~~~~~ 774-guard3 if flags["mult_guard_digit"]: print(" * appears to have a guard digit, as it should.") if flags["div_guard_digit"]: print(" / appears to have a guard digit, as it should.") if flags["add_sub_guard_digit"]: print(" - appears to have a guard digit, as it should.") milestone = 40 # ============================== pause() # ~~~~~~~ 775-rounding print("Checking rounding on multiply, divide and add/subtract. ") flags["mult_rounding"] = how_does_mult_round() milestone = 45 # ============================== flags["div_rounding"] = how_does_div_round() # ~~~~~~~ 777-rounding2 test_cond(err_failure, ONE_OVER_B * B - ONE_HALF == ONE_HALF, "B * ( 1 / B ) differs from 1") # ~~~~~~~ 779-rounding3 milestone = 50 # ============================== test_cond(err_failure, (ONE_MINUS_ULP + ULP_OF_ONE_MINUS) - ONE_HALF == ONE_HALF and (B_MINUS_ULP + ULP_OF_ONE_PLUS) - ONE == B - ONE, "Incomplete carry-propagation in Addition") # ~~~~~~~ 782-rounding4 flags["add_sub_rounding"] = how_does_add_sub_round() # ~~~~~~~ 785-sticky # The boolean set_sticky is used to indicate the presence of a sticky bit # below. It is cleared if the first simple test fails. set_sticky = rounding_symmetry_test() # ~~~~~~~ 790-sticky2 if (flags["mult_guard_digit"] and flags["div_guard_digit"] and flags["add_sub_guard_digit"] and flags["mult_rounding"] == "rounded" and flags["div_rounding"] == "rounded" and flags["add_sub_rounding"] == "rounded" and floor(B_OVER_TWO) == B_OVER_TWO): print("Checking for sticky bit.") set_sticky &= has_sticky_bit() flags["uses_sticky_bit"] = set_sticky # ~~~~~~~ 795-round_summary if flags["uses_sticky_bit"]: print("Sticky bit apparently used correctly.") else: print("Sticky bit used incorrectly or not at all.") test_cond(err_flaw, (flags["mult_guard_digit"] and flags["div_guard_digit"] and flags["add_sub_guard_digit"] and flags["mult_rounding"] != "other" and flags["div_rounding"] != "other" and flags["add_sub_rounding"] != "other"), "lack(s) of guard digits or failure(s) to correctly round or chop\n" + "(noted above) count as one flaw in the final tally below") # ~~~~~~~ 800-mul_cmmute milestone = 60 # ============================== print("") print("Does Multiplication commute? ", end="") print("Testing on {:d} random pairs.".format(NUM_TRIALS)) test_mult_commutivity(NUM_TRIALS) # ~~~~~~~ 805-sqrt milestone = 70 # ============================== print("\nRunning test of square root(x).") test_cond(err_failure, (ZERO == sqrt(ZERO)) and (- ZERO == sqrt(- ZERO)) and (ONE == sqrt(ONE)), "Square root of 0.0, -0.0 or 1.0 wrong") min_sqrt_error = ZERO # the global sqrt err bounds max_sqrt_error = ZERO ok = sqrt_single_bound_test([ (B, ULP_OF_ONE_PLUS, err_serious), (ONE_OVER_B, ONE_OVER_B * ULP_OF_ONE_MINUS, err_serious), (BIG_B_NTH, ONE, err_serious), # BUG: Missing from C (ULP_OF_ONE_MINUS, ULP_OF_ONE_MINUS * ULP_OF_ONE_MINUS, err_serious), ]) if not ok: pause() # ~~~~~~~ 810-sqrt_int print("Testing if sqrt(x * x) == x for {:d} Integers x.".format(NUM_TRIALS)) test_sqrt_int() print("Test for sqrt monotonicity.") test_sqrt_monotonicity() milestone = 80 # ============================== if verbose: print("\tAfter monotonicity test:") print("\t\tmin_sqrt_error = {:0.6e}".format(min_sqrt_error)) print("\t\tmax_sqrt_error = {:0.6e}".format(max_sqrt_error)) # ~~~~~~~ 810a-sqrt_int1 print("Test sqrt for accuracy.") # TODO: explain this bump - scaling? min_sqrt_error = min_sqrt_error + ONE_HALF max_sqrt_error = max_sqrt_error - ONE_HALF y = (sqrt(ONE + ULP_OF_ONE_PLUS) - ONE) / ULP_OF_ONE_PLUS up_err = (y - ONE) + ULP_OF_ONE_PLUS / EIGHT down_err = y + ULP_OF_ONE_PLUS / EIGHT update_sqrt_errors(down_err, up_err) if verbose: print("\tAfter 1 + ulp test:") print("\t\tmin_sqrt_error = {:0.17e}".format(min_sqrt_error)) print("\t\tmax_sqrt_error = {:0.17e}".format(max_sqrt_error)) # ~~~~~~~ 810b-sqrt_int2 y = (((sqrt(ONE_MINUS_ULP) - ULP_OF_ONE_PLUS) - (ONE - ULP_OF_ONE_PLUS)) / ULP_OF_ONE_MINUS) if verbose: print("sqrt(1-ulp) = {:0.17e}".format(sqrt(ONE_MINUS_ULP))) print("y = {:0.17e}".format(y)) up_err = y + ULP_OF_ONE_MINUS / EIGHT down_err = (y + ONE) + ULP_OF_ONE_MINUS / EIGHT update_sqrt_errors(down_err, up_err) if verbose: print("\tAfter 1 - ulp test:") print("\t\tmin_sqrt_error = {:0.17e}".format(min_sqrt_error)) print("\t\tmax_sqrt_error = {:0.17e}".format(max_sqrt_error)) # ~~~~~~~ 810c-sqrt_int3 ulp = ULP_OF_ONE_PLUS x = ulp for (x, ulp) in [(ULP_OF_ONE_PLUS, ULP_OF_ONE_PLUS), (ULP_OF_ONE_PLUS * floor(EIGHT / (NINE * sqrt(ULP_OF_ONE_PLUS))), ULP_OF_ONE_PLUS), (-ULP_OF_ONE_MINUS, ULP_OF_ONE_MINUS)]: if verbose: print("\t3-term sqrt loop: x = {:.17e} ulp = {:.17e}".format(x, ulp)) y = sqrt((x + ULP_OF_ONE_MINUS + x) + ONE_MINUS_ULP) y = ((y - ULP_OF_ONE_PLUS) - ((ONE - ULP_OF_ONE_PLUS) + x)) / ulp z = ((ULP_OF_ONE_MINUS - x) + ONE_MINUS_ULP) * ONE_HALF * x * x / ulp down_err = (y + ONE_HALF) + z up_err = (y - ONE_HALF) + z update_sqrt_errors(down_err, up_err) if verbose: print("\tAfter ulp of 1+ cases") print("\t\tmin_sqrt_error = {:0.17e}".format(min_sqrt_error)) print("\t\tmax_sqrt_error = {:0.17e}".format(max_sqrt_error)) # ~~~~~~~ 815-sqrt_num_theory milestone = 85 # ============================== sqrt_rnd_odd = False sqrt_anomalous_int = False tests = 0 flags["sqrt_rounding"] = "other" while B != ONE: print("Testing whether sqrt is rounded or chopped.") D = floor(ONE_HALF + pow(B, ONE + PRECISION - floor(PRECISION))) # ... == B^(1 + fract) if (PRECISION == Integer + fract. x = D / B y = D / A if x != floor(x) or y != floor(y): sqrt_anomalous_int = True break # ~~~~~~~ 815c-sqrt_num_theory3 x = ZERO z2 = x y = ONE y2 = y # **** B = 8 #********************************************** # *** D = 16 #********************************************** z1 = B - ONE FourD = FOUR * D # ~~~~~~~ 815f-sqrt_num_theory6 while True: if (y2 > z2): Q = B y1 = y while True: x1 = fabs(Q + floor(ONE_HALF - Q / y1) * y1) Q = y1 y1 = x1 if ultra_verbose: print("\t\t\tloop inner x1 = {:.6e} Q = {:.6e} y1 = {:.6e}" .format(x1, Q, y1)) if x1 <= ZERO: break if (Q <= ONE): z2 = y2 z = y # ~~~~~~~ 815i-sqrt_num_theory9 y = y + TWO x = x + EIGHT y2 = y2 + x if y2 >= FourD: y2 = y2 - FourD if ultra_verbose: print("\t\t\tloop x = {:.6e} x1 = {:.6e}".format(x, x1)) print("\t\t\t y = {:.6e} y1 = {:.6e} y2 = {:.6e}".format(y, y1, y2)) print("\t\t\t z = {:.6e} z1 = {:.6e} z2 = {:.6e}".format(z, z1, z2)) if y >= D: break # ~~~~~~~ 815l-sqrt_num_theory12 X8 = FourD - z2 # ***JTC: fix global Q used by num_theory_sqrt_value Q = (X8 + z * z) / FourD X8 = X8 / EIGHT if ultra_verbose: print("Loop finds Q = {:.17e} X8 = {:.17e}".format(Q, X8)) if Q != floor(Q): sqrt_anomalous_int = True break # ~~~~~~~ 815n-sqrt_num_theory14 while True: x = z1 * z x = x - floor(x / B) * B if x != ONE: z1 = z1 - ONE if x == ONE or z1 <= ZERO: break if z1 <= ZERO and x != ONE: sqrt_anomalous_int = True break # ~~~~~~~ 815p-sqrt_num_theory16 if z1 > B_OVER_TWO: z1 = z1 - B while True: x, D, Q, z, z1 = num_theory_sqrt_value(x, D, Q, z, z1) if ULP_OF_ONE_PLUS * D >= ONE_MINUS_ULP: break if D * B - D != BIG_B_NTH - D: sqrt_anomalous_int = True break # ~~~~~~~ 815r-sqrt_num_theory18 z2 = D y = D + (ONE + z) * ONE_HALF x = D + z + Q tests += num_theory_sqrt_test(x, D, y, z2) y = D + (ONE - z) * ONE_HALF + D x = D - z + D x = x + Q + x tests += num_theory_sqrt_test(x, D, y, z2) x, D, Q, z, z1 = num_theory_sqrt_value(x, D, Q, z, z1) if D - z2 != BIG_B_NTH - z2: sqrt_anomalous_int = True break # ~~~~~~~ 815t-sqrt_num_theory20 y = (D - z2) + (z2 + (ONE - z) * ONE_HALF) x = (D - z2) + (z2 - z + Q) tests += num_theory_sqrt_test(x, D, y, z2) y = (ONE + z) * ONE_HALF x = Q tests += num_theory_sqrt_test(x, D, y, z2) break # exit ficttitious loop in one pass # ~~~~~~~ 815u-sqrt_num_theory22 if verbose: print("\tAfter all sqrt test computations:") print("\t\tmin_sqrt_error = {:0.17e}".format(min_sqrt_error)) print("\t\tmax_sqrt_error = {:0.17e}".format(max_sqrt_error)) if tests == 0 or sqrt_anomalous_int: bad_cond(err_failure, "Anomalous arithmetic with Integer < ") print("B^PRECISION = {:0.7e}".format(BIG_B_NTH)) print(" fails test whether sqrt rounds or chops.") sqrt_rnd_odd = True else: if min_sqrt_error >= ZERO and max_sqrt_error <= ZERO: flags["sqrt_rounding"] = "rounded" print("Square root appears to be correctly rounded.") else: if (max_sqrt_error + ULP_OF_ONE_PLUS > ULP_OF_ONE_PLUS - ONE_HALF or min_sqrt_error > ONE_HALF or min_sqrt_error + B < ONE_HALF): sqrt_rnd_odd = True else: flags["sqrt_rounding"] = "chopped" print("Square root appears to be chopped.") # ~~~~~~~ 815w-sqrt_num_theory24 if sqrt_rnd_odd: print("Square root is neither chopped nor correctly rounded.") print("Observed errors run from {:0.7e} " .format(min_sqrt_error - ONE_HALF), end="") print("to {:0.7e} ulps.".format(ONE_HALF + max_sqrt_error)) test_cond(err_serious, max_sqrt_error - min_sqrt_error < B * B, "sqrt gets too many last digits wrong") # ~~~~~~~ 820-pow_small_int milestone = 90 # ============================== pause() print("Testing powers z^i for small Integers z and i.") pow_err_cnt = 0 # ... test powers of zero. # If(-1)^k is invalid, replace MINUS_ONE by ONE. for (z, i) in [(-ZERO, 0), (MINUS_ONE, -4)]: x = ONE test_integer_powers(x, z, 0, 3) # For higher odd powers z is the starting value of x. test_integer_powers(z, z, 1023, 1026) print_if_err_cnt_positive() save_pow_err_cnt = pow_err_cnt pow_err_cnt = 0 m = int(floor(TWO * log(BIG_B_NTH) / log(A))) for z in [A, ONE_OVER_A]: # Executes 2x if A = 1/A = 1 test_integer_powers(z, z, 1, m) # ~~~~~~~ 825-pow_primes milestone = 100 # ============================== # Powers of B have been tested, sonext try a few primes. # The simpler loop skips the trick of stepping by 2 through the odd # numbers from 3, filtering the further multiples of 3. m = NUM_TRIALS for z in [3, 5, 7, 11, 13, 17, 19, 23]: test_integer_powers(z, z, 1, m) if pow_err_cnt > 0: print("Errors like this may invalidate financial calculations") print("\tinvolving interest rates.") print_if_err_cnt_positive() pow_err_cnt += save_pow_err_cnt if pow_err_cnt == 0: print("... no discrepancies found.") if pow_err_cnt > 0: pause() else: print("") # ~~~~~~~ 900-uflow1 milestone = 110 # ============================== # UNDERFLOW AND THE BEHAVIOR OF TINY VALUES # Starts at Basic 4330 print("Seeking three values related to Underflow:") print(" underflow_threshold = smallest well-behaved value") print(" min_positive = smallest nonzero value discovered") print(" too_tiny_x = a value underflowed to zero or a pseudo-zero") # First step: compute tiny powers of the radix. H and C are two useful # constants. # H = min(1/B, 1/2); ONE_OVER_H = max(B, 2) # C = 1/B**k, not too close to the underflow thereshold; ONE_OVER_C = B**k less_tiny_B, tiny_B, too_tiny_B = tiny_powers_of_B(C) # ~~~~~~~ 900a-uflow1a # Constant SAFE_ULPS_OF_ONE = B OR 1 * ULP_OF_ONE_PLUS should have # enough units in the last place of 1 to leave something nonzero # in multiplication, even if there's no guard digit. one_plus_safe = ONE + SAFE_ULPS_OF_ONE # Compute a rich set of values analogous to the tiniest powers of B, but with # nonzero low-order bits and attention to the differences of small values. (less_tiny_x, tiny_x, too_tiny_x, underflow_threshold, add_sub_tiny, add_sub_hi, add_sub_lo) = tiny_values_and_difference(one_plus_safe) print("***less_tiny_x, tiny_x, too_tiny_x = {:.7e} {:.7e} {:.7e}" .format(less_tiny_x, tiny_x, too_tiny_x)) print("***underflow_threshold, add_sub_tiny, add_sub_hi = {:.7e} {:.7e} {:.7e}" .format(underflow_threshold, add_sub_tiny, add_sub_hi)) # ~~~~~~~ 902-uflow2 # The two sets of tiny values characterize the underflow behavior. # The following comments mirror BASIC comments 4530-4560. The relational ">~" # may be loosely read "greater than or kind of equal to". It refers to the odd # behavior of some arithmetics near 0, where some "pseudo zero" values behave # like zero in some contexts and nonzero in others. # # 1 >> C = 1/B^k >= less_tiny_B > tiny_B >~ too_tiny_B >~ 0 # where the tiny values differ by a factor of H # # 1 >> d = (1+SAFE_ULPS_OF_ONE)*C >= underflow_threshold # >= add_sub_hi >= less_tiny_x > tiny_x >~ too_tiny_x >~ 0 # where underflow_threshold = d * H^k is the first NONZERO value to # violate (x*H)/H = x else underflow_threshold = 0 # and where add_sub_hi = underflow_threshold * H^k is the first to satisfy # add_sub_tiny <- |(x*H)/H - x| > 0 else add_sub_hi = less_tiny_x test_for_pseudo_zero(too_tiny_x) # ~~~~~~~ 903-uflow3 milestone = 120 # ============================== # less_tiny_B and less_tiny_x result from similar loops, one starting # with C, the other with (1 + SAFE_ULPS_OF_ONE) * C. # Check to see who is the tiniest of them all, but scale up # to avoid faulty comparisons as on CDC 6000. # If the number encoding is such that what "looks like" the smallest power # of the radix is interpreted as zero, then the smallest nonzero values # will not be power of B. This test catches that case. The value h_fact # records that the smallest power of B is too big by nearly a factor of H. if ONE_OVER_C * less_tiny_B > ONE_OVER_C * less_tiny_x: h_fact = H min_positive = tiny_x else: h_fact = ONE min_positive = tiny_B # ~~~~~~~ 904-uflow4 # Compared here are min_positive, the smallest nonzero value # gotten by successively multiplying by H, and add_sub_tiny, # which is |x - (x*H)/H| for a tiny x. if (add_sub_tiny != ZERO) and (add_sub_tiny != min_positive): bad_cond(err_defect, "") if (add_sub_tiny < min_positive): print("Products underflow at a higher", end="") print(" threshold than differences.") if too_tiny_x == ZERO: # Only if there are NO pseudo-zeros. # Don't admit add_sub_tiny if it could be a pseudo-zero. min_positive = add_sub_tiny else: print("Difference underflows at a higher", end="") print(" threshold than products.") print("Smallest strictly positive number found is min_positive = {:g} ." .format(min_positive)) # ~~~~~~~ 905-uflow5 # Now consolidate the three triples of tiny results into the values # from the loop computation. Break into several cases... # This defines base_tiny, which may be needed as a final result in some # anomalous arithmetics. if does_tiny_value_misbehave(min_positive): base_tiny = less_tiny_B # Back up by a factor of 1/H, for CDC 7600 else: base_tiny = min_positive # ~~~~~~~ 906-uflow6 # The Basic code switches on four cases depending on whether two values are # zero or nonzero. # add_sub_tiny: the difference between two tiny unequal values may # underflow to zero # underflow_threshold: intended to be the smallest normal number # unaffected by underflow, but will not have been set yet for # arithmetic flushes underflow abruptly to zero # # -----------CASES---- 4 3 2 1 # add_sub_tiny non0 non0 0 0 # underflow_threshold non0 0 non0 0 # # In high-quality arithmetic, both values are nonzero, so it's just a matter # of checking for gradual underflow and its quality. # If on the other hand, either value is zero, a specific action is taken and # then all three cases fall into an explicit check for differences of zero # arising from unequal values. # ~~~~~~~ 907-uflow7 if add_sub_tiny and underflow_threshold: # BASIC case 4 if ((add_sub_hi == underflow_threshold) and (add_sub_tiny == min_positive) and (fabs(underflow_threshold - add_sub_tiny / SAFE_ULPS_OF_ONE) <= add_sub_tiny)): flags["IEEE"] = is_gradual_underflow_IEEE(min_positive) # ~~~~~~~ 908-uflow8 else: # BASIC cases 1-3. Case 3 falls through to a final test. if underflow_threshold == ZERO: # Cases 1-2 if add_sub_tiny: # Case 2 underflow_threshold = add_sub_hi # last good behavior bad_cond(err_failure, "Underflow confuses Comparison, which alleges that") print("add_sub_hi == add_sub_lo while denying that |add_sub_hi" + " - add_sub_lo| == 0; these values") print("print out as add_sub_hi = {:0.17e}, " + "add_sub_lo ={:0.17e} .".format(add_sub_hi, add_sub_lo)) print("|add_sub_hi - add_sub_lo| = {:0.17e} ." .format(fabs(add_sub_hi - add_sub_lo))) # BUG: C lacks the following message. print("and add_sub_hi / add_sub_lo| = 1.0 + {:0.17e} ." .format((add_sub_hi / add_sub_lo - ONE_HALF) - ONE_HALF)) # ~~~~~~~ 909-uflow9 else: # Case 1 underflow_threshold = base_tiny # Safe value computed above # A sanity check determines that the result from # tiny_x_and_difference() matches expectation, based on the # loop on powers of B. The factor one_plus_safe accounts for # the difference between the tiny_B and tiny_x loops. # h_fact accounts for the case that what ought to be the # tiniest power of B in the representation is given the value # zero. If these don't match, the threshold is backed up a # factor of B. if ((ONE_OVER_C * add_sub_hi) != ((ONE_OVER_C * less_tiny_B) * one_plus_safe * h_fact)): underflow_threshold = less_tiny_B bad_cond(err_failure, "Either accuracy deteriorates as numbers\n") print("approach a threshold = {:0.17e}" .format(underflow_threshold)) print(" coming down from {:0.17e}".format(C)) print(" or else multiplication gets too many ", end="") print("last digits wrong.") pause() # Cases 1-3 fall through to a final test for zero differences. test_tiny_differences() # ~~~~~~~ 910-uflow10 print("The Underflow threshold is {:0.17e}, below which" .format(underflow_threshold)) print("calculation may suffer larger Relative error than ", end="") print("merely roundoff.") test_for_narrow_range() milestone = 130 # ============================== test_extreme_underflow(underflow_threshold) # ~~~~~~~ 930-exp2 milestone = 140 # ============================== print("") # ...calculate exp2 == exp(2) == 7.389056099... exp2 = e_squared() test_x_to_xp1_over_xm1(exp2) # ~~~~~~~ 932-z_to_q milestone = 150 # ============================== # Test z^q at 4 nearly extreme values. extreme_z_to_q() print("") # ~~~~~~~ 934-oflow1 milestone = 160 # ============================== pause() print("Searching for Overflow threshold:") print("This may generate an error.") bigger_B, big_B, less_big_B, no_oflow_except = big_powers_of_B() # In a system that saturates at INFINITY, only less_big_B will be finite. overflow_threshold = less_big_B # first approxomation, to be refined print("Can z = -y overflow?") print("Trying it on big_B = {:0.17e} .".format(big_B)) v = -big_B pos_big_B = v if overflow_threshold - big_B == overflow_threshold + pos_big_B: print("Seems O.K.") else: print("finds a ") bad_cond(err_flaw, "-(-big_B) differs from big_B.") if bigger_B != big_B: bad_cond(err_serious, "") print("overflow past {:0.17e}\n\tshrinks to {:0.17e} ." .format(big_B, bigger_B)) # ~~~~~~~ 938-oflow2 if no_oflow_except: overflow_threshold = no_trap_overflow_threshold(big_B, less_big_B) else: overflow_threshold = trap_overflow_threshold(big_B) print("Computed overflow_threshold = {:0.17e} .".format(overflow_threshold)) if no_oflow_except: print("Overflow saturates at pos_big_B = {:0.17e} .".format(pos_big_B)) else: print("There is no saturation value because " + "the system traps on overflow.") # ~~~~~~~ 934a-oflow1a print("----Starting overflow watch.----") print("No Overflow should be signaled for overflow_threshold * 1 = ", end="") v = overflow_threshold * ONE print("{:0.17e}".format(v)) print(" nor for overflow_threshold / 1 = ", end="") v = overflow_threshold / ONE print("{:0.17e} .".format(v)) print("Any overflow signal during this watch is a DEFECT.") print("----End of overflow watch.---") # ~~~~~~~ 938-oflow_comparisions milestone = 170 # ============================== if not (-overflow_threshold < overflow_threshold and -pos_big_B < pos_big_B and -underflow_threshold < overflow_threshold and underflow_threshold < overflow_threshold): bad_cond(err_failure, "Comparisons involving ") print("+-{:.6e}, +-{:.6e}\nand +-{:.6e} are confused by Overflow." .format(overflow_threshold, pos_big_B, underflow_threshold)) # ~~~~~~~ 942-tiny_sqrt_tests milestone = 175 # ============================== print("") test_sqrt_tiny([underflow_threshold, min_positive, too_tiny_x]) milestone = 180 # ============================== # Peel off historical case from the more typical else clause. if B == 2 and PRECISION == 56 and too_tiny_B != ZERO and -ZERO != ZERO: print("FAILURE: Attempts to evaluate SQR(overflow_threshold)") print("in Double Precision n BASIC on the IBM PC display the word") print("`Overflow' and then disable the Keyboard! This is disastrous.") else: test_sqrt_big([overflow_threshold, pos_big_B]) # ~~~~~~~ 950-range_unit_div_zero milestone = 190 # ============================== pause() print("Checking balance of over/underflow thresholds.") check_range_balance() milestone = 200 # ============================== print("Checking x/x for various x, some extreme.") test_unit_ratios([ONE_MINUS_ULP, ONE + ULP_OF_ONE_PLUS, overflow_threshold, underflow_threshold, B]) milestone = 210 # ============================== test_div_by_zero() # ~~~~~~~ 960-final_report milestone = 220 # ============================== pause() print("") msg = ["FAILUREs encountered =", "SERIOUS DEFECTs discovered =", "DEFECTs discovered =", "FLAWs discovered =" ] for i in range(0, 4): if error_count[i]: print("The number of {:>29s} {:d}.".format(msg[i], error_count[i])) # ~~~~~~~ 960a-final_report1 print("") if (error_count[err_failure] or error_count[err_serious] or error_count[err_defect] or error_count[err_flaw]): if (error_count[err_failure] + error_count[err_serious] + error_count[err_defect] == 0) and (error_count[err_flaw] > 0): print("The arithmetic diagnosed seems ", end="") print("Satisfactory though flawed.") # ~~~~~~~ 960b-final_report2 elif ((error_count[err_failure] + error_count[err_serious] == 0) and error_count[err_defect] > 0): print("The arithmetic diagnosed may be Acceptable", end="") print(" despite inconvenient Defects.") # ~~~~~~~ 960c-final_report3 elif error_count[err_failure] or error_count[err_serious]: print("The arithmetic diagnosed has", end="") print(" unacceptable Serious Defects.") if error_count[err_failure]: print("Potentially fatal FAILURE may have spoiled this", end="") print(" program's subsequent diagnoses.") # ~~~~~~~ 960d-final_report4 else: print("No failures, defects nor flaws have been discovered.") if not (flags["mult_rounding"] == "rounded" and flags["div_rounding"] == "rounded" and flags["add_sub_rounding"] == "rounded" and flags["sqrt_rounding"] == "rounded"): print("The arithmetic diagnosed seems Satisfactory.") else: if (flags["uses_sticky_bit"] and (B - TWO) * (B - NINE - ONE) == ZERO): print("Rounding appears to conform to ", end="") print("IEEE standard ", end="") if ((B == TWO) and ((PRECISION - FOUR * THREE * TWO) * (PRECISION - TWENTY_SEVEN - TWENTY_SEVEN + ONE) == ZERO)): print("754", end="") else: print("854", end="") if flags["IEEE"]: print(".") else: print(",\nexcept for possibly Double Rounding", end="") print(" during Gradual Underflow.") print("The arithmetic diagnosed appears to be Excellent!") # ~~~~~~~ 960e-final_report5 if fpecount: print("\nA total of {:d} floating point exceptions were registered." .format(fpecount)) print("END OF TEST.") exit(0)