3

I am interested in understanding the maximum number of fractional decimal digits required for exact division.

The Java BigDecimal implementation states (and implements):

If the quotient this/divisor has a terminating decimal expansion, the expansion can have no more than
(a.precision() + ceil(10*b.precision)/3)
digits.

There is a typo/parenthesis error in the Java BigDecimal source comment. It should read

(a.precision() + ceil(10*b.precision/3))

In this context precision is used as in computer representations of floating point numbers (e.g. IEEE Standard for Floating-Point Arithmetic 754-2008). It can be thought of as the number of digits in the significand/coefficient/mantissa s when a terminating decimal is written in base 10 scientific notation s * 10e where s has been normalized to remove all leading and trailing zeros.

Here is a snippet of the code in question:

            /*
             * If the quotient this/divisor has a terminating decimal
             * expansion, the expansion can have no more than
             * (a.precision() + ceil(10*b.precision)/3) digits.
             * Therefore, create a MathContext object with this
             * precision and do a divide with the UNNECESSARY rounding
             * mode.
             */
            MathContext mc = new MathContext( (int)Math.min(this.precision() +
                                                            (long)Math.ceil(10.0*divisor.precision()/3.0),
                                                            Integer.MAX_VALUE),
                                              RoundingMode.UNNECESSARY);
            BigDecimal quotient;
            try {
                quotient = this.divide(divisor, mc);
            } catch (ArithmeticException e) {
                throw new ArithmeticException("Non-terminating decimal expansion; " +
                                              "no exact representable decimal result.");
            }

This links to the BigDecimal source code:
https://github.com/frohoff/jdk8u-dev-jdk/blob/da0da73ab82ed714dc5be94acd2f0d00fbdfe2e9/src/share/classes/java/math/BigDecimal.java#L1674

I found a reference to Java BigDecimal in the answer provided by @siefca to
How to estimate the number of decimal places required for a division?
but, there is no explanation as to how it is derived.

Q: How is this formula for the maximum number of digits in an exact decimal division derived?

MTH
  • 43
  • 1
    Obviously "exact" decimal division in $1/3$ is not attained in any finite number of places. Perhaps you are asking how many places are needed to give us the repeating part of a fraction's decimal expansion? – hardmath Mar 30 '23 at 23:16
  • @hardmath Thank you for your prompt response. I am not looking for how many places are needed to give the repeating part of a rational number's decimal expansion. Rather, in the case where a rational number explicitly does not repeat, how many decimal digits are required. I am looking for an explanation for dividend.precision() + ceil(10*divisor.precision()) / 3; – MTH Mar 30 '23 at 23:26
  • ^Depends on the fraction, each one is different. The maximum possible number of digits before a non-terminating rational number repeats is one fewer than the denominator (e.g. 1/7 has a sequence of 6 digits that repeats). – Nate Mar 31 '23 at 01:49
  • What exactly is "precision" in BigDecimal? Is it the number of digits including the first non-zero digit, the last non-zero digit, and all digits in between, but not counting zeros before the first non-zero or after the last non-zero? If that's not what it is, I'm not sure how to make sense of the formula. – David K Mar 31 '23 at 14:45
  • Yes, your understanding is correct. In the context of Java BigDecimal "precision" refers to the number of decimal digits in the significand/mantissa. Given a number written in plain (non-scientific) notation, strip all the leading zeros and trailing zeros, independent of the decimal separator. Count the number of digits from most significant through least significant ... that gives you your precision. – MTH Mar 31 '23 at 18:42
  • 1
    @hardmath Thank you for your feedback. I have added a link to the Java BigDecimal source code lines in questions. – MTH Mar 31 '23 at 21:58
  • The simple case, of dividing two integers $m$ and $n$, where $m$ is an exact multiple of $n$, is worth considering first. An obvious upper bound on the (exact) result's decimal places is $\lceil \log_{10} m \rceil - \lfloor \log_{10} n \rfloor$. I'm not familiar with the BigDecimal method .precision that you reference, so we should try and give Readers a mathematical definition for it. – hardmath Apr 02 '23 at 01:33
  • @hardmath, I'm not capable of coming up with a mathematical definition for precision, but I made an attempt. – MTH Apr 02 '23 at 14:02

2 Answers2

1

To keep the formulas a little more concise, let $n$ be the precision of the numerator, $d$ the precision of the denominator. The formula in the BigDecimal implementation that bounds the precision of the quotient can then be written $$ n + \left\lceil \frac{10d}{3} \right\rceil.$$

