blob: d6f02a673fa1b9f77163e56b4def76705ca43028 [file] [log] [blame]
/*
* Copyright (C) 2025 Igalia, S.L. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "FractionToDouble.h"
#include "MathCommon.h"
// The calculations here are based on algorithms from two sources. The second
// one builds on the first.
//
// Shewchuk (1997). Adaptive precision floating-point arithmetic and fast robust
// geometric predicates. Discrete & Computational Geometry 18(3), pp. 305–363.
// https://doi.org/10.1007/PL00009321
//
// Hida, Li, Bailey (2008). Library for double-double and quad-double
// arithmetic. Manuscript. https://www.davidhbailey.com/dhbpapers/qd.pdf
// and the accompanying QD library https://github.com/BL-highprecision/QD,
// which is BSD-licensed.
namespace JSC {
// Double-double precision floating point number, represented as the unevaluated
// sum of two doubles. In other words, dd[0] is the double approximation term
// and dd[1] is the error term.
//
// There are many such representations, but only one is 'normalized' meaning the
// dd[0] term is the most accurate possible double-precision approximation of
// the double-double value.
using DD = std::array<double, 2>;
// Conversion of Int128 to double-double precision floating point. The
// calculations follow from the definition of hi and lo: hi is the closest
// double-precision approximation to the exact value (which itself will be a
// safe integer) and lo is the error term.
static DD int128ToDD(const Int128& value)
{
double hi = static_cast<double>(value);
double lo = static_cast<double>(value - static_cast<Int128>(hi));
return { hi, lo };
}
// Computes double-double precision a + b of two doubles a and b. This is the
// Two-Sum algorithm in theorem 7 of the Shewchuk paper.
static DD ddSum(double a, double b)
{
// First compute the double-precision approximation of the sum by regular
// double addition.
double sum = a + b;
// Compute the error term.
double bVirtual = sum - a;
double aVirtual = sum - bVirtual;
double bRoundoff = b - bVirtual;
double aRoundoff = a - aVirtual;
double error = aRoundoff + bRoundoff;
return { sum, error };
}
// Computes double-double precision a * b of two doubles a and b. The
// optimization using std::fma() is suggested in section 2 of the Hida-Li-Bailey
// paper.
static DD ddProduct(double a, double b)
{
// First compute the double-precision approximation of the product by
// regular double multiplication.
double product = a * b;
// On armv8, this emits the fnmsub instruction.
// On x86_64, this emits the vfmsub213sd instruction if compiled with SSE
// instructions. If not, it calls libm's fma(), which is comparably fast to
// using the Two-Product algorithm in theorem 18 of the Shewchuk paper.
double error = std::fma(a, b, -product);
return { product, error };
}
// Computes double-double precision numerator / denominator, where divisor is a
// double, and rounds the result to double precision. This is described in
// section 3.5 of the Hida-Li-Bailey paper.
static double fractionToDoubleSlow(const Int128& numerator, double denominator)
{
DD ddNumerator = int128ToDD(numerator);
// Compute a first approximation of the quotient by regular double division.
double quotient0 = ddNumerator[0] / denominator;
// Compute remainder, ddNumerator - quotient0 * denominator.
DD product = ddProduct(quotient0, denominator);
DD remainder = ddSum(ddNumerator[0], -product[0]);
// Compute the next approximation term.
double error = remainder[1] + ddNumerator[1] - product[1];
double quotient1 = (remainder[0] + error) / denominator;
// The result is DD { quotient0, quotient1 }. If we wanted double-double
// precision here, we would have to use the Fast-Two-Sum algorithm from
// theorem 6 of the Shewchuk paper to renormalize the two terms, but since
// we only need double precision we can discard the error term.
return quotient0 + quotient1;
}
double fractionToDouble(const Int128& numerator, double denominator)
{
ASSERT(denominator > 0);
ASSERT(isSafeInteger(denominator));
if (!numerator)
return 0;
// When the denominator is 1, we are just calculating the double
// approximation of the numerator.
if (denominator == 1)
return static_cast<double>(numerator);
// When the numerator can be represented exactly as a double the algorithm
// collapses to a simple double division.
if (isSafeInteger(static_cast<double>(numerator))) [[likely]]
return static_cast<double>(numerator) / denominator;
// Otherwise use double-double precision to compute the result.
return fractionToDoubleSlow(numerator, denominator);
}
} // namespace JSC