The HotDocs Computation Archive
Get Extra Help

0116 - (Even) Smarter Fraction Formatting

Description:

Formats any decimal value as its fraction; omits the fraction portion for whole numbers.


• Explanation •

Problem. HotDocs' "9 1/8" fraction formatting is limited to eighths (and multiples thereof). Thus it forces all decimal values into one of the following fractions: 1/8, 1/4, 3/8, 1/2, 5/8, 3/4, 7/8. There is not a format that accomodates anything but eighths. Further, HotDocs will include "0/8" if your number is whole.

Solution. These computations will format any decimal value as its representative fraction. So .1 becomes 1/10, .67 becomes 2/3, and .09 becomes 9/100. Your number is then returned as a text string formatted as "1 2/3". If there is no decimal value, the fraction will be omitted.

Both computations will work as-is. They look daunting, but the work has been done for you. As long as you create the required elements, you can simply copy and paste the computations.

The computations require the looping mechanism described in Computation #0015: Loops via REPEAT. Please refer to that page for some caveats about this method.

Differences. The first computation is the original "Smarter Fraction Formatter." It will work up to two decimal places. Its highest precision is 1/100. It is included here as a fairly accessible model of how this type of computation is performed.

The second computation, the "(Even) Smarter Fraction Formatter," uses a much more sophisticated algorithm to produce the highest precision available in HotDocs: 1/1000000000 (one one-billionth). Note that it is no harder to use than the first. Just copy and paste.

See also, Computation #0051: Format a Number as "nine point seven five", #0052: Format a Fraction as "four and one half", #0062: "All (100%)", #0150: Decimal Formatter.


•  •  •  •  •  •  •


• Code •

Original Computation:

""
// Round to two decimal places
SET Temp-t TO "«ROUND( NumVar, 2 ):9.00»"

// Numerator is the decimal value
SET Numerator TO INTEGER( LAST( Temp-t, 2 ) )

// No decimal part. Just return their number.
IF Numerator = 0
   "«NumVar:9»"

// There is a decimal. Find the fraction.
ELSE

   // Special handling: 1/3
   IF Numerator = 33
      SET Numerator TO 1
      SET Denominator TO 3

   // Special handling: 2/3
   ELSE IF Numerator = 66 OR Numerator = 67
      SET Numerator TO 2
      SET Denominator TO 3

   // Find the fraction
   ELSE

      // Denominator is 100
      SET Denominator TO 100
	  
      // Set up a loop
      SET LoopLimit[ 100 ] TO "x"
      ASK NONE
      REPEAT Loop
	  
         // Count backward from 100, looking for greatest common multiples
         SET Temp-n TO 101 - COUNTER

         // Is this a common multiple?
         IF REMAINDER( Numerator , Temp-n ) = 0
         AND REMAINDER( Denominator , Temp-n ) = 0

            // Yes! Divide both by the multiple
            SET Numerator TO Numerator / Temp-n
            SET Denominator TO Denominator / Temp-n

         END IF
      END REPEAT

      // Clean up the loop
      REPEAT Loop
	     SET LoopLimit TO UNANSWERED
      END REPEAT
      ASK DEFAULT
   END IF

   // Format the fraction
   "«NumVar:9» «Numerator»/«Denominator»"
END IF

• Explanation •

Required Elements:

The Nitty-Gritty. The computation first rounds your number to two decimal places and puts this value into a temporary text variable, Temp-t. This is necessary to extract the decimal portion. We can grab the decimal portion by just taking the LAST two characters of Temp-t and converting them to an integer. This will now be our numerator (e.g. .25 = 25/100).

At this point there is no need to continue if there is no numerator, so if that is the case the computation will simply return the integer portion of NumVar. But if there is a numerator, we should examine it to see if we have either 1/3 or 2/3, since these cannot be produced by our loop (they would end up as 33/100 and 33/50, respectively). If the numerator is 33, we can just change it to 1 and make the denominator 3 and be done. Or if the numerator is 66 or 67 (in the event of rounding), we make the numerator 2 and the denominator 3.

