Ejemplo n.º 1
0
    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))
Ejemplo n.º 2
0
    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))