File: C:/Users/fred/anaconda3/Lib/site-packages/astropy/time/utils.py
# Licensed under a 3-clause BSD style license - see LICENSE.rst
"""Time utilities.
In particular, routines to do basic arithmetic on numbers represented by two
doubles, using the procedure of Shewchuk, 1997, Discrete & Computational
Geometry 18(3):305-363 -- http://www.cs.berkeley.edu/~jrs/papers/robustr.pdf
Furthermore, some helper routines to turn strings and other types of
objects into two values, and vice versa.
"""
import decimal
import numpy as np
import astropy.units as u
def day_frac(val1, val2, factor=None, divisor=None):
"""Return the sum of ``val1`` and ``val2`` as two float64s.
The returned floats are an integer part and the fractional remainder,
with the latter guaranteed to be within -0.5 and 0.5 (inclusive on
either side, as the integer is rounded to even).
The arithmetic is all done with exact floating point operations so no
precision is lost to rounding error. It is assumed the sum is less
than about 1e16, otherwise the remainder will be greater than 1.0.
Parameters
----------
val1, val2 : array of float
Values to be summed.
factor : float, optional
If given, multiply the sum by it.
divisor : float, optional
If given, divide the sum by it.
Returns
-------
day, frac : float64
Integer and fractional part of val1 + val2.
"""
# Add val1 and val2 exactly, returning the result as two float64s.
# The first is the approximate sum (with some floating point error)
# and the second is the error of the float64 sum.
sum12, err12 = two_sum(val1, val2)
if factor is not None:
sum12, carry = two_product(sum12, factor)
carry += err12 * factor
sum12, err12 = two_sum(sum12, carry)
if divisor is not None:
q1 = sum12 / divisor
p1, p2 = two_product(q1, divisor)
d1, d2 = two_sum(sum12, -p1)
d2 += err12
d2 -= p2
q2 = (d1 + d2) / divisor # 3-part float fine here; nothing can be lost
sum12, err12 = two_sum(q1, q2)
# get integer fraction
day = np.round(sum12)
# Calculate remaining fraction. This can have gotten >0.5 or <-0.5, which means
# we would lose one bit of precision. So, correct for that. Here, we need
# particular care for the case that frac=0.5 and check>0 or frac=-0.5 and check<0,
# since in that case if check is large enough, rounding was done the wrong way.
frac, check = two_sum(sum12 - day, err12)
excess = np.where(
frac * np.sign(check) != 0.5, np.round(frac), np.round(frac + 2 * check)
)
day += excess
frac = sum12 - day
frac += err12
return day, frac
def quantity_day_frac(val1, val2=None):
"""Like ``day_frac``, but for quantities with units of time.
The quantities are separately converted to days. Here, we need to take
care with the conversion since while the routines here can do accurate
multiplication, the conversion factor itself may not be accurate. For
instance, if the quantity is in seconds, the conversion factor is
1./86400., which is not exactly representable as a float.
To work around this, for conversion factors less than unity, rather than
multiply by that possibly inaccurate factor, the value is divided by the
conversion factor of a day to that unit (i.e., by 86400. for seconds). For
conversion factors larger than 1, such as 365.25 for years, we do just
multiply. With this scheme, one has precise conversion factors for all
regular time units that astropy defines. Note, however, that it does not
necessarily work for all custom time units, and cannot work when conversion
to time is via an equivalency. For those cases, one remains limited by the
fact that Quantity calculations are done in double precision, not in
quadruple precision as for time.
"""
if val2 is not None:
res11, res12 = quantity_day_frac(val1)
res21, res22 = quantity_day_frac(val2)
# This summation is can at most lose 1 ULP in the second number.
return res11 + res21, res12 + res22
try:
factor = val1.unit.to(u.day)
except Exception:
# Not a simple scaling, so cannot do the full-precision one.
# But at least try normal conversion, since equivalencies may be set.
return val1.to_value(u.day), 0.0
if factor == 1.0:
return day_frac(val1.value, 0.0)
if factor > 1:
return day_frac(val1.value, 0.0, factor=factor)
else:
divisor = u.day.to(val1.unit)
return day_frac(val1.value, 0.0, divisor=divisor)
def two_sum(a, b):
"""
Add ``a`` and ``b`` exactly, returning the result as two float64s.
The first is the approximate sum (with some floating point error)
and the second is the error of the float64 sum.
Using the procedure of Shewchuk, 1997,
Discrete & Computational Geometry 18(3):305-363
http://www.cs.berkeley.edu/~jrs/papers/robustr.pdf
Returns
-------
sum, err : float64
Approximate sum of a + b and the exact floating point error
"""
x = a + b
eb = x - a # bvirtual in Shewchuk
ea = x - eb # avirtual in Shewchuk
eb = b - eb # broundoff in Shewchuk
ea = a - ea # aroundoff in Shewchuk
return x, ea + eb
def two_product(a, b):
"""
Multiple ``a`` and ``b`` exactly, returning the result as two float64s.
The first is the approximate product (with some floating point error)
and the second is the error of the float64 product.
Uses the procedure of Shewchuk, 1997,
Discrete & Computational Geometry 18(3):305-363
http://www.cs.berkeley.edu/~jrs/papers/robustr.pdf
Returns
-------
prod, err : float64
Approximate product a * b and the exact floating point error
"""
x = a * b
ah, al = split(a)
bh, bl = split(b)
y1 = ah * bh
y = x - y1
y2 = al * bh
y -= y2
y3 = ah * bl
y -= y3
y4 = al * bl
y = y4 - y
return x, y
def split(a):
"""
Split float64 in two aligned parts.
Uses the procedure of Shewchuk, 1997,
Discrete & Computational Geometry 18(3):305-363
http://www.cs.berkeley.edu/~jrs/papers/robustr.pdf
"""
c = 134217729.0 * a # 2**27+1.
abig = c - a
ah = c - abig
al = a - ah
return ah, al
_enough_decimal_places = 34 # to represent two doubles
def longdouble_to_twoval(val1, val2=None):
if val2 is None:
val2 = val1.dtype.type(0.0)
else:
best_type = np.result_type(val1.dtype, val2.dtype)
val1 = val1.astype(best_type, copy=False)
val2 = val2.astype(best_type, copy=False)
# day_frac is independent of dtype, as long as the dtype
# are the same and no factor or divisor is given.
i, f = day_frac(val1, val2)
return i.astype(float, copy=False), f.astype(float, copy=False)
def decimal_to_twoval1(val1, val2=None):
with decimal.localcontext() as ctx:
ctx.prec = _enough_decimal_places
d = decimal.Decimal(val1)
i = round(d)
f = d - i
return float(i), float(f)
def bytes_to_twoval1(val1, val2=None):
return decimal_to_twoval1(val1.decode("ascii"))
def twoval_to_longdouble(val1, val2):
return val1.astype(np.longdouble) + val2.astype(np.longdouble)
def twoval_to_decimal1(val1, val2):
with decimal.localcontext() as ctx:
ctx.prec = _enough_decimal_places
return decimal.Decimal(val1) + decimal.Decimal(val2)
def twoval_to_string1(val1, val2, fmt):
if val2 == 0.0:
# For some formats, only a single float is really used.
# For those, let numpy take care of correct number of digits.
return str(val1)
result = format(twoval_to_decimal1(val1, val2), fmt).strip("0")
if result[-1] == ".":
result += "0"
return result
def twoval_to_bytes1(val1, val2, fmt):
return twoval_to_string1(val1, val2, fmt).encode("ascii")
decimal_to_twoval = np.vectorize(decimal_to_twoval1)
bytes_to_twoval = np.vectorize(bytes_to_twoval1)
twoval_to_decimal = np.vectorize(twoval_to_decimal1)
twoval_to_string = np.vectorize(twoval_to_string1, excluded="fmt")
twoval_to_bytes = np.vectorize(twoval_to_bytes1, excluded="fmt")