Failing the previous tests, we now are forced to come up with the fraction on our own. The starting value of the fraction is n/100. This will almost always need to be reduced (e.g. 50/100 can be reduced to 1/2). We do this by counting down backwards from 100, trying to find the greatest common multiples for the numerator and the denominator. Each time we find a common multiple (i.e. there is no remainder when they are divided by the number) we perform the division to reduce the numbers. By the time our count has reached 1, we have effectively reduced our original fraction.

Finally, we return the formatted number in the form 1 2/3. You can tweak the formatting to your liking.

Contributor: LegalCS


•  •  •  •  •  •  •


• Code •

High-Precision Computation:

""
SET Numerator-n TO NumVar - TRUNCATE(NumVar, 0 )
SET WholeNumber-n TO TRUNCATE(NumVar, 0 )

// No decimal part. Just return the number.
IF Numerator-n = 0
   "«NumVar:9»"

// There is a decimal. Find the fraction.
ELSE
   // Initialize vars
   SET Test-n TO Numerator-n 
   SET FractionSet-b TO FALSE

   // Check to see if value > 1/2 (algorithm only works below 1/2)
   // If value > 1/2, find result for (1 - value)
   IF Test-n > 0.5
      SET GreaterThanHalf-b TO TRUE
      SET Test-n TO 1 - Test-n
   ELSE
      SET GreaterThanHalf-b TO FALSE
   END IF

   // Set up a loop
   SET LoopLimit[ 10 ] TO "x"
   SET Temp-n TO Test-n
   ASK NONE
   REPEAT Loop
          
      // Run 10 iterations, looking for least 
      // common denominator
      IF FractionSet-b = FALSE
         SET Denominator-n TO  ROUND((1 / Temp-n), 8)

         // Is this a common denominator?
         // If NumVar has 10 digit precision (i.e., database variable
         // or combined HotDocs vars entered by user), use this test
         IF ABSOLUTE VALUE(Denominator-n - ROUND(Denominator-n, 0)) / (MIN(Denominator-n, 100)) < 0.00125
		 
         // If NumVar is limited to 5 digit precision (i.e., standard 
         // single HotDocs var entered by user), use this test
         // IF ABSOLUTE VALUE(Denominator-n - ROUND(Denominator-n, 0)) / (MIN(Denominator-n, 50)) < 0.0075

            // Success! Set condition as true
            SET FractionSet-b TO TRUE
            SET LoopAnswered-n TO 1
            SET LoopFraction-n TO 1
  
         ELSE
            // Failure, prepare for next iteration
            SET Temp-n TO Denominator-n - TRUNCATE(Denominator-n, 0)
            IF Temp-n > 0.5
                SET Temp-n TO 1 - Temp-n
            END IF 
            SET LoopFraction-n TO Temp-n
            SET LoopAnswered-n TO 1
         END IF
      ELSE
         SET LoopResult-n TO 1
      END IF
   END REPEAT
  
   // Now do the loop in reverse
   REPEAT Loop
      SET Counter-n TO 11 - COUNTER
      SET Answered-n TO LoopAnswered-n[ Counter-n ] 
      IF ANSWERED( Answered-n )
         // Pull result from previous iteration
         SET Counter-n TO Counter-n + 1
         IF Counter-n = 11
            SET Temp-n TO 1
         ELSE        
            SET Temp-n TO LoopResult-n[ Counter-n ] 
         END IF
         SET Counter-n TO Counter-n - 1
         // Divide by the fraction in this iteration
         SET Temp-n TO ROUND( Temp-n / LoopFraction-n[ Counter-n ], 0 )
         // Set result for this iteration
         SET LoopResult-n[ Counter-n ] TO Temp-n
      END IF
   END REPEAT

   //  Set numerator and denominator
   SET Numerator TO ROUND(LoopResult-n[ 1 ], 0)
   SET Denominator TO ROUND(Numerator / Test-n, 0)
   // Clean up the loop
   REPEAT Loop
      SET LoopLimit TO UNANSWERED
      SET LoopFraction-n TO UNANSWERED
      SET LoopAnswered-n TO UNANSWERED
      SET LoopResult-n TO UNANSWERED
   END REPEAT
   ASK DEFAULT

   IF FractionSet-b = FALSE
      //Did not work, set to "nnn/1000"
      SET Test-n TO Test-n * 1000
      SET Numerator TO ROUND(Test-n, 0)
      SET Denominator TO 1000

      // See if numerator and denominator are both
      // divisible by prime factors of 1000, 2*2*2*5*5*5
      IF REMAINDER(Numerator, 2) = 0
      AND REMAINDER(Denominator, 2) = 0
         SET Numerator TO Numerator / 2
         SET Denominator TO Denominator / 2
      END IF
      IF REMAINDER(Numerator, 2) = 0
      AND REMAINDER(Denominator, 2) = 0
         SET Numerator TO Numerator / 2
         SET Denominator TO Denominator / 2
      END IF
      IF REMAINDER(Numerator, 2) = 0
      AND REMAINDER(Denominator, 2) = 0
         SET Numerator TO Numerator / 2
         SET Denominator TO Denominator / 2
      END IF
      IF REMAINDER(Numerator, 5) = 0
      AND REMAINDER(Denominator, 5) = 0
         SET Numerator TO Numerator / 5
         SET Denominator TO Denominator / 5
      END IF
      IF REMAINDER(Numerator, 5) = 0
      AND REMAINDER(Denominator, 5) = 0
         SET Numerator TO Numerator / 5
         SET Denominator TO Denominator / 5
      END IF
      IF REMAINDER(Numerator, 5) = 0
      AND REMAINDER(Denominator, 5) = 0
         SET Numerator TO Numerator / 5
         SET Denominator TO Denominator / 5
      END IF
   END IF

   //If value was > .5, invert numerator result
   IF GreaterThanHalf-b
      SET Numerator TO Denominator - Numerator  
   END IF

   // Format the fraction
   "«WholeNumber-n» «Numerator:9999»/«Denominator:9999»"