Note that the denominator can be multiplied by a power of $10$ to produce an integer. That integer can have only factors of some power of $2$ and some power of $5$, since otherwise we get a non-terminating decimal fraction, which is an error. Moreover, if we can pair up any factor of $2$ with any factor of $5$, they form a factor of $10,$ which does not contribute to the precision of the number. So the denominator is either $2^x \times 10^y$ or $5^x \times 10^y$ or simply $10^y$ for some positive integer $x$ and some integer $y$. (By allowing $y$ to be negative, we also account for any decimal fraction in the original denominator.)

Denominators of the form $10^y$ lead to quotients with $n$ digits of precision, so that case is dealt with easily.

Now note that each of the $x$ factors of $2$ in $2^x \times 10^y$ or $5$ in $5^x \times 10^y$ adds exactly one digit of precision (and no more) on the right-hand end of the numerator during division:

\begin{align} \frac{9999}{2} &= 4999.5 & \frac{9999}{5} &= 1999.8 \\ \frac{9999}{2^2} &= 2499.75 & \frac{9999}{5^2} &= \phantom{0}399.96 \\ \frac{9999}{2^3} &= 1249.875 & \frac{9999}{5^3} &= \phantom{00}79.992 \end{align}

So an upper bound on the number of digits of the quotient is simply $n + x.$

But the formula assumes we know $d,$ not $x.$ What is the upper bound of $x$ in terms of a known value of $d$? It's simply the exponent of the largest power of either $2$ or $5$ that you can write with $d$ digits of precision. But the exponent of that power of $2$ will always be greater than the exponent of that power of $5$. So the question is really just what power of $2$ can be written with $d$ digits of precision.

In general, for $x > 0,$ to write $2^x$ takes $d = \lceil x\log_{10}(2) \rceil = x\log_{10}(2) + f$ digits, where $0\leq f < 1.$ Solving for $x,$

$$ x = \frac{d - f}{\log_{10}(2)} \leq \frac{d}{\log_{10}(2)} < \frac{d}{0.3} = \frac{10d}{3} \leq \left\lceil\frac{10d}{3} \right\rceil $$ since $\log_{10}(2) > 0.3.$ And so $n + x \leq n + \left\lceil {10d/3} \right\rceil,$ and since $n + x$ is an upper bound on the precision of the quotient, so is $n + \left\lceil {10d/3} \right\rceil.$ That proves the correctness of the formula.


But the formula is not a particularly tight upper bound on the precision of the quotient. There is the substitution of $0.3$ for $\log_{10}$, which hardly ever will make a difference except for very large denominators (and not much difference then). There is also the fact that since $x \leq {d/\log_{10}(2)}$ and $x$ is an integer, by definition $x \leq \left\lfloor {d/\log_{10}(2)} \right\rfloor.$ And since $d/\log_{10}(2)$ is never an integer, that means the formula always gives an answer at least $1$ greater than required. Since $\log_{10}(2) > 0.301,$ a slightly tighter bound would be

$$ n + \left\lfloor \frac{d}{0.301} \right\rfloor.$$

But even that is not what makes the BigDecimal formula such a loose bound. A larger effect is due to the fact that when you repeatedly divide a number by $2,$ every third or fourth division removes one digit of precision from the left side of the number. It turns out that instead of adding $x$ digits of precision to the numerator by dividing it by $2^x,$ we add something more like $0.7 x$ digits of precision.

Another way to think about this is that dividing by $2^x$ is the same as multiplying by $5^x$ and then dividing by $10^x.$ Dividing by $10^x$ doesn't change the precision, but the multiplication by $5^x$ potentially adds as many digits of precision as the precision of $5^x,$ which is $\lceil x\log_{10}(5) \rceil.$ Since $x \leq \left\lfloor {d/\log_{10}(2)} \right\rfloor,$ this means the precision of the quotient can be at most

$$ n + \left\lceil\left\lfloor\frac{d}{\log_{10}(2)}\right\rfloor \log_{10}(5)\right\rceil \leq n + \left\lceil\frac{\log_{10}(5)}{\log_{10}(2)}d \right\rceil \leq n + \left\lceil 2.322 d \right\rceil. $$

So an alternative formulation is that there are at most $n + \left\lceil 2.322 d \right\rceil$ digits in the quotient. This grows slower than the formula that was used.

David K
  • 98,388
  • Thank you for your thorough response. It will take me a while to digest it, but this looks like exactly the derivation I was looking for. – MTH Apr 01 '23 at 13:17
0

Regarding the formula used in Java BigDecimal to determine maximum number of significand digits for non-repeating exact decimal division David K wrote:

... the formula always gives an answer at least 1 greater than required.

For a tighter upper bound David K showed:

... there are at most +⌈2.322⌉ digits in the quotient. This grows slower than the formula that was used.

