| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343 |
- """The reviewed test design: one case group per public dmath function.
- Hexadecimal inputs name exact binary64 values, not approximate decimal inputs.
- Every boundary expansion retains its purpose in the emitted case name.
- These groups were designed afresh; no legacy vectors or fingerprints are used.
- """
- from dataclasses import dataclass
- from oracle import bits, value, SIGN, INF
- @dataclass(frozen=True)
- class Case:
- name: str
- x: int
- y: int = 0
- def word(x):
- return bits(float.fromhex(x)) if isinstance(x, str) else bits(float(x))
- def c(name, x, y=0):
- return Case(name, word(x), word(y))
- def edge(name, x, both_signs=False):
- u = word(x)
- assert 0 < u < INF
- rows = [Case(name + suffix, u + offset) for suffix, offset in
- [('_below', -1), ('_at', 0), ('_above', 1)]]
- if both_signs:
- rows += [Case('negative_' + r.name, r.x | SIGN) for r in rows]
- return rows
- def special():
- return [
- Case('positive_zero', 0), Case('negative_zero', SIGN),
- Case('least_subnormal', 1), Case('negative_least_subnormal', SIGN | 1),
- Case('third_subnormal', 3), Case('negative_third_subnormal', SIGN | 3),
- Case('largest_subnormal', 0x000FFFFFFFFFFFFF),
- Case('negative_largest_subnormal', 0x800FFFFFFFFFFFFF),
- Case('least_normal', 0x0010000000000000),
- Case('negative_least_normal', 0x8010000000000000),
- Case('largest_finite', 0x7FEFFFFFFFFFFFFF),
- Case('negative_largest_finite', 0xFFEFFFFFFFFFFFFF),
- Case('positive_infinity', INF), Case('negative_infinity', INF | SIGN),
- Case('quiet_nan_payload', 0x7FF8ABCDEF135790),
- Case('negative_quiet_nan_payload', 0xFFF8ABCDEF135790),
- Case('signaling_nan_payload', 0x7FF0000000010248),
- Case('negative_signaling_nan_payload', 0xFFF0000000010248),
- ]
- def cases_isfinite():
- return special() + [c('ordinary_positive', '0x1.abc123p+37'),
- c('ordinary_negative', '-0x1.2468p-137')]
- def cases_isinf():
- return special() + [Case('exponent_below_infinity', 0x7FE0000000000000),
- Case('first_nan_encoding', INF + 1)]
- def cases_isnan():
- return special() + [Case('all_fraction_bits_set', 0x7FFFFFFFFFFFFFFF),
- Case('negative_all_bits_set', 0xFFFFFFFFFFFFFFFF)]
- def cases_isnormal():
- return special() + edge('normal_transition', '0x1p-1022', True)
- def cases_fabs():
- return special() + [c('negative_fraction', '-0x1.96p-4'),
- c('negative_large_integral', '-0x1.23456789abcdep+89')]
- def cases_copysign():
- rows = []
- for r in special() + [c('ordinary', '0x1.b38p+6')]:
- rows += [Case(r.name + '_positive_sign', r.x, word('0x1p-81')),
- Case(r.name + '_negative_sign', r.x, word('-0x1p+81'))]
- rows += [Case('sign_from_negative_nan', word(7.625), 0xFFF0000000010248),
- Case('sign_from_negative_zero', word(7.625), SIGN),
- Case('sign_from_positive_nan', word(-7.625), 0x7FF8ABCDEF135790)]
- return rows
- def ordering_cases():
- pairs = [(0, SIGN), (SIGN, 0), (SIGN, SIGN), (0, 0),
- (word(-7.625), word(3.1875)), (1, 3),
- (INF, word(11.125)), (INF | SIGN, word(-11.125))]
- rows = [Case('ordered_pair_' + str(i), a, b) for i, (a, b) in enumerate(pairs)]
- for nan in special()[-4:]:
- rows += [Case(nan.name + '_left', nan.x, word(13.25)),
- Case(nan.name + '_right', word(13.25), nan.x),
- Case(nan.name + '_both', nan.x, 0xFFF8ABCDEF135790)]
- return rows
- def cases_fmin():
- return ordering_cases() + [c('equal_negative_numbers', -19.875, -19.875)]
- def cases_fmax():
- return ordering_cases() + [c('equal_positive_numbers', 19.875, 19.875)]
- def rounding_boundaries():
- rows = special()
- for label, x in [('unit', '0x1p+0'), ('half', '0x1p-1'),
- ('word_split', '0x1p+20'), ('signed32', '0x1p+31'),
- ('last_fractional_binade', '0x1p+51'),
- ('integral_binade', '0x1p+52'), ('signed64', '0x1p+63')]:
- rows += edge(label, x, True)
- return rows
- def cases_ceil():
- return rounding_boundaries() + [c('up_from_positive_fraction', 23.0625),
- c('up_from_negative_fraction', -23.0625)]
- def cases_floor():
- return rounding_boundaries() + [c('down_from_positive_fraction', 23.9375),
- c('down_from_negative_fraction', -23.9375)]
- def cases_trunc():
- return rounding_boundaries() + [c('drop_positive_fraction', 37.6875),
- c('drop_negative_fraction', -37.6875)]
- def cases_modf():
- return rounding_boundaries() + [c('split_positive', 129.8125),
- c('split_negative', -129.8125),
- c('exact_negative_integer', -129)]
- def cases_fmod():
- rows = [c('positive_remainder', 53.75, 7.5), c('negative_remainder', -53.75, 7.5),
- c('negative_divisor', 53.75, -7.5), c('both_negative', -53.75, -7.5),
- c('positive_exact_multiple', 52.5, 7.5),
- c('negative_exact_multiple', -52.5, 7.5),
- c('quotient_exceeds_double', '0x1.abcdefp+900', '0x1.7p-901'),
- c('subnormal_remainder', '0x1.0000000000001p-1022', '0x1p-1022'),
- Case('subnormal_division', 29, 7), Case('negative_subnormal_division', SIGN | 29, 7)]
- for r in special():
- rows += [Case(r.name + '_dividend', r.x, word(7.5)),
- Case(r.name + '_divisor', word(-53.75), r.x)]
- return rows
- def cases_sqrt():
- return (special() + [c('exact_square', 206.640625), c('irrational', 13.625),
- c('wide_significand', '0x1.a5c739b18426fp+409')]
- + edge('square_neighbor', '0x1.9d48p+7')
- + edge('normal_subnormal_transition', '0x1p-1022'))
- def cases_cbrt():
- rows = special() + [c('exact_positive_cube', 155.287109375),
- c('exact_negative_cube', -155.287109375),
- c('irrational_positive', 19.375), c('irrational_negative', -19.375)]
- for rem in range(3):
- rows += edge('exponent_class_' + str(rem), '0x1.73p+' + str(600 + rem), True)
- return rows
- def cases_exp():
- rows = special() + [c('moderate_positive', 6.3125), c('moderate_negative', -6.3125),
- c('large_finite_result', 708.375), c('subnormal_result', -738.625)]
- for name, x in [('tiny_shortcut', '0x1p-28'), ('first_reduction', '0x1.62e43p-2'),
- ('second_reduction', '0x1.0a2b2p+0'),
- ('overflow_boundary', '0x1.62e42fefa39efp+9')]:
- rows += edge(name, x, True)
- # The last nonzero result lies at this negative magnitude.
- rows += [Case('underflow_' + r.name, r.x | SIGN)
- for r in edge('threshold', '0x1.74910d52d3051p+9')]
- return rows
- def cases_exp2():
- rows = special() + [c('fractional_positive', 12.3125), c('fractional_negative', -12.3125),
- c('exact_normal_power', -847), c('subnormal_power', -1053)]
- for n in (9, 67, 143, 219):
- rows += edge('table_midpoint_' + str(n), value(word((n + 0.5) / 256)))
- rows += edge('overflow', '0x1p+10')
- rows += [Case('underflow_' + r.name, r.x | SIGN)
- for r in edge('tie', 1075)]
- return rows
- def cases_exp10():
- return (special() + [c('exact_positive_power', 17), c('negative_power', -17),
- c('fractional_positive', 4.1875), c('fractional_negative', -4.1875),
- c('subnormal_result', -319.75)]
- + edge('overflow', '0x1.34413509f79ffp+8'))
- def log_boundaries():
- return (special() + edge('near_one', 1) + edge('normalize', '0x1.6a09ep+0')
- + [c('less_than_one', 0.171875), c('greater_than_one', 23.5625)])
- def cases_log():
- return log_boundaries() + [c('wide_exponent', '0x1.a9bcdef012345p+723')]
- def cases_log2():
- return log_boundaries() + [c('exact_binary_power', '0x1p-817'),
- c('exact_subnormal_power', '0x1p-1053')]
- def cases_log10():
- return log_boundaries() + [c('decimal_power', 10000000),
- c('small_decimal', 0.00000003125)]
- def cases_log_base():
- rows = [c('base_above_one', 19.375, 3.25), c('base_below_one', 19.375, 0.3125),
- c('argument_below_one', 0.3125, 3.25), c('equal_base_and_argument', 3.25, 3.25),
- c('zero_result_sign', 1, 0.3125), c('base_one_positive', 19.375, 1),
- c('base_one_negative', 0.3125, 1), c('both_one', 1, 1),
- c('near_one_pair', '0x1.0000000000003p+0', '0x1.0000000000007p+0')]
- for r in special():
- rows += [Case(r.name + '_argument', r.x, word(3.25)),
- Case(r.name + '_base', word(19.375), r.x)]
- rows += [Case('both_infinite', INF, INF), Case('both_zero', 0, 0)]
- return rows
- def cases_pow():
- rows = [c('positive_fractional', 3.3125, 2.1875),
- c('negative_odd_integer', -3.3125, 7), c('negative_even_integer', -3.3125, 6),
- c('negative_reciprocal', -3.3125, -7), c('negative_fractional_domain', -3.3125, 2.1875),
- c('square_shortcut', '0x1.abcdef1234567p+5', 2),
- c('cube_shortcut', '0x1.abcdef1234567p+5', 3),
- c('fourth_power_shortcut', '0x1.abcdef1234567p+5', 4),
- c('sqrt_shortcut', 23.5625, 0.5), c('reciprocal_shortcut', 23.5625, -1),
- c('near_one_large_positive', '0x1.0000000000003p+0', '0x1p+49'),
- c('near_one_large_negative', '0x1.ffffffffffffbp-1', '-0x1p+49'),
- c('finite_overflow', 7.625, 4096), c('finite_underflow', 7.625, -4096),
- c('negative_odd_overflow', -7.625, 4097), c('negative_odd_underflow', -7.625, -4097),
- c('largest_odd_exponent', -1, '0x1.fffffffffffffp+52'),
- c('first_even_only_binade', -1, '0x1p+53')]
- for r in special():
- rows += [Case(r.name + '_odd_power', r.x, word(7)),
- Case(r.name + '_negative_odd_power', r.x, word(-7)),
- Case(r.name + '_zero_power', r.x, 0),
- Case(r.name + '_exponent', word(3.3125), r.x)]
- rows += [Case('one_to_nan', word(1), 0xFFF0000000010248),
- Case('negative_one_to_infinity', word(-1), INF),
- Case('negative_zero_fractional_pole', SIGN, word(-0.25))]
- return rows
- def angle_cases():
- rows = special() + [c('small_positive_angle', 0.34375), c('small_negative_angle', -0.34375),
- c('moderate_positive_angle', 43.8125), c('moderate_negative_angle', -43.8125),
- c('large_reduction', '0x1.a5c739b18426fp+41'),
- c('very_large_reduction', '0x1.73b4a82cf19dep+811')]
- rows += edge('quarter_turn_kernel', '0x1.921fb54442d18p-1', True)
- rows += edge('medium_reduction_cutoff', '0x1.921fbp+20', True)
- return rows
- def cases_sin():
- return angle_cases() + edge('tiny_cutoff', '0x1p-26', True)
- def cases_cos():
- return angle_cases() + edge('tiny_cutoff', '0x1.6a09ep-27', True)
- def cases_tan():
- return (angle_cases() + edge('reciprocal_pole', '0x1.2d97c7f3321d2p+2', True)
- + edge('kernel_transform', '0x1.59428p-1', True))
- def cases_sincos():
- return angle_cases() + [c('second_quadrant', 2.3125), c('third_quadrant', 4.1875),
- c('fourth_quadrant', 5.9375)]
- def cases_asin():
- return (special() + edge('domain_endpoint', 1, True) + edge('half_interval', 0.5, True)
- + edge('near_endpoint_formula', '0x1.f3333p-1', True)
- + [c('interior_positive', 0.71875), c('interior_negative', -0.71875)])
- def cases_acos():
- return (special() + edge('domain_endpoint', 1, True) + edge('half_interval', 0.5, True)
- + edge('tiny_cutoff', '0x1p-57', True)
- + [c('interior_positive', 0.28125), c('interior_negative', -0.28125)])
- def cases_atan():
- rows = special() + [c('ordinary_positive', 9.3125), c('ordinary_negative', -9.3125)]
- for label, x in [('tiny_cutoff', '0x1p-27'), ('first_interval', 0.4375),
- ('second_interval', 0.6875), ('third_interval', 1.1875),
- ('reciprocal_interval', 2.4375), ('asymptote', '0x1p+66')]:
- rows += edge(label, x, True)
- return rows
- def cases_atan2():
- rows = []
- for i, (y, x) in enumerate([(5.8125, 0.21875), (5.8125, -0.21875),
- (-5.8125, 0.21875), (-5.8125, -0.21875)]):
- rows.append(c('quadrant_' + str(i + 1), y, x))
- for y in [0, SIGN, INF, INF | SIGN]:
- for x in [0, SIGN, INF, INF | SIGN]:
- rows.append(Case('axes_%016x_%016x' % (y, x), y, x))
- for r in special():
- rows += [Case(r.name + '_ordinate', r.x, word(-5.8125)),
- Case(r.name + '_abscissa', word(-5.8125), r.x)]
- rows += [c('tiny_ratio', '0x1p-1000', '0x1p+900'),
- c('huge_ratio', '0x1p+900', '0x1p-1000'),
- c('tiny_ratio_negative_x', '0x1p-1000', '-0x1p+900'),
- c('unit_abscissa_shortcut', 1.8125, 1)]
- return rows
- # Declaration order is the category order in the test reports.
- GROUPS = {
- 'classification': ['isfinite', 'isinf', 'isnan', 'isnormal'],
- 'sign_and_order': ['fabs', 'copysign', 'fmin', 'fmax'],
- 'rounding_and_remainder': ['ceil', 'floor', 'trunc', 'modf', 'fmod'],
- 'roots': ['sqrt', 'cbrt'],
- 'exponentials': ['exp', 'exp2', 'exp10', 'pow'],
- 'logarithms': ['log', 'log2', 'log10', 'log_base'],
- 'trigonometry': ['sin', 'cos', 'tan', 'sincos'],
- 'inverse_trigonometry': ['asin', 'acos', 'atan', 'atan2'],
- }
- # Tolerances apply only to the independent accuracy oracle, never to the frozen
- # implementation results. They are case-suite budgets, not universal bounds.
- ULPS = {name: 0 for name in sum(list(GROUPS.values())[:4], [])}
- ULPS.update(cbrt=1, exp=1, exp2=1, exp10=2, pow=2, log=1, log2=1, log10=1,
- log_base=3, sin=1, cos=1, tan=1, sincos=1, asin=1, acos=1, atan=1, atan2=2)
|