XNS BigDecimal()

What Is It, Exactly?

As I have noted in the main page for XNS, JavaScript cannot accurately handle all fractions of a number. However, it can and does accurately handle integers in the IEEE 754 binary floating point system. The XNS BigDecimal() script takes advantage of this fact by breaking up a given number A into sections of digits, which have no decimal point in them. These sections are therefore integers by definition.

A valid XNS BigDecimal() number takes the format:

A = ( k = 0 a k * 10 - k * j ) * (-1 or 1), with j being a positive integer and 0 a k < 10 j , and a k being an integer .

The idea is A is the finished number. Suppose j = 1. Then the number π is 3.1415926535... Note the alternation between the bold-faced and the normal characters. Each section indicates a value of digits combined, which is itself a valid integer. In fact, with the exception of the decimal point and the minus sign, all such sections of decimal numbers are nonnegative integers.

Now suppose instead j = 4. Then the number π is 3.1415926535... It still matches the definition of a decimal number, but at the same time we have discrete blocks of digits within the number.

You may notice in the above examples that the first digit to the left of the decimal point is part of the same section as the first digit to the right. The decimal point is a fundamental feature of any number, and its position must be exactly determined. So suppose we rearranged our digits such that the decimal point was between sections instead of embedded in one. In this scenario, π is 3.1415926535...

With this arrangement of digits, we can then take each distinct section of digits and make it an element of an array: [3, 1415, 9265, 3500] represents the sequence of digits for π to 11 digits. We note the decimal point is after the 0 section of the array [3], and the number is not a negative number.

These three facts: the array of sectioned digits, the placement of the decimal, and whether or not the value is negative, are all which are required to define a decimal number. Thus, the three facts can be collected as properties of a BigDecimal() number type.

XNS has already defined a standard for BigDecimal.prototype.isNegative as a function. Therefore, we only need two additional properties: this.digitArray to hold the array of digit sections, and this.decPtAfter to specify where the decimal point resides.

How Accurate are BigDecimal() numbers?

As accurate as you want them to be. Because the arithmetic processes do not themselves depend on the number of sections in the BigDecimal(), you can have as many sections, and thus as many digits, as you desire.

The BigDecimal() script sets a default maximum length of approximately 100 digits, however. This value, reflected via the BigDecimal.maxDigits property, guarantees at least 100 significant digits, when such is needed.

As for within each section of digits, I have set the maximum number of digits per section at 6. The reason is simple: 999999 * 999999 = 999998000001, which has precisely 12 digits. Allowing for multiples of this number, we can safely reach 16 digits per section, before JavaScript's accuracy begins to falter. (To reach 17 digits, you'd need at least 10,000 instances of 9999999 * 9999999 entering into that section: a highly unlikely event except under extreme conditions.)

Rounding and Carrying Operations in BigDecimal() Numbers

In the process of arithmetic operations, a BigDecimal() number may be constructed which starts out valid, but quickly becomes invalid. For instance, we may have a digit section within the number which is negative. Or we may have a digit section which exceeds the maximum section value permissible. These are the cost of doing business with BigDecimal() numbers, and the BigDecimal() script provides two functions to convert an invalid BigDecimal() number into a valid one.

The first is the carryCompact() method of BigDecimal() number objects. It checks for several things:

  1. Whether the leftmost digit section is negative (in which case all sections must be multiplied by -1)
  2. Carrying the digits for a section that is out-of-bounds to the previous section (for instance, 12000 in a section where 9999 is the max, carrying 10000 to the next left section, and leaving 2000 behind)
  3. Adding in a new digit section if the leftmost digit section is itself out-of-bounds
  4. Removing digit sections from the two ends if those sections are equal to zero (0003 == 3)

The second is the compactNum() method of BigDecimal() number objects. It calls this.carryCompact() twice: once at the beginning of this.compactNum(), and once at the end. The reason for this is simple. the compactNum() function is specifically designed for rounding operations, in case the BigDecimal() number exceeds the maximum preset number of digits. (0.99999... == 1.0) This function takes several steps:

  1. It finds the first significant digit of the BigDecimal() number (basically the first non-zero digit)
  2. It advances past that digit by the maximum number of digits permissible.
  3. It rounds off the number to that digit.
  4. It cuts the length of this.digitArray to have no spare sections.

All four arithmetic methods call compactNum() on the BigDecimal() numbers they return, just prior to returning those numbers.

Comparison of Two BigDecimal() Numbers

The rules are pretty simple:

  1. If either number is NaN, return Number.NaN.
  2. If A is positive and B is negative, then A > B. (3 > -3)
  3. If A < 0 and B < 0, then -A > 0 and -B > 0. So we can temporarily flip the signs and reduce our testcase to two positive numbers.
  4. If the decimal point for a positve A comes after the decimal point for a positive B, then A > B. (3,000,000.000 > 3,000.000)
  5. If that doesn't resolve which is greater, check each digit section that the two share until one digit section is greater than its counterpart. (300,456 > 300,000.)
  6. If the two have equal sections until one of them runs out of sections, check to see if the other's remaining sections are all zeroes. If so, they are equal. If not, the one with extra sections is greater. (3,000.000456789 > 3,000.000456)
  7. If we flipped signs earlier, flip the comparison return value (if -1, return 1, and vice versa).