I wrote some Java code to demonstrate this. In the following table n and d represent numerator and denominator precision. The 10/3 column is the BigDecimal implementation. The 0.301 column is David K's slight improvement. The 2.322 column is David K's much tighter bound. The 9/pow2 column shows actual quotient significand precision with a numerator of 9 (max value with n == 1) and a denominator of the max power of 2 that fits in d digits.

    n     d =>  10/3 0.301 2.322 9/pow2 
 ----  ---- => ----- ----- ----- ------ 
    1     1 =>     5     4     4      4 
    1     2 =>     8     7     6      6 
    1     3 =>    11    10     8      8 
    1     4 =>    15    14    11     11 
    1     5 =>    18    17    13     13 
    1     6 =>    21    20    15     15 
    1     7 =>    25    24    18     18 
    1     8 =>    28    27    20     20 
    1     9 =>    31    30    22     22 
    1    10 =>    35    34    25     25 
    1    20 =>    68    67    48     48 
    1    30 =>   101   100    71     71 
    1    40 =>   135   133    94     94 
    1    50 =>   168   167   118    117 not exact
    1    60 =>   201   200   141    141 
    1    70 =>   235   233   164    164 
    1    80 =>   268   266   187    187 
    1    90 =>   301   300   210    210 
    1   100 =>   335   333   234    234 
    1   200 =>   668   665   466    466 
    1   300 =>  1001   997   698    698 
    1   400 =>  1335  1329   930    930 
    1   500 =>  1668  1662  1162   1162 
    1   600 =>  2001  1994  1395   1395 
    1   700 =>  2335  2326  1627   1627 
    1   800 =>  2668  2658  1859   1859 
    1   900 =>  3001  2991  2091   2091 
    1  1000 =>  3335  3323  2323   2323 
total not exact:186

One can see that 2.322 column is significantly tighter, usually exact and never off by more than one for d up thru 1000. The formula predicts one digit more than is actually required in 186 cases in the range [1, 1000].

In the case of n == 1 d == 50 the formula predicts 118 digits of significand precision when, in fact, 9/(2**50) requires 117 decimal significand digits.

Here is the Java source code:

import java.math.BigDecimal;

class QuotientPrecisionBound {

public static void main(String[] argv) {
    testQuotientPrecisionBound();
}

static int calcQuotBound3(int n, int d) {
    int q = n + (int)Math.ceil((10 * d)/ 3.0);
    return q;
}

static int calcQuotBound301(int n, int d) {
    int q = n + (int)(d / 0.301);
    return q;
}

static int calcQuotBound2322(int n, int d) {
    int q = n + (int) Math.ceil(2.322 * d);
    return q;
}

static int nineDivPow2Precision(int d) {
    int exponent = (int)(d / Math.log10(2.0));
    BigDecimal pow2 = BigDecimal.valueOf(2).pow(exponent);
    BigDecimal quotient = BigDecimal.valueOf(9).divide(pow2);
    /*
    if (d == 50) {
        System.out.printf("d == %s\n2**50 == %s\n9/2**50 == %s\n",
                d, pow2, quotient);
    }
    */
    int precision = quotient.precision();
    return precision;
}

final static int D_MAX = 1000;

final static String FORMAT = "%5s %5s => %5s %5s %5s %6s %s\n";

static void testQuotientPrecisionBound() {
    System.out.printf(FORMAT, "n", "d", "10/3", "0.301", "2.322", "9/pow2", "");
    System.out.printf(FORMAT, "----", "----", "-----", "-----", "-----", "------", "");
    int notExactCount = 0;
    int n = 1;
    for (int d = 1; d &lt;= D_MAX; ++d) {
        int quotBound10_3 = calcQuotBound3(n, d);
        int quotBound301 = calcQuotBound301(n, d);
        int quotBound2322 = calcQuotBound2322(n, d);
        int nineDivPow2 = nineDivPow2Precision(d);

        boolean exactMatch = quotBound2322 == nineDivPow2;
        if (! exactMatch)
            ++notExactCount;

        if (d &lt; 10 ||
                d &lt; 100 && d % 10 == 0 ||
                d % 100 == 0) {
            System.out.printf(FORMAT, n, d, 
                    quotBound10_3, quotBound301, quotBound2322, nineDivPow2,
                    exactMatch ? "" : "not exact");
        }
        if (quotBound2322 &lt; nineDivPow2)
            throw new RuntimeException("didn't fit!");
        if (quotBound2322 - nineDivPow2 > 1)
            throw new RuntimeException("difference of more than one");

    }
    System.out.printf("total not exact:%s\n", notExactCount);
}

}

MTH
  • 43