END IF

• Explanation •

Required Elements: (Note: All variables but NumVar are temporary variables. Set their Advanced options to "Ask only in dialog," "Don't warn if unanswered," and "Don't save in answer file")

This "(Even) Smarter Fraction" computation will convert a decimal to a fraction for any numerator and denominator, denominator up to the limit of the precision of the HotDocs variable being tested. If the tested variable has ten digit precision (the HotDocs maximum precision), both the numerator and denominator can (theoretically) be between 1 and 10,000,000,000. However, even with 10 digit precision, results get unpredictable below 1/100000 and above 99999/100000.

If the computation is unable to come up with a decimal using its algorithm, it will convert the decimal to "nnn/1000", and then reduce by any applicable prime factors of both nnn and 1000.

Thanks to the Computation Archive for the original "Smarter Fraction" computation and for the structure used in this computation.

A Note on 1/3 and 2/3. This computation automatically recognizes 1/3 and 2/3, but only at three decimal places. Note the fractions produced by each of the following values:


   .300  ->  3/10
   .330  ->  33/100
   .333  ->  1/3
   .600  ->  3/5
   .660  ->  33/50
   .666  ->  2/3

If your variable is limited to two decimal places, you will need to do a special check for .33 and .66. The technique is demonstrated in the original "Smarter Fraction" computation above.

Contributor: Benjamin Reich, Esquire

 

• Contributors •

LegalCS   Benjamin Reich, Esquire
1103 Coventry Avenue
Cheltenham, PA 19012
(215) 635-1705
LawBase and HotDocs Consulting