def test_floats_in_tiny_interval_within_bounds(data, center): assume(not (math.isinf(next_down(center)) or math.isinf(next_up(center)))) lo = Decimal.from_float(next_down(center)).next_plus() hi = Decimal.from_float(next_up(center)).next_minus() assert float(lo) < lo < center < hi < float(hi) val = data.draw(st.floats(lo, hi)) assert lo < val < hi
def test_fuzz_floats_bounds(data): bound = none() | floats(allow_nan=False) low, high = data.draw(tuples(bound, bound), label="low, high") if low is not None and high is not None and low > high: low, high = high, low exmin = low is not None and low != inf and data.draw(booleans(), label="ex_min") exmax = high is not None and high != -inf and data.draw(booleans(), label="ex_max") try: val = data.draw(floats(low, high, exclude_min=exmin, exclude_max=exmax), label="value") assume(val) # positive/negative zero is an issue except (InvalidArgument, HypothesisDeprecationWarning): assert ((exmin and exmax and low == next_down(high)) or (low == high and (exmin or exmax)) or (low == high == 0 and copysign(1.0, low) == 1 and copysign(1.0, high) == -1)) reject() # no floats in required range if low is not None: assert low <= val if high is not None: assert val <= high if exmin: assert low != val if exmax: assert high != val
def test_fuzz_floats_bounds(data): width = data.draw(sampled_from([64, 32, 16])) bound = none() | floats(allow_nan=False, width=width) low, high = data.draw(tuples(bound, bound), label="low, high") if low is not None and high is not None and low > high: low, high = high, low if low is not None and high is not None and low > high: low, high = high, low exmin = low is not None and low != inf and data.draw(booleans(), label="ex_min") exmax = high is not None and high != -inf and data.draw(booleans(), label="ex_max") if low is not None and high is not None: lo = next_up(low, width) if exmin else low hi = next_down(high, width) if exmax else high # There must actually be floats between these bounds assume(lo <= hi) if lo == hi == 0: assume(not exmin and not exmax and copysign(1.0, lo) <= copysign(1.0, hi)) s = floats(low, high, exclude_min=exmin, exclude_max=exmax, width=width) val = data.draw(s, label="value") assume(val) # positive/negative zero is an issue if low is not None: assert low <= val if high is not None: assert val <= high if exmin: assert low != val if exmax: assert high != val
def test_down_means_lesser(x): lo = next_down(x) if not x > lo: assert ( (math.isnan(x) and math.isnan(lo)) or (x < 0 and math.isinf(x)) or (x == lo == 0 and is_negative(lo) and not is_negative(x)) )
def get_number_bounds( schema: Schema, *, _for_integer: bool = False, ) -> Tuple[Optional[float], Optional[float], bool, bool]: """Get the min and max allowed floats, and whether they are exclusive.""" assert "number" in get_type(schema) or _for_integer lower = schema.get("minimum") upper = schema.get("maximum") exmin = schema.get("exclusiveMinimum", False) exmax = schema.get("exclusiveMaximum", False) assert lower is None or isinstance(lower, (int, float)) assert upper is None or isinstance(upper, (int, float)) assert isinstance(exmin, (bool, int, float)) assert isinstance(exmax, (bool, int, float)) # Canonicalise to number-and-boolean representation if exmin is not True and exmin is not False: if lower is None or exmin >= lower: lower, exmin = exmin, True else: exmin = False if exmax is not True and exmax is not False: if upper is None or exmax <= upper: upper, exmax = exmax, True else: exmax = False assert isinstance(exmin, bool) assert isinstance(exmax, bool) # Adjust bounds and cast to float if lower is not None and not _for_integer: lo = float(lower) if lo < lower: lo = next_up(lo) exmin = False lower = lo if upper is not None and not _for_integer: hi = float(upper) if hi > upper: hi = next_down(hi) exmax = False upper = hi return lower, upper, exmin, exmax
def get_number_bounds( schema: Schema, ) -> Tuple[Optional[float], Optional[float], bool, bool]: """Get the min and max allowed floats, and whether they are exclusive.""" lower, upper, exmin, exmax = _get_numeric_bounds(schema) if lower is not None: lo = float(lower) if lo < lower: lo = next_up(lo) exmin = False lower = lo if upper is not None: hi = float(upper) if hi > upper: hi = next_down(hi) exmax = False upper = hi return lower, upper, exmin, exmax
def test_updown_roundtrip(val): assert val == next_up(next_down(val)) assert val == next_down(next_up(val))
def test_down_means_lesser(x): lo = next_down(x) if not x > lo: assert (math.isnan(x) and math.isnan(lo)) or (x < 0 and math.isinf(x))
def canonicalish(schema: JSONType) -> Dict[str, Any]: """Convert a schema into a more-canonical form. This is obviously incomplete, but improves best-effort recognition of equivalent schemas and makes conversion logic simpler. """ if schema is True: return {} elif schema is False: return {"not": {}} # Make a copy, so we don't mutate the existing schema in place. # Using the canonical encoding makes all integer-valued floats into ints. schema = json.loads(encode_canonical_json(schema)) # Otherwise, we're dealing with "objects", i.e. dicts. if not isinstance(schema, dict): raise InvalidArgument( f"Got schema={schema} of type {type(schema).__name__}, " "but expected a dict.") if "const" in schema: if not make_validator(schema).is_valid(schema["const"]): return FALSEY return {"const": schema["const"]} if "enum" in schema: validator = make_validator(schema) enum_ = sorted((v for v in schema["enum"] if validator.is_valid(v)), key=sort_key) if not enum_: return FALSEY elif len(enum_) == 1: return {"const": enum_[0]} return {"enum": enum_} # Recurse into the value of each keyword with a schema (or list of them) as a value for key in SCHEMA_KEYS: if isinstance(schema.get(key), list): schema[key] = [canonicalish(v) for v in schema[key]] elif isinstance(schema.get(key), (bool, dict)): schema[key] = canonicalish(schema[key]) else: assert key not in schema for key in SCHEMA_OBJECT_KEYS: if key in schema: schema[key] = { k: v if isinstance(v, list) else canonicalish(v) for k, v in schema[key].items() } type_ = get_type(schema) if "number" in type_: if schema.get("exclusiveMinimum") is False: del schema["exclusiveMinimum"] if schema.get("exclusiveMaximum") is False: del schema["exclusiveMaximum"] lo, hi, exmin, exmax = get_number_bounds(schema) mul = schema.get("multipleOf") if (lo is not None and hi is not None and ((mul and not has_divisibles(lo, hi, mul, exmin, exmax)) or (next_up(lo) if exmin else lo) > (next_down(hi) if exmax else hi))): type_.remove("number") elif isinstance(mul, int): # Numbers which are a multiple of an integer? That's the integer type. type_.remove("number") type_ = [t for t in TYPE_STRINGS if t in type_ or t == "integer"] if "integer" in type_: lo, hi = get_integer_bounds(schema) mul = schema.get("multipleOf") if lo is not None and isinstance(mul, int) and mul > 1 and (lo % mul): lo += mul - (lo % mul) if hi is not None and isinstance(mul, int) and mul > 1 and (hi % mul): hi -= hi % mul if lo is not None: schema["minimum"] = lo schema.pop("exclusiveMinimum", None) if hi is not None: schema["maximum"] = hi schema.pop("exclusiveMaximum", None) if lo is not None and hi is not None and lo > hi: type_.remove("integer") if "array" in type_ and "contains" in schema: if schema["contains"] == FALSEY: type_.remove("array") else: schema["minItems"] = max(schema.get("minItems", 0), 1) if schema["contains"] == TRUTHY: schema.pop("contains") schema["minItems"] = max(schema.get("minItems", 1), 1) if "array" in type_ and schema.get("minItems", 0) > schema.get( "maxItems", math.inf): type_.remove("array") if ("array" in type_ and "minItems" in schema and isinstance(schema.get("items", []), (bool, dict))): count = upper_bound_instances(schema["items"]) if (count == 0 and schema["minItems"] > 0) or (schema.get( "uniqueItems", False) and count < schema["minItems"]): type_.remove("array") if "array" in type_ and isinstance(schema.get("items"), list): schema["items"] = schema["items"][:schema.get("maxItems")] for idx, s in enumerate(schema["items"]): if s == FALSEY: schema["items"] = schema["items"][:idx] schema["maxItems"] = idx schema.pop("additionalItems", None) break if schema.get("minItems", 0) > min( len(schema["items"]) + upper_bound_instances(schema.get("additionalItems", TRUTHY)), schema.get("maxItems", math.inf), ): type_.remove("array") if ("array" in type_ and isinstance(schema.get("items"), list) and schema.get("additionalItems") == FALSEY): schema.pop("maxItems", None) if "array" in type_ and (schema.get("items") == FALSEY or schema.get("maxItems", 1) == 0): schema["maxItems"] = 0 schema.pop("items", None) schema.pop("uniqueItems", None) schema.pop("additionalItems", None) if "array" in type_ and schema.get("items", TRUTHY) == TRUTHY: schema.pop("items", None) if ("properties" in schema and not schema.get("patternProperties") and schema.get("additionalProperties") == FALSEY): schema["maxProperties"] = min(schema.get("maxProperties", math.inf), len(schema["properties"])) if "object" in type_ and schema.get("minProperties", 0) > schema.get( "maxProperties", math.inf): type_.remove("object") # Canonicalise "required" schemas to remove redundancy if "object" in type_ and "required" in schema: assert isinstance(schema["required"], list) reqs = set(schema["required"]) if schema.get("dependencies"): # When the presence of a required property requires other properties via # dependencies, those properties can be moved to the base required keys. dep_names = { k: sorted(v) for k, v in schema["dependencies"].items() if isinstance(v, list) } while reqs.intersection(dep_names): for r in reqs.intersection(dep_names): reqs.update(dep_names.pop(r)) for k, v in list(schema["dependencies"].items()): if isinstance(v, list) and k not in dep_names: schema["dependencies"].pop(k) schema["required"] = sorted(reqs) max_ = schema.get("maxProperties", float("inf")) assert isinstance(max_, (int, float)) propnames = schema.get("propertyNames", {}) if len(schema["required"]) > max_: type_.remove("object") else: validator = make_validator(propnames) if not all( validator.is_valid(name) for name in schema["required"]): type_.remove("object") for t, kw in TYPE_SPECIFIC_KEYS: numeric = {"number", "integer"} if t in type_ or (t in numeric and numeric.intersection(type_)): continue for k in kw.split(): schema.pop(k, None) # Remove no-op requires if "required" in schema and not schema["required"]: schema.pop("required") # Canonicalise "not" subschemas if "not" in schema: not_ = schema.pop("not") if not_ == TRUTHY or not_ == schema: # If everything is rejected, discard all other (irrelevant) keys # TODO: more sensitive detection of cases where the not-clause # excludes everything in the schema. return FALSEY type_keys = {k: set(v.split()) for k, v in TYPE_SPECIFIC_KEYS} type_constraints = {"type"} for v in type_keys.values(): type_constraints |= v if set(not_).issubset(type_constraints): not_["type"] = get_type(not_) for t in set(type_).intersection(not_["type"]): if not type_keys.get(t, set()).intersection(not_): type_.remove(t) if t not in ("integer", "number"): not_["type"].remove(t) not_ = canonicalish(not_) if not_ != FALSEY: # If the "not" key rejects nothing, discard it schema["not"] = not_ assert isinstance(type_, list), type_ if not type_: assert type_ == [] return FALSEY if type_ == ["null"]: return {"const": None} if type_ == ["boolean"]: return {"enum": [False, True]} if type_ == ["null", "boolean"]: return {"enum": [None, False, True]} if len(type_) == 1: schema["type"] = type_[0] elif type_ == get_type({}): schema.pop("type", None) else: schema["type"] = type_ # Canonicalise "xxxOf" lists; in each case canonicalising and sorting the # sub-schemas then handling any key-specific logic. if TRUTHY in schema.get("anyOf", ()): schema.pop("anyOf", None) if "anyOf" in schema: schema["anyOf"] = sorted(schema["anyOf"], key=encode_canonical_json) schema["anyOf"] = [s for s in schema["anyOf"] if s != FALSEY] if not schema["anyOf"]: return FALSEY if len(schema) == len(schema["anyOf"]) == 1: return schema["anyOf"][0] # type: ignore if "allOf" in schema: schema["allOf"] = sorted(schema["allOf"], key=encode_canonical_json) if any(s == FALSEY for s in schema["allOf"]): return FALSEY if all(s == TRUTHY for s in schema["allOf"]): schema.pop("allOf") elif len(schema) == len(schema["allOf"]) == 1: return schema["allOf"][0] # type: ignore else: tmp = schema.copy() ao = tmp.pop("allOf") out = merged([tmp] + ao) if isinstance(out, dict): # pragma: no branch schema = out # TODO: this assertion is soley because mypy 0.750 doesn't know # that `schema` is a dict otherwise. Needs minimal report upstream. assert isinstance(schema, dict) if "oneOf" in schema: one_of = schema.pop("oneOf") assert isinstance(one_of, list) one_of = sorted(one_of, key=encode_canonical_json) one_of = [s for s in one_of if s != FALSEY] if len(one_of) == 1: m = merged([schema, one_of[0]]) if m is not None: # pragma: no branch return m if (not one_of) or one_of.count(TRUTHY) > 1: return FALSEY schema["oneOf"] = one_of # if/then/else schemas are ignored unless if and another are present if "if" not in schema: schema.pop("then", None) schema.pop("else", None) if "then" not in schema and "else" not in schema: schema.pop("if", None) if schema.get("uniqueItems") is False: del schema["uniqueItems"] return schema
] def kw(marks=(), **kwargs): id_ = ", ".join(f"{k}={v!r}" for k, v in kwargs.items()) return pytest.param(kwargs, id=id_, marks=marks) @pytest.mark.parametrize( "kwargs", [ kw(min_value=1), kw(min_value=1), kw(max_value=-1), kw(min_value=float_info.min), kw(min_value=next_down(float_info.min), exclude_min=True), kw(max_value=-float_info.min), kw(min_value=next_up(-float_info.min), exclude_max=True), ], ) def test_subnormal_validation(kwargs): strat = floats(**kwargs, allow_subnormal=True) with pytest.raises(InvalidArgument): strat.example() @pytest.mark.parametrize( "kwargs", [ # min value kw(allow_subnormal=False, min_value=1),
def numeric_schema(schema: dict) -> st.SearchStrategy[float]: """Handle numeric schemata.""" lower = schema.get("minimum") upper = schema.get("maximum") type_ = get_type(schema) or ["integer", "number"] exmin = schema.get("exclusiveMinimum") if exmin is True and "integer" in type_: assert lower is not None, "boolean exclusiveMinimum implies numeric minimum" lower += 1 exmin = False elif exmin is not False and exmin is not None: lo = exmin + 1 if int(exmin) == exmin else math.ceil(exmin) if lower is None: lower = lo if "integer" in type_ else exmin else: lower = max(lower, lo if "integer" in type_ else exmin) exmin = False exmax = schema.get("exclusiveMaximum") if exmax is True and "integer" in type_: assert upper is not None, "boolean exclusiveMaximum implies numeric maximum" upper -= 1 exmax = False elif exmax is not False and exmax is not None: hi = exmax - 1 if int(exmax) == exmax else math.floor(exmax) if upper is None: upper = hi if "integer" in type_ else exmax else: upper = min(upper, hi if "integer" in type_ else exmax) exmax = False if "multipleOf" in schema: multiple_of = schema["multipleOf"] assert isinstance(multiple_of, (int, float)) if lower is not None: lo = math.ceil(lower / multiple_of) assert lo * multiple_of >= lower, (lower, lo) lower = lo if upper is not None: hi = math.floor(upper / multiple_of) assert hi * multiple_of <= upper, (upper, hi) upper = hi strat = st.integers(lower, upper).map(partial(operator.mul, multiple_of)) # check for and filter out float bounds, inexact multiplication, etc. return strat.filter(partial(is_valid, schema=schema)) strat = st.nothing() if "integer" in type_: lo = lower if lower is None else math.ceil(lower) hi = upper if upper is None else math.floor(upper) if lo is None or hi is None or lo <= hi: strat = st.integers(lo, hi) if "number" in type_: # Filter out negative-zero as it does not exist in JSON lo = exmin if lower is None else lower if lo is not None: lower = float(lo) if lower < lo: lower = next_up(lower) # type: ignore # scary floats magic assert lower >= lo hi = exmax if upper is None else upper if hi is not None: upper = float(hi) if upper > hi: upper = next_down(upper) # type: ignore # scary floats magic assert upper <= hi strat |= st.floats( min_value=lower, max_value=upper, allow_nan=False, allow_infinity=False, exclude_min=exmin is not None, exclude_max=exmax is not None, ).filter(lambda n: n != 0 or math.copysign(1, n) == 1) return strat
output = out.getvalue() assert f"{example_type} example:" in output assert_output_contains_failure(output, test, **kwargs) @contextlib.contextmanager def temp_registered(type_, strat_or_factory): """Register and un-register a type for st.from_type(). This not too hard, but there's a subtlety in restoring the previously-registered strategy which we got wrong in a few places. """ prev = _global_type_lookup.get(type_) try: register_type_strategy(type_, strat_or_factory) yield finally: del _global_type_lookup[type_] from_type.__clear_cache() if prev is not None: register_type_strategy(type_, prev) # Specifies whether we can represent subnormal floating point numbers. # IEE-754 requires subnormal support, but it's often disabled anyway by unsafe # compiler options like `-ffast-math`. On most hardware that's even a global # config option, so *linking against* something built this way can break us. # Everything is terrible PYTHON_FTZ = next_down(sys.float_info.min) == 0.0
def floats( min_value: Optional[Real] = None, max_value: Optional[Real] = None, *, allow_nan: Optional[bool] = None, allow_infinity: Optional[bool] = None, width: int = 64, exclude_min: bool = False, exclude_max: bool = False, ) -> SearchStrategy[float]: """Returns a strategy which generates floats. - If min_value is not None, all values will be ``>= min_value`` (or ``> min_value`` if ``exclude_min``). - If max_value is not None, all values will be ``<= max_value`` (or ``< max_value`` if ``exclude_max``). - If min_value or max_value is not None, it is an error to enable allow_nan. - If both min_value and max_value are not None, it is an error to enable allow_infinity. Where not explicitly ruled out by the bounds, all of infinity, -infinity and NaN are possible values generated by this strategy. The width argument specifies the maximum number of bits of precision required to represent the generated float. Valid values are 16, 32, or 64. Passing ``width=32`` will still use the builtin 64-bit ``float`` class, but always for values which can be exactly represented as a 32-bit float. The exclude_min and exclude_max argument can be used to generate numbers from open or half-open intervals, by excluding the respective endpoints. Excluding either signed zero will also exclude the other. Attempting to exclude an endpoint which is None will raise an error; use ``allow_infinity=False`` to generate finite floats. You can however use e.g. ``min_value=-math.inf, exclude_min=True`` to exclude only one infinite endpoint. Examples from this strategy have a complicated and hard to explain shrinking behaviour, but it tries to improve "human readability". Finite numbers will be preferred to infinity and infinity will be preferred to NaN. """ check_type(bool, exclude_min, "exclude_min") check_type(bool, exclude_max, "exclude_max") if allow_nan is None: allow_nan = bool(min_value is None and max_value is None) elif allow_nan and (min_value is not None or max_value is not None): raise InvalidArgument( f"Cannot have allow_nan={allow_nan!r}, with min_value or max_value" ) if width not in (16, 32, 64): raise InvalidArgument( f"Got width={width!r}, but the only valid values " "are the integers 16, 32, and 64.") check_valid_bound(min_value, "min_value") check_valid_bound(max_value, "max_value") min_arg, max_arg = min_value, max_value if min_value is not None: min_value = float_of(min_value, width) assert isinstance(min_value, float) if max_value is not None: max_value = float_of(max_value, width) assert isinstance(max_value, float) if min_value != min_arg: raise InvalidArgument( f"min_value={min_arg!r} cannot be exactly represented as a float " f"of width {width} - use min_value={min_value!r} instead.") if max_value != max_arg: raise InvalidArgument( f"max_value={max_arg!r} cannot be exactly represented as a float " f"of width {width} - use max_value={max_value!r} instead.") if exclude_min and (min_value is None or min_value == math.inf): raise InvalidArgument(f"Cannot exclude min_value={min_value!r}") if exclude_max and (max_value is None or max_value == -math.inf): raise InvalidArgument(f"Cannot exclude max_value={max_value!r}") if min_value is not None and (exclude_min or (min_arg is not None and min_value < min_arg)): min_value = next_up(min_value, width) if min_value == min_arg: assert min_value == min_arg == 0 assert is_negative(min_arg) and not is_negative(min_value) min_value = next_up(min_value, width) assert min_value > min_arg # type: ignore if max_value is not None and (exclude_max or (max_arg is not None and max_value > max_arg)): max_value = next_down(max_value, width) if max_value == max_arg: assert max_value == max_arg == 0 assert is_negative(max_value) and not is_negative(max_arg) max_value = next_down(max_value, width) assert max_value < max_arg # type: ignore if min_value == -math.inf: min_value = None if max_value == math.inf: max_value = None bad_zero_bounds = (min_value == max_value == 0 and is_negative(max_value) and not is_negative(min_value)) if (min_value is not None and max_value is not None and (min_value > max_value or bad_zero_bounds)): # This is a custom alternative to check_valid_interval, because we want # to include the bit-width and exclusion information in the message. msg = ( "There are no %s-bit floating-point values between min_value=%r " "and max_value=%r" % (width, min_arg, max_arg)) if exclude_min or exclude_max: msg += f", exclude_min={exclude_min!r} and exclude_max={exclude_max!r}" raise InvalidArgument(msg) if allow_infinity is None: allow_infinity = bool(min_value is None or max_value is None) elif allow_infinity: if min_value is not None and max_value is not None: raise InvalidArgument( f"Cannot have allow_infinity={allow_infinity!r}, " "with both min_value and max_value") elif min_value == math.inf: raise InvalidArgument("allow_infinity=False excludes min_value=inf") elif max_value == -math.inf: raise InvalidArgument("allow_infinity=False excludes max_value=-inf") unbounded_floats = FloatStrategy(allow_infinity=allow_infinity, allow_nan=allow_nan, width=width) if min_value is None and max_value is None: return unbounded_floats elif min_value is not None and max_value is not None: if min_value == max_value: assert isinstance(min_value, float) result = just(min_value) elif is_negative(min_value): if is_negative(max_value): return floats(min_value=-max_value, max_value=-min_value, width=width).map(operator.neg) else: return floats( min_value=0.0, max_value=max_value, width=width) | floats( min_value=0.0, max_value=-min_value, width=width).map( operator.neg) elif count_between_floats(min_value, max_value) > 1000: return FixedBoundedFloatStrategy(lower_bound=min_value, upper_bound=max_value, width=width) else: ub_int = float_to_int(max_value, width) lb_int = float_to_int(min_value, width) assert lb_int <= ub_int result = integers( min_value=lb_int, max_value=ub_int).map(lambda x: int_to_float(x, width)) elif min_value is not None: assert isinstance(min_value, float) if is_negative(min_value): # Ignore known bug https://github.com/python/mypy/issues/6697 return unbounded_floats.map(abs) | floats( # type: ignore min_value=min_value, max_value=-0.0, width=width) else: result = unbounded_floats.map(lambda x: min_value + abs(x)) else: assert isinstance(max_value, float) if not is_negative(max_value): return floats( min_value=0.0, max_value=max_value, width=width) | unbounded_floats.map(lambda x: -abs(x)) else: result = unbounded_floats.map(lambda x: max_value - abs(x)) if width < 64: def downcast(x): try: return float_of(x, width) except OverflowError: # pragma: no cover reject() result = result.map(downcast) if not allow_infinity: result = result.filter(lambda x: not math.isinf(x)) return result
def _from_dtype( xp: Any, dtype: Union[DataType, str], *, min_value: Optional[Union[int, float]] = None, max_value: Optional[Union[int, float]] = None, allow_nan: Optional[bool] = None, allow_infinity: Optional[bool] = None, allow_subnormal: Optional[bool] = None, exclude_min: Optional[bool] = None, exclude_max: Optional[bool] = None, ) -> st.SearchStrategy[Union[bool, int, float]]: """Return a strategy for any value of the given dtype. Values generated are of the Python scalar which is :xp-ref:`promotable <type_promotion.html>` to ``dtype``, where the values do not exceed its bounds. * ``dtype`` may be a dtype object or the string name of a :xp-ref:`valid dtype <data_types.html>`. Compatible ``**kwargs`` are passed to the inferred strategy function for integers and floats. This allows you to customise the min and max values, and exclude non-finite numbers. This is particularly useful when kwargs are passed through from :func:`arrays()`, as it seamlessly handles the ``width`` or other representable bounds for you. """ check_xp_attributes(xp, ["iinfo", "finfo"]) if isinstance(dtype, str): dtype = dtype_from_name(xp, dtype) builtin = find_castable_builtin_for_dtype(xp, dtype) def check_valid_minmax(prefix, val, info_obj): name = f"{prefix}_value" check_valid_bound(val, name) check_argument( val >= info_obj.min, f"dtype={dtype} requires {name}={val} to be at least {info_obj.min}", ) check_argument( val <= info_obj.max, f"dtype={dtype} requires {name}={val} to be at most {info_obj.max}", ) if builtin is bool: return st.booleans() elif builtin is int: iinfo = xp.iinfo(dtype) if min_value is None: min_value = iinfo.min if max_value is None: max_value = iinfo.max check_valid_integer(min_value, "min_value") check_valid_integer(max_value, "max_value") assert isinstance(min_value, int) assert isinstance(max_value, int) check_valid_minmax("min", min_value, iinfo) check_valid_minmax("max", max_value, iinfo) check_valid_interval(min_value, max_value, "min_value", "max_value") return st.integers(min_value=min_value, max_value=max_value) else: finfo = xp.finfo(dtype) kw = {} # Whilst we know the boundary values of float dtypes from finfo, we do # not assign them to the floats() strategy by default - passing min/max # values will modify test case reduction behaviour so that simple bugs # may become harder for users to identify. We plan to improve floats() # behaviour in https://github.com/HypothesisWorks/hypothesis/issues/2907. # Setting width should manage boundary values for us anyway. if min_value is not None: check_valid_bound(min_value, "min_value") assert isinstance(min_value, Real) check_valid_minmax("min", min_value, finfo) kw["min_value"] = min_value if max_value is not None: check_valid_bound(max_value, "max_value") assert isinstance(max_value, Real) check_valid_minmax("max", max_value, finfo) if min_value is not None: check_valid_interval(min_value, max_value, "min_value", "max_value") kw["max_value"] = max_value # We infer whether an array module will flush subnormals to zero, as may # be the case when libraries are built with compiler options that # violate IEEE-754 (e.g. -ffast-math and -ftz=true). Note we do this for # the specific dtype, as compilers may end up flushing subnormals for # one float but supporting subnormals for the other. # # By default, floats() will generate subnormals if they are in the # inferred values range. If we have detected that xp flushes to zero for # the passed dtype, we ensure from_dtype() will not generate subnormals # by default. if allow_subnormal is not None: kw["allow_subnormal"] = allow_subnormal else: subnormal = next_down(finfo.smallest_normal, width=finfo.bits) ftz = bool(xp.asarray(subnormal, dtype=dtype) == 0) if ftz: kw["allow_subnormal"] = False if allow_nan is not None: kw["allow_nan"] = allow_nan if allow_infinity is not None: kw["allow_infinity"] = allow_infinity if exclude_min is not None: kw["exclude_min"] = exclude_min if exclude_max is not None: kw["exclude_max"] = exclude_max return st.floats(width=finfo.bits, **kw)
decimals(bad).example() with pytest.raises(InvalidArgument) as excinfo2: with decimal.localcontext(decimal.Context(traps=[])): decimals(bad).example() assert str(excinfo.value) == str(excinfo2.value) @pytest.mark.parametrize( "s, msg", [ ( floats(min_value=inf, allow_infinity=False), "allow_infinity=False excludes min_value=inf", ), ( floats(min_value=next_down(inf), exclude_min=True, allow_infinity=False), "exclude_min=True turns min_value=.+? into inf, but allow_infinity=False", ), ( floats(max_value=-inf, allow_infinity=False), "allow_infinity=False excludes max_value=-inf", ), ( floats(max_value=next_up(-inf), exclude_max=True, allow_infinity=False), "exclude_max=True turns max_value=.+? into -inf, but allow_infinity=False", ), ],