Rational and VFP Numbers: Difference between revisions
(9 intermediate revisions by 3 users not shown) | |||
Line 1: | Line 1: | ||
===Introduction=== | ===Introduction=== | ||
<p><b>Rational numbers</b> complement the 64-bit integer datatype to provide infinite precision (or <apll>WS FULL</apll>) at the cost of some performance. Similarly, <b>Variable-precision Floating Point</b> (VFP) numbers complement the 64-bit floating point datatype to provide more precision (as much as the user cares to specify) again with some cost in performance.</p> | <p><b>Rational numbers</b> complement the existing 64-bit integer datatype to provide infinite precision (or <apll>WS FULL</apll>) at the cost of some performance. Similarly, <b>Variable-precision Floating Point</b> (VFP) numbers complement the existing 64-bit floating point (IEEE-754) datatype to provide more precision (as much as the user cares to specify) again with some cost in performance.</p> | ||
<p>There is no separate datatype for infinite precision Integers. Instead, they are represented as a special case of rational numbers. In the discussion below, the phrase <b>rational integer</b> means a rational number whose denominator is one.</p> | <p>There is no separate datatype for infinite precision Integers. Instead, they are represented as a special case of rational numbers. In the discussion below, the phrase <b>rational integer</b> means a rational number whose denominator is one.</p> | ||
Line 9: | Line 9: | ||
===Rationale=== | ===Rationale=== | ||
<p>Simply put: precision. If the 53 bits of precision in the floating point result of, say, <apll>2÷3</apll> is not enough, you now have two more choices: one as an exact number, and one as a | <p>Simply put: precision. If the 53 bits of precision in the floating point result of, say, <apll>2÷3</apll> is not enough, you now have two more choices: one as an exact number, and one as a binary floating point number with as much precision as you care to specify.</p> | ||
<p>For example,</p> | <p>For example,</p> | ||
<apll> | <apll><pre> | ||
⎕PP←60 ⋄ ⎕FPC←512 | |||
2÷3 | |||
0.6666666666666666 | |||
0.666666666666666666666666666666666666666666666666666666666667 | 2÷3<pn>x</pn> | ||
2<pn>r</pn>3 | |||
1. | 2÷3<pn>v</pn> | ||
0.666666666666666666666666666666666666666666666666666666666667 | |||
1606938044258990275541962092341162602522202993782792835301376 | 2*200 | ||
1.6069380442589903<pn>E</pn>60 | |||
3.141592653589793< | 2*200<pn>x</pn> | ||
1606938044258990275541962092341162602522202993782792835301376 | |||
3.14159265358979323846264338327950288419716939937510582097494</apll> | ○1 | ||
3.141592653589793 | |||
○1<pn>v</pn> | |||
3.14159265358979323846264338327950288419716939937510582097494</pre></apll> | |||
===Constants=== | ===Constants=== | ||
<p>Rational constants may be entered as follows:</p> | <p>Rational constants may be entered by suffixing an integer constant with an <apll><pn>x</pn></apll> (for a rational integer) or separating the numerator and denominator with an <apll><pn>r</pn></apll> (for a rational number) as follows:</p> | ||
* <apll> | * <apll>123<pn>x</pn></apll> for the constant <apll>123</apll> (the suffix <apll><pn>x</pn></apll> is but a shorthand for <apll><pn>r</pn>1</apll>) | ||
* <apll> | * <apll>123<pn>r</pn>4567</apll> for the constant <apll>123÷4567</apll> | ||
<p>VFP constants may be entered as follows:</p> | <p>VFP constants may be entered by suffixing the integer or floating point constant with a <apll><pn>v</pn></apll> as follows:</p> | ||
* <apll> | * <apll>123<pn>v</pn></apll> for the constant <apll>123</apll> | ||
* <apll> | * <apll>123.4567<pn>v</pn></apll> for the constant <apll>123.4567</apll> | ||
* <apll> | * <apll>123.4567e3<pn>v</pn></apll> for the constant <apll>123.4567<pn>E</pn>3</apll> | ||
<p>The above formats for constants (except for the suffix <apll>x</apll>) may be used in other constants such as <apll> | <p>The above formats for constants (except for the suffix <apll><pn>x</pn></apll>) may be used in other constants such as <apll>1<pn>r</pn>4<pn>p</pn>2</apll> to generate a shorter and more accurate value for <apll>π<sup>2</sup>/4</apll> than, say, <apll>((○1)*2)÷4</apll>.</p> | ||
===Precision=== | ===Precision=== | ||
Line 48: | Line 51: | ||
<p>VFP numbers have <b>user-controlled variable precision</b>, and each number may have a different precision. The default precision at startup is controlled by the value of the system variable <apll>⎕FPC</apll>. The system default of this value is <apll>128</apll> in units of bits of precision of the mantissa (the digits) of the number, not counting the exponent (which is of fixed size). The current precision may be changed as needed by assigning a new value to the system variable. All newly created VFP numbers will have the new precision – the precision of VFP numbers already present in the workspace does not change.</p> | <p>VFP numbers have <b>user-controlled variable precision</b>, and each number may have a different precision. The default precision at startup is controlled by the value of the system variable <apll>⎕FPC</apll>. The system default of this value is <apll>128</apll> in units of bits of precision of the mantissa (the digits) of the number, not counting the exponent (which is of fixed size). The current precision may be changed as needed by assigning a new value to the system variable. All newly created VFP numbers will have the new precision – the precision of VFP numbers already present in the workspace does not change.</p> | ||
<p>Generally, precision is set once for a particular application and unchanged thereafter. Although not recommended, it is possible to mix VFP numbers of different precisions in a single array – presumably you <b>really</b> know what you are doing. The system function <apll>⎕DR</apll> may be used to display an array's precision(s).</p> | <p>Generally, precision is set once for a particular application and unchanged thereafter. Although not recommended, it is possible to mix VFP numbers of different precisions in a single array – presumably you <b>really</b> know what you are doing. The system function <apll>0 ⎕DR</apll> may be used to display an array's precision(s).</p> | ||
===Datatype Propagation=== | ===Datatype Propagation=== | ||
Line 56: | Line 59: | ||
<p>An example from the programming problems site [http://projecteuler.net/ ProjectEuler.net] illustrates this point. [http://projecteuler.net/problem=48 Problem #48] asks what are the low-order ten digits of the sum of the first thousand instances of N<sup>N</sup>?</p> | <p>An example from the programming problems site [http://projecteuler.net/ ProjectEuler.net] illustrates this point. [http://projecteuler.net/problem=48 Problem #48] asks what are the low-order ten digits of the sum of the first thousand instances of N<sup>N</sup>?</p> | ||
<p>The obvious expression <apll>¯10↑⍕+/*⍨⍳1000</apll> at first sight seems to solve the problem until you realize that it quickly runs afoul of the limited precision of 64-bit integer and floating point numbers. Clearly, this is a problem for the infinite precision of rational integers.</p> | <p>The obvious expression <apll>¯10↑⍕+/*⍨⍳1000</apll>, at first sight, seems to solve the problem until you realize that it quickly runs afoul of the limited precision of 64-bit integer and floating point numbers. Clearly, this is a problem for the infinite precision of rational integers.</p> | ||
<p>As <apll>⍳1000</apll> generates the first thousand integers as an integer datatype (actually an Arithmetic Progression Array), <apll> | <p>As <apll>⍳1000</apll> generates the first thousand integers as an integer datatype (actually an Arithmetic Progression Array), <apll>⍳1000<pn>x</pn></apll> generates the same values as rational integers. Next, <apll>*⍨⍳1000<pn>x</pn></apll> generates the first thousand instances of N<sup>N</sup> as exact rational integers, and unlike its integer counterpart, there is no overflow to floating point, just an increase in precision (as well as space used in the workspace). Then, <apll>+/*⍨⍳1000<pn>x</pn></apll> sums them into a single 3001-digit rational integer, and finally <apll>¯10↑⍕+/*⍨⍳1000<pn>x</pn></apll> converts the large integer to characters and extracts the low-order ten digits — <apll>9110846700</apll> — all in a small number of milliseconds.</p> | ||
<p>Note how we started with an obvious expression that failed because of its limited precision, and made a single change to suffix the constant <apll>1000</apll> with an <apll>x</apll> to convert it to a rational integer which then propagates through the calculation with infinite precision to yield the correct result.</p> | <p>Note how we started with an obvious expression that failed because of its limited precision, and made a single change to suffix the constant <apll>1000</apll> with an <apll><pn>x</pn></apll> to convert it to a rational integer which then propagates through the calculation with infinite precision to yield the correct result.</p> | ||
===Display=== | ===Display=== | ||
<p>Rational integers are displayed as an integer with no special adornment; rational non-integers are displayed as a numerator and denominator separated by an <apll>r</apll> as in <apll> | <p>Rational integers are displayed as an integer with no special adornment; rational non-integers are displayed as a numerator and denominator separated by an <apll><pn>r</pn></apll> as in <apll>34<pn>r</pn>9</apll>. As with the integer datatype, the numerator and denominator of a rational number are displayed exactly, unaffected by the current setting for Printing Precision (<apll>⎕PP</apll>).</p> | ||
<br /> | <br /> | ||
<apll> | <apll><pre> | ||
8. | !40 | ||
8.159152832478977<pn>E</pn>47 | |||
815915283247897734345611269596115894272000000000 | !40<pn>x</pn> | ||
815915283247897734345611269596115894272000000000 | |||
1 | +\÷⍳10<pn>x</pn> | ||
</apll> | 1 3<pn>r</pn>2 11<pn>r</pn>6 25<pn>r</pn>12 137<pn>r</pn>60 49<pn>r</pn>20 363<pn>r</pn>140 761<pn>r</pn>280 7129<pn>r</pn>2520 7381<pn>r</pn>2520 | ||
</pre></apll> | |||
<p>VFP numbers are displayed as decimal numbers to the precision inherent in the number or <apll>⎕PP</apll>, whichever is smaller, just as floating point numbers are displayed. For example,</p> | <p>VFP numbers are displayed as decimal numbers to the precision inherent in the number or <apll>⎕PP</apll>, whichever is smaller, just as floating point numbers are displayed. For example,</p> | ||
<apll> | <apll><pre> | ||
⎕PP←100 | |||
⎕FPC←64 | |||
3.141592653589793< | ○1 | ||
3.141592653589793 | |||
3.14159265358979323851 | ○1<pn>x</pn> | ||
3.14159265358979323851 | |||
⎕FPC←128 | |||
3.141592653589793238462643383279502884195</ | ○1<pn>x</pn> | ||
3.141592653589793238462643383279502884195</pre></apll> | |||
<p>where both of the above displays were limited by the precision of the number, not <apll>⎕PP</apll>.</p> | <p>where both of the above displays were limited by the precision of the number, not <apll>⎕PP</apll>.</p> | ||
<p>However, the first of the following displays <b>is</b> limited by <apll>⎕PP</apll> | <p>However, the first of the following displays <b>is</b> limited by <apll>⎕PP</apll>:</p> | ||
<apll> | <apll><pre> | ||
⎕FPC←128 | |||
⎕PP←20 | |||
!40<pn>v</pn> | |||
81591528324789773435____________________________ | |||
⎕PP←80 | |||
!40<pn>v</pn> | |||
8159152832478977343456112695961158942720________</pre></apll> | |||
<p | <p>In the last display, the current setting of Printing Precision is large enough, but the current setting of the Floating Point Control (<apll>⎕FPC</apll>) whose value is in bits is too small, so the display is truncated.</p> | ||
===Formatted Display=== | ===Formatted Display=== | ||
Line 108: | Line 110: | ||
<p>The system function <apll>⎕FMT</apll> has been enhanced to allow formatting of rational numbers via the (new) <apll>R</apll>-format specifier. For example,</p> | <p>The system function <apll>⎕FMT</apll> has been enhanced to allow formatting of rational numbers via the (new) <apll>R</apll>-format specifier. For example,</p> | ||
<apll> | <apll><pre> | ||
1 | 'R4.2' ⎕FMT ∘.÷⍨⍳6<_x/> | ||
2 | 1 1<_r/>2 1<_r/>3 1<_r/>4 1<_r/>5 1<_r/>6 | ||
3 | 2 1 2<_r/>3 1<_r/>2 2<_r/>5 1<_r/>3 | ||
4 | 3 3<_r/>2 1 3<_r/>4 3<_r/>5 1<_r/>2 | ||
5 | 4 2 4<_r/>3 1 4<_r/>5 2<_r/>3 | ||
6 | 5 5<_r/>2 5<_r/>3 5<_r/>4 1 5<_r/>6 | ||
6 3 2 3<_r/>2 6<_r/>5 1 </pre></apll> | |||
<p>Moreover, the Symbol Substitution (<apll>S<…></apll>) feature of <apll>⎕FMT</apll> allows you to substitute a different symbol for the default <apll> | <p>Moreover, the Symbol Substitution (<apll>S<…></apll>) feature of <apll>⎕FMT</apll> allows you to substitute a different symbol for the default <apll><_r/></apll> used to separate the numerator and denominator of a rational number, as in</p> | ||
<apll> | <apll><pre> | ||
1 | 'S<r/>R4.2' ⎕FMT ∘.÷⍨⍳6<_x/> | ||
2 | 1 1/2 1/3 1/4 1/5 1/6 | ||
3 | 2 1 2/3 1/2 2/5 1/3 | ||
4 | 3 3/2 1 3/4 3/5 1/2 | ||
5 | 4 2 4/3 1 4/5 2/3 | ||
6 | 5 5/2 5/3 5/4 1 5/6 | ||
6 3 2 3/2 6/5 1 </pre></apll> | |||
===Datatype Promotion=== | ===Datatype Promotion=== | ||
Line 130: | Line 134: | ||
<p>For the most part, rational numbers beget rational numbers and VFP numbers beget VFP numbers. However, when irrational, transcendental, and certain other functions are used, rational numbers beget VFP numbers. For example,</p> | <p>For the most part, rational numbers beget rational numbers and VFP numbers beget VFP numbers. However, when irrational, transcendental, and certain other functions are used, rational numbers beget VFP numbers. For example,</p> | ||
<apll> | <apll><pre> | ||
2.718281828459045< | *1 | ||
2.718281828459045 | |||
2. | *1<pn>x</pn> | ||
2.718281828459045235360287471352662497759</pre></apll> | |||
<p>where the datatype of the two results are floating point and VFP, respectively. That is, in a manner similar to how some primitive functions with integer arguments may return floating point results | <p>where the datatype of the two results are floating point and VFP, respectively. That is, in a manner similar to how some primitive functions with integer arguments may return floating point results when a rational number is used as an argument to a primitive function that can't return a result with infinite precision, it returns a VFP number.</p> | ||
<p class="note">The reason irrational, transcendental, and certain other functions on rational numbers | <p class="note">The reason irrational, transcendental, and certain other functions on rational numbers do not return rational numbers is that, by definition, the result of such a function is, in general, not representable as a rational number; instead, VFP numbers are better suited to represent irrational results where the end user may control exactly how much precision is desired in an obviously inexact number.</p> | ||
<p>Two special functions are the prime decomposition (<apll>πR</apll>)/number theoretic (<apll>LπR</apll>) functions. In these cases, fractional or VFP right arguments are converted to integers or rational integers, respectively, which is the datatype of the result except for <apll>0πR</apll> (Primality Test) which always returns a Boolean result regardless of the type of <apll>R</apll>.</p> | <p>Two special functions are the prime decomposition (<apll>πR</apll>)/number theoretic (<apll>LπR</apll>) functions. In these cases, fractional or VFP right arguments are converted to integers or rational integers, respectively, which is the datatype of the result except for <apll>0πR</apll> (Primality Test) which always returns a Boolean result regardless of the type of <apll>R</apll>.</p> | ||
Line 167: | Line 172: | ||
<p>It is common in APL implementations to demote datatypes where appropriate. For example, the constant <apll>1.0</apll> might actually be represented as an integer or even Boolean datatype. The idea is there is no loss of precision and the storage is typically smaller which might lead to a more efficient algorithm when next used, so why not?</p> | <p>It is common in APL implementations to demote datatypes where appropriate. For example, the constant <apll>1.0</apll> might actually be represented as an integer or even Boolean datatype. The idea is there is no loss of precision and the storage is typically smaller which might lead to a more efficient algorithm when next used, so why not?</p> | ||
<p class="note">With rational and VFP numbers those reasons no longer apply. While the constant <apll> | <p class="note">With rational and VFP numbers those reasons no longer apply. While the constant <apll>1<pn>x</pn></apll> might have the same precision as the constant <apll>1.0</apll>, the difference in latent precision between the two is vast. In fact, in order for datatype propagation of rational and VFP numbers to work at all, we must be careful <b>not</b> to demote them automatically to a smaller datatype. Otherwise, it would require an intolerable degree of analysis on the part of the programmer to ensure that the desired datatype (rational or VFP) remains in effect throughout a calculation.</p> | ||
===Conversions=== | ===Conversions=== | ||
<p>To convert manually from one datatype to another,</p> | <p>To convert manually from one datatype to another, use the system function <apll>⎕DC</apll> to convert any numeric datatype</p> | ||
* Integer | * To 64-bit Integer, use <apll>'i' ⎕DC R</apll> | ||
* To 64-bit Floating Point, use <apll>'f' ⎕DC R</apll> | |||
* | * To Multiple Precision Integer/Rational, use <apll>'r' ⎕DC R</apll> | ||
* Rational | * To Multiple Precision Floating point, use <apll>'v' ⎕DC R</apll> | ||
* | |||
===Comparisons=== | ===Comparisons=== | ||
Line 186: | Line 189: | ||
<li><p>Comparisons between a rational number and a floating point number convert both arguments to VFP numbers and compare the two as below.</p></li> | <li><p>Comparisons between a rational number and a floating point number convert both arguments to VFP numbers and compare the two as below.</p></li> | ||
<li><p>Comparisons between a VFP number and any other number is sensitive to <apll>⎕CT</apll> — just as they are between | <li><p>Comparisons between a VFP number and any other number is sensitive to the current setting of Comparison Tolerance (<apll>⎕CT</apll>) — just as they are between floating point numbers.</p></li> | ||
</ul> | </ul> | ||
Line 194: | Line 197: | ||
===Integer Tolerance=== | ===Integer Tolerance=== | ||
<p>Both rational and VFP numbers may be used where the system ordinarily requires an integer (such axis coordinates, indexing, left argument to structural primitives, etc.) just as the system tolerates floating point numbers in those contexts if they are sufficiently near an integer. In all cases, the system attempts to convert the non-integer to an integer using the fixed system comparison tolerance (at the moment, <apll> | <p>Both rational and VFP numbers may be used where the system ordinarily requires an integer (such axis coordinates, indexing, left argument to structural primitives, etc.) just as the system tolerates floating point numbers in those contexts if they are sufficiently near an integer. In all cases, the system attempts to convert the non-integer to an integer using the fixed system comparison tolerance (at the moment, <apll>3<pn>E</pn>¯15</apll>).</p> | ||
===Infinities=== | ===Infinities=== | ||
Line 200: | Line 203: | ||
<p>Support for <apll>±∞</apll> has been extended to rational and VFP numbers in the same manner as it applies to 64-bit integers and 64-bit floats. That is, the same cases covered by the system variable <apll>⎕IC</apll> (Indeterminate Control) also apply to infinite rational and VFP numbers. Moreover, infinite numeric constants may be entered, for example, as</p> | <p>Support for <apll>±∞</apll> has been extended to rational and VFP numbers in the same manner as it applies to 64-bit integers and 64-bit floats. That is, the same cases covered by the system variable <apll>⎕IC</apll> (Indeterminate Control) also apply to infinite rational and VFP numbers. Moreover, infinite numeric constants may be entered, for example, as</p> | ||
* <apll> | * <apll>∞<pn>x</pn></apll> | ||
* <apll> | * <apll>∞<pn>r</pn>1</apll> | ||
* <apll> | * <apll>∞<pn>v</pn></apll> | ||
* <apll> | * <apll>∞<pn>v</pn>0</apll> | ||
<p>Also constants such as <apll> | <p>Also constants such as <apll>2<pn>r</pn>∞</apll> resolve to <apll>0<pn>x</pn></apll>.</p> | ||
===New And/Or Different Behavior=== | ===New And/Or Different Behavior=== | ||
<ul> | <ul> | ||
<li><p>Both roll (<apll>?R</apll>) and deal (<apll>L?R</apll>) on rational integers use a built-in random number generator so as to use the entire range of rational integers – this algorithm uses its own internal seeds that are much more complicated than the simple integer seed that is <apll>⎕RL</apll>. Thus <apll>⎕RL</apll> is unchanged by these functions on rationals.</p> | <li><p>Both roll (<apll>?R</apll>) and deal (<apll>L?R</apll>) on rational integers use a built-in random number generator so as to use the entire range of rational integers – this algorithm uses its own internal seeds that are much more complicated than the simple integer seed that is <apll>⎕RL</apll> (Random Link). Thus <apll>⎕RL</apll> is unchanged by these functions on rationals.</p> | ||
<p>For example, if you need <b>really</b> large random numbers</p> | <p>For example, if you need <b>really</b> large random numbers</p> | ||
<apll> | <apll><pre> | ||
370857192605742854709703007683731949504799559659692534573173</apll> | ?10*60<pn>x</pn> | ||
370857192605742854709703007683731949504799559659692534573173</pre></apll> | |||
</li> | </li> | ||
<li><p>Matrix inverse (<apll>⌹R</apll>) and matrix division (<apll>L⌹R</apll>) on rational or VFP arguments each have two limitations above and beyond that of normal conformability:</p> | <li><p>Matrix inverse (<apll>⌹R</apll>) and matrix division (<apll>L⌹R</apll>) on rational or VFP arguments each have two limitations above and beyond that of normal conformability:</p> | ||
Line 242: | Line 246: | ||
<p>The designers of J are thanked for having the foresight to include [http://www.jsoftware.com/help/dictionary/dictg.htm rational numbers] as a separate datatype.</p> | <p>The designers of J are thanked for having the foresight to include [http://www.jsoftware.com/help/dictionary/dictg.htm rational numbers] as a separate datatype.</p> | ||
<p>The following | <p>The following LGPL libraries have been used to provide support for these datatypes:</p> | ||
* MPIR (Multiple Precision Integers and Rationals) at [http://www.mpir.org mpir.org]. | * MPIR (Multiple Precision Integers and Rationals) at [http://www.mpir.org mpir.org]. | ||
* MPFR (Multiple Precision Floating-Point Reliable Library ) at [http://www.mpfr.org mpfr.org]. | * MPFR (Multiple Precision Floating-Point Reliable Library ) at [http://www.mpfr.org mpfr.org]. | ||
===References=== | |||
<p>For a PDF version of this page, view it [http://www.sudleyplace.com/APL/Rational%20&%20Variable-Precision%20FP.pdf here].</p> |
Latest revision as of 11:57, 17 October 2019
Introduction
Rational numbers complement the existing 64-bit integer datatype to provide infinite precision (or WS FULL) at the cost of some performance. Similarly, Variable-precision Floating Point (VFP) numbers complement the existing 64-bit floating point (IEEE-754) datatype to provide more precision (as much as the user cares to specify) again with some cost in performance.
There is no separate datatype for infinite precision Integers. Instead, they are represented as a special case of rational numbers. In the discussion below, the phrase rational integer means a rational number whose denominator is one.
Throughout this discussion the similarity between integer and rational numbers as well as floating point and VFP numbers will become apparent.
Rationale
Simply put: precision. If the 53 bits of precision in the floating point result of, say, 2÷3 is not enough, you now have two more choices: one as an exact number, and one as a binary floating point number with as much precision as you care to specify.
For example,
⎕PP←60 ⋄ ⎕FPC←512 2÷3 0.6666666666666666 2÷3x 2r3 2÷3v 0.666666666666666666666666666666666666666666666666666666666667 2*200 1.6069380442589903E60 2*200x 1606938044258990275541962092341162602522202993782792835301376 ○1 3.141592653589793 ○1v 3.14159265358979323846264338327950288419716939937510582097494
Constants
Rational constants may be entered by suffixing an integer constant with an x (for a rational integer) or separating the numerator and denominator with an r (for a rational number) as follows:
- 123x for the constant 123 (the suffix x is but a shorthand for r1)
- 123r4567 for the constant 123÷4567
VFP constants may be entered by suffixing the integer or floating point constant with a v as follows:
- 123v for the constant 123
- 123.4567v for the constant 123.4567
- 123.4567e3v for the constant 123.4567E3
The above formats for constants (except for the suffix x) may be used in other constants such as 1r4p2 to generate a shorter and more accurate value for π2/4 than, say, ((○1)*2)÷4.
Precision
Rational numbers have infinite precision. They are stored with a separate numerator and denominator, both of which are exact numbers in the sense that their size (and hence precision) grows limited only by the available workspace.
VFP numbers have user-controlled variable precision, and each number may have a different precision. The default precision at startup is controlled by the value of the system variable ⎕FPC. The system default of this value is 128 in units of bits of precision of the mantissa (the digits) of the number, not counting the exponent (which is of fixed size). The current precision may be changed as needed by assigning a new value to the system variable. All newly created VFP numbers will have the new precision – the precision of VFP numbers already present in the workspace does not change.
Generally, precision is set once for a particular application and unchanged thereafter. Although not recommended, it is possible to mix VFP numbers of different precisions in a single array – presumably you really know what you are doing. The system function 0 ⎕DR may be used to display an array's precision(s).
Datatype Propagation
Generally, the datatype of constants propagates through a calculation. That is, if you start with a rational number and don't calculate with irrational or transcendental functions, you'll end up with a rational result, and if you start with a VFP number, you'll end up with a VFP result.
An example from the programming problems site ProjectEuler.net illustrates this point. Problem #48 asks what are the low-order ten digits of the sum of the first thousand instances of NN?
The obvious expression ¯10↑⍕+/*⍨⍳1000, at first sight, seems to solve the problem until you realize that it quickly runs afoul of the limited precision of 64-bit integer and floating point numbers. Clearly, this is a problem for the infinite precision of rational integers.
As ⍳1000 generates the first thousand integers as an integer datatype (actually an Arithmetic Progression Array), ⍳1000x generates the same values as rational integers. Next, *⍨⍳1000x generates the first thousand instances of NN as exact rational integers, and unlike its integer counterpart, there is no overflow to floating point, just an increase in precision (as well as space used in the workspace). Then, +/*⍨⍳1000x sums them into a single 3001-digit rational integer, and finally ¯10↑⍕+/*⍨⍳1000x converts the large integer to characters and extracts the low-order ten digits — 9110846700 — all in a small number of milliseconds.
Note how we started with an obvious expression that failed because of its limited precision, and made a single change to suffix the constant 1000 with an x to convert it to a rational integer which then propagates through the calculation with infinite precision to yield the correct result.
Display
Rational integers are displayed as an integer with no special adornment; rational non-integers are displayed as a numerator and denominator separated by an r as in 34r9. As with the integer datatype, the numerator and denominator of a rational number are displayed exactly, unaffected by the current setting for Printing Precision (⎕PP).
!40 8.159152832478977E47 !40x 815915283247897734345611269596115894272000000000 +\÷⍳10x 1 3r2 11r6 25r12 137r60 49r20 363r140 761r280 7129r2520 7381r2520
VFP numbers are displayed as decimal numbers to the precision inherent in the number or ⎕PP, whichever is smaller, just as floating point numbers are displayed. For example,
⎕PP←100 ⎕FPC←64 ○1 3.141592653589793 ○1x 3.14159265358979323851 ⎕FPC←128 ○1x 3.141592653589793238462643383279502884195
where both of the above displays were limited by the precision of the number, not ⎕PP.
However, the first of the following displays is limited by ⎕PP:
⎕FPC←128 ⎕PP←20 !40v 81591528324789773435____________________________ ⎕PP←80 !40v 8159152832478977343456112695961158942720________
In the last display, the current setting of Printing Precision is large enough, but the current setting of the Floating Point Control (⎕FPC) whose value is in bits is too small, so the display is truncated.
Formatted Display
The system function ⎕FMT has been enhanced to allow formatting of rational numbers via the (new) R-format specifier. For example,
'R4.2' ⎕FMT ∘.÷⍨⍳6x 1 1r2 1r3 1r4 1r5 1r6 2 1 2r3 1r2 2r5 1r3 3 3r2 1 3r4 3r5 1r2 4 2 4r3 1 4r5 2r3 5 5r2 5r3 5r4 1 5r6 6 3 2 3r2 6r5 1
Moreover, the Symbol Substitution (S<…>) feature of ⎕FMT allows you to substitute a different symbol for the default r used to separate the numerator and denominator of a rational number, as in
'S<r/>R4.2' ⎕FMT ∘.÷⍨⍳6x
1 1/2 1/3 1/4 1/5 1/6
2 1 2/3 1/2 2/5 1/3
3 3/2 1 3/4 3/5 1/2
4 2 4/3 1 4/5 2/3
5 5/2 5/3 5/4 1 5/6
6 3 2 3/2 6/5 1
Datatype Promotion
For the most part, rational numbers beget rational numbers and VFP numbers beget VFP numbers. However, when irrational, transcendental, and certain other functions are used, rational numbers beget VFP numbers. For example,
*1
2.718281828459045
*1x
2.718281828459045235360287471352662497759
where the datatype of the two results are floating point and VFP, respectively. That is, in a manner similar to how some primitive functions with integer arguments may return floating point results when a rational number is used as an argument to a primitive function that can't return a result with infinite precision, it returns a VFP number.
The reason irrational, transcendental, and certain other functions on rational numbers do not return rational numbers is that, by definition, the result of such a function is, in general, not representable as a rational number; instead, VFP numbers are better suited to represent irrational results where the end user may control exactly how much precision is desired in an obviously inexact number.
Two special functions are the prime decomposition (πR)/number theoretic (LπR) functions. In these cases, fractional or VFP right arguments are converted to integers or rational integers, respectively, which is the datatype of the result except for 0πR (Primality Test) which always returns a Boolean result regardless of the type of R.
Ignoring purely structural functions, the list of functions that produce VFP numbers given rational numbers is as follows:
- Power: *R and L*R (except when R is a 32-bit integer, in which case the result is a rational number)
- Logarithm: ⍟R and L⍟R
- Pi Times and Circle functions: ○R and L○R
- Root: √R and L√R
- Factorial and Binomial: !R and L!R (except when the arguments are rational integers, in which case the result is a rational integer)
Beyond the ones mentioned above, the list of functions that don't produce a rational or VFP result given those argument(s) is as follows:
- Depth: ≡ (Integer)
- Dyadic Comparison: = ≠ < ≤ ≥ > ≡ ≢ (Boolean)
- Nand and Nor: ⍲ ⍱ (Boolean)
- Grade Up/Down: ⍋ ⍒ (Integer)
- Index Of: ⍳ (Integer)
- Member Of: ∊ (Boolean)
- Find: ⍷ (Boolean)
- Subset and Superset: ⊆ ⊇ (Boolean)
- Format: ⍕ (Character)
Otherwise, rational argument(s) produce rational result(s) and VFP argument(s) produce VFP result(s).
Datatype Demotion
It is common in APL implementations to demote datatypes where appropriate. For example, the constant 1.0 might actually be represented as an integer or even Boolean datatype. The idea is there is no loss of precision and the storage is typically smaller which might lead to a more efficient algorithm when next used, so why not?
With rational and VFP numbers those reasons no longer apply. While the constant 1x might have the same precision as the constant 1.0, the difference in latent precision between the two is vast. In fact, in order for datatype propagation of rational and VFP numbers to work at all, we must be careful not to demote them automatically to a smaller datatype. Otherwise, it would require an intolerable degree of analysis on the part of the programmer to ensure that the desired datatype (rational or VFP) remains in effect throughout a calculation.
Conversions
To convert manually from one datatype to another, use the system function ⎕DC to convert any numeric datatype
- To 64-bit Integer, use 'i' ⎕DC R
- To 64-bit Floating Point, use 'f' ⎕DC R
- To Multiple Precision Integer/Rational, use 'r' ⎕DC R
- To Multiple Precision Floating point, use 'v' ⎕DC R
Comparisons
Comparisons between two rational numbers or a rational number and any other integer is exact — just as they are between integers.
Comparisons between a rational number and a floating point number convert both arguments to VFP numbers and compare the two as below.
Comparisons between a VFP number and any other number is sensitive to the current setting of Comparison Tolerance (⎕CT) — just as they are between floating point numbers.
That is, comparisons continue the analogy between integers and rationals as well as floats and VFPs.
Integer Tolerance
Both rational and VFP numbers may be used where the system ordinarily requires an integer (such axis coordinates, indexing, left argument to structural primitives, etc.) just as the system tolerates floating point numbers in those contexts if they are sufficiently near an integer. In all cases, the system attempts to convert the non-integer to an integer using the fixed system comparison tolerance (at the moment, 3E¯15).
Infinities
Support for ±∞ has been extended to rational and VFP numbers in the same manner as it applies to 64-bit integers and 64-bit floats. That is, the same cases covered by the system variable ⎕IC (Indeterminate Control) also apply to infinite rational and VFP numbers. Moreover, infinite numeric constants may be entered, for example, as
- ∞x
- ∞r1
- ∞v
- ∞v0
Also constants such as 2r∞ resolve to 0x.
New And/Or Different Behavior
Both roll (?R) and deal (L?R) on rational integers use a built-in random number generator so as to use the entire range of rational integers – this algorithm uses its own internal seeds that are much more complicated than the simple integer seed that is ⎕RL (Random Link). Thus ⎕RL is unchanged by these functions on rationals.
For example, if you need really large random numbers
?10*60x 370857192605742854709703007683731949504799559659692534573173
Matrix inverse (⌹R) and matrix division (L⌹R) on rational or VFP arguments each have two limitations above and beyond that of normal conformability:
for a square right argument that it be non-singular, and
for an overdetermined (>/⍴R) right argument that the symmetric matrix (⍉R)+.×R be non-singular.
These limitations are due to the algorithm (Gauss-Jordan Elimination) used to implement Matrix Inverse/Divide on rational and VFP numbers.
Integer and floating point arguments are not subject to these limitations because they use a more general algorithm (Singular Value Decomposition) that produces a unique result even for singular arguments (e.g., ⌹5 3⍴0).
Conclusions
The new datatypes offer several benefits:
- They extend the precision of existing integer and floating point datatypes to a much greater level.
- As integer blows up to floating point, rational blows up to VFP, providing a natural parallel progression for irrational and transcendental primitive functions.
- There is a close similarity between integer and rational numbers as well as floating point and VFP numbers.
- Datatype propagation without demotion allows one to code an algorithm in either of the new types easily and without the need for detailed analysis of the datatype in intermediate results.
- All primitives extend naturally to encompass the new types as numbers.
- The notation for constants builds on existing point notation formats.
Acknowledgments
The designers of J are thanked for having the foresight to include rational numbers as a separate datatype.
The following LGPL libraries have been used to provide support for these datatypes:
- MPIR (Multiple Precision Integers and Rationals) at mpir.org.
- MPFR (Multiple Precision Floating-Point Reliable Library ) at mpfr.org.
References
For a PDF version of this page, view it here.