Addition, Subtraction, and Multiplication of Two BigDecimal() Numbers

In elementary addition and subtraction, we are taught to line up the decimal points, and add or subtract the digits that line up. The same thing applies to addition and subtraction of BigDecimals(). (The two processes are so similar, in fact, that the subtract() method truly calls the add() method, with a special second argument.)

Given two BigDecimal() numbers A and B, both positive and being added together, the following takes place:

  1. Create C as a clone of A.
  2. Let offset = A.decPtAfter - B.decPtAfter
  3. For each section k in B, C.digitArray[k + offset] += B.digitArray[k]

For subtraction, we invert the sign (+= becomes -=). When B is a negative number, we invert the sign again.

In elementary multiplication, we are also taught to shift the decimal point appropriately. Again, BigDecimal() does this, sort of. Given two BigDecimal() numbers A and B, and a response C,. we find C.decPtAfter == A.decPtAfter + B.decPtAfter.

As for performing the actual multiplication, we rely on a basic algebra theorem:

(a + b)(c + d) == ac + ad + bc + bd

Many English-speaking people call this "FOIL": First, Outer, Inner, Last. In BigDecimal() numbers, b and d may be later sections in the respective digitArray properties than a and c, and we shift them down further.

Given two BigDecimal() numbers A and B, both positive and being multiplied together, the following takes place:

  1. Create C with a value of 0.
  2. C.decPtAfter == A.decPtAfter + B.decPtAfter
  3. For each section h in A and for each section k in B, C.digitArray[h + k] += A.digitArray[h] * B.digitArray[k]

Division of Two BigDecimals()

Here, we take a diversion from typical arithmetic, for reasons of practicality. I actually cover this in a separate document (involving a fair amount of mathematical theory). The document is titled "Division of BigDecimals". The source code is listed below.

XNS BigDecimal()

function BigDecimal() {
   if (arguments.length > 0) {
      var numString = arguments[0]
      } else {
      this.isNegative = returnFalse
      this.digitArray = [0]
      this.decPtAfter = 0
      return this
      }

   if (typeof numString.xnsType == "undefined") {
      return new XNS_NaN()
      }

   switch (numString.xnsType) {
      case "NaN":
      return new XNS_NaN()

      case "Infinity":
      return new XNS_Infinity()

      case "-Infinity":
      return new XNS_NEG_Infinity()

      case "BigDecimal":
      this.decPtAfter = numString.decPtAfter * 1
      this.digitArray = numString.digitArray.join().split(",")
      if (numString.isNegative()) {
         this.isNegative = returnTrue
         } else {
         this.isNegative = returnFalse
         }
      break;

      case "Number":
      var source = numString.toString() // we want a pure string

      switch (source) {
         case "NaN":
         return new XNS_NaN()

         case "Infinity":
         return new XNS_Infinity()

         case "-Infinity":
         return new XNS_NEG_Infinity()
         break;
         }

      if (isNaN(source)) {
         return new XNS_NaN()
         break;
         }

      if (source.charAt(0) == "-") {
         this.isNegative = returnTrue
         source = source.substr(1)
         } else {
         this.isNegative = returnFalse
         }
      if (source.indexOf(".") == -1) {
         source+="."
         }
      if (source.charAt(0) == ".") {
         source = "0" + source
         }
      var wholeNum = source.substr(0, source.indexOf("."))
      var decimals = source.substr(source.indexOf(".") + 1)
      var wholeNum_shift = wholeNum.length % BigDecimal.sectDigits
      var digitArray = []
      if (wholeNum_shift != 0) {
         digitArray[0] = wholeNum.substr(0, wholeNum_shift)
         }
      digitArray = digitArray.concat(wholeNum.substr(wholeNum_shift).match(BigDecimal.regexpSplit))
      if (digitArray[digitArray.length - 1] == null) {
         digitArray.length--
         }
      var decPtAfter = digitArray.length - 1
      var decimal_back = decimals.length % BigDecimal.sectDigits
      digitArray = digitArray.concat(decimals.match(BigDecimal.regexpSplit));
      if (digitArray[digitArray.length - 1] == null) {
         digitArray.length--
         }
      if (decimal_back != 0) {
         digitArray[digitArray.length] = decimals.substr(decimals.length-decimal_back)
         while (digitArray[digitArray.length - 1].length < BigDecimal.sectDigits) {
            digitArray[digitArray.length - 1] += "0"
            }
         }
      this.digitArray = digitArray
      this.decPtAfter = decPtAfter
      this.compactNum() // automatically clean up number to restrict BigDecimal value
      break; // end case "Number" for numString.xnsType

      default:
      return new XNS_NaN()
      }

// return global variables

   return this
   }

Back to the XNS Main Page.

docs/BigDecimal.html version 0.5 created May 25, 2002