def from_float( cls, value: float, absolute_error: typing.Optional[float] = None) -> 'TimeType': """Convert a floating point number to a TimeType using one of three modes depending on `absolute_error`. The default str(value) guarantees that all floats have a different result with sensible rounding. This was chosen as default because it is the expected behaviour most of the time if the user defined the float from a literal in code. Args: value: Floating point value to convert to arbitrary precision TimeType absolute_error: - :obj:`None`: Use `str(value)` as a proxy to get consistent precision - 0: Return the exact value of the float i.e. float(0.8) == 3602879701896397 / 4503599627370496 - 0 < `absolute_error` <= 1: Return the best approximation to `value` within `(value - absolute_error, value + absolute_error)`. The best approximation is defined as the fraction with the smallest denominator. Raises: ValueError: If `absolute_error` is not None and not 0 <= `absolute_error` <= 1 """ # gmpy2 is at least an order of magnitude faster than fractions.Fraction if absolute_error is None: # this method utilizes the 'print as many digits as necessary to distinguish between all floats' # functionality of str if type(value) in (cls, cls._InternalType, fractions.Fraction): return cls(value) else: try: # .upper() is a bit faster than replace('e', 'E') which gmpy2.mpq needs return cls(cls._to_internal(str(value).upper())) except ValueError: if isinstance( value, numbers.Number) and not numpy.isfinite(value): raise ValueError( 'Cannot represent "{}" as TimeType'.format(value), value) else: raise elif absolute_error == 0: return cls(cls._to_internal(value)) elif absolute_error < 0: raise ValueError('absolute_error needs to be > 0') elif absolute_error > 1: raise ValueError('absolute_error needs to be <= 1') else: return cls( qupulse_numeric.approximate_double( value, absolute_error, fraction_type=cls._to_internal))
def test_approximate_double(self): test_values = [ ((.1, .05), Fraction(1, 7)), ((.12, .005), Fraction(2, 17)), ((.15, .005), Fraction(2, 13)), # .111_111_12, 0.000_000_005 ((.11111112, 0.000000005), Fraction(888890, 8000009)), ((.111125, 0.0000005), Fraction(859, 7730)), ((2.50000000008, .1), Fraction(5, 2)) ] for (x, err), expected in test_values: result = approximate_double(x, err, Fraction) self.assertEqual(expected, result, msg='{x} ± {err} results in {result} ' 'which is not the expected {expected}'.format(x=x, err=err, result=result, expected=expected))