4.9 KiB
		
	
	
	
	
	
	
	
			
		
		
	
	| title | pagination_prev | pagination_next | 
|---|---|---|
| Rational Approximation | constellation/ssf | constellation/codepage | 
The SheetJS frac library computes rational approximations to floating point
numbers with bounded denominator. It is a core component in number formatting,
powering "Fraction with up to 1 digit" and related number formats.
The library is also available for standalone use on the SheetJS CDN1.
Source code and project documentation are hosted on the SheetJS Git server at https://git.sheetjs.com/sheetjs/frac
Live Demo
Formatted texts are calculated from the value and maximum denominators.
Please report an issue if a particular result does not align with expectations.
function SheetJSFrac() {
  const [val, setVal] = React.useState(0.699450515);
  const [text, setText] = React.useState("");
  if(typeof frac == "undefined") return ( <b>ERROR: Reload this page</b> );
  const fmt = arr => `${(""+arr[1]).padStart(5)} / ${(""+arr[2]).padEnd(5)}`;
  React.useEffect(() => {
    if(typeof frac == "undefined") return setText("ERROR: Reload this page!");
    let v = +val;
    if(!isFinite(v)) return setText(`ERROR: ${val} is not a valid number!`);
    try {
      fmt(frac(val, 9)); setText("");
    } catch(e) { setText("ERROR: " + (e && e.message || e)); }
  }, [val]);
  const n = { textAlign:"right" };
  const g = { backgroundColor:"#C6EFCE", color:"#006100", whiteSpace:"pre-wrap" };
  const b = { backgroundColor:"#FFC7CE", color:"#9C0006" };
  return ( <table>
    <tr><td><b>Number Value</b></td><td colspan="4">
      <input type="text" value={val} onChange={e => setVal(e.target.value)}/>
    </td></tr>
    <tr><td></td><th>Max Denom</th><th>Mediant</th><th>Continued Frac</th></tr>
    {[1,2,3,4,5].map(d => ( <tr>
      <td><b>Up to {d} Digit{d == 1 ? "" : "s"}</b></td>
      <td style={n}><code>{10**d - 1}</code></td>
      <td><code style={text?b:g}>{text||fmt(frac(val,10**d-1))}</code></td>
      <td><code style={text?b:g}>{text||fmt(frac.cont(val,10**d-1))}</code></td>
    </tr> ))}
  </table> );
}
API
In the browser, the library exports the frac global. In NodeJS, the library
default export is a function.
Algorithms
The "Mediant" algorithm (frac in the browser; the default export in NodeJS)
calculates the exact solution.
The "Continued Fractions" algorithm (frac.cont in the browser; the cont
field in the NodeJS export) calculates an approximate solution but has better
worst-case runtime performance.
Spreadsheet software use these algorithms to render number formats including
?/? and ??/??. The algorithm choices are summarized in the following table:
| Spreadsheet Software | Algorithm | 
|---|---|
| SheetJS | Continued Fractions | 
| Apple Numbers | Mediant Algorithm | 
| Google Sheets | Mediant Algorithm | 
| Lotus 1-2-3 | (unsupported) | 
| Microsoft Excel | Continued Fractions | 
| Quattro Pro | Continued Fractions | 
| WPS 电子表格 | Mediant Algorithm | 
:::danger LibreOffice bugs
There are known rounding errors in LibreOffice2 which result in inaccurate fraction calculations.
The LibreOffice developers believe these numerical errors are desirable:
"We ignore the last two bits for many stuff to improve the user experience."
It is strongly recommended to use a different spreadsheet tool for accurate data processing involving fractions and numeric data.
:::
Functions
Both functions accept three arguments:
var frac_mediant   = frac(value, denominator, mixed);
var frac_cont = frac.cont(value, denominator, mixed);
- value: original value
- D: maximum denominator (e.g. 99 = "Up to 2 digits")
- mixed: if- true, return a mixed fraction.
The return value is an array with three integers:
var [ int, num, den ] = result;
- int(first element) represents the integer part of the estimate.
- num(second element) is the numerator of the fraction
- den(second element) is the positive denominator of the fraction
The estimate can be recovered from the array:
var estimate = int + num / den;
If mixed is false, then int = 0 and 0 < den ≤ D
If mixed is true, then 0 ≤ num < den ≤ D
:::info Negative Values
When mixed is true, int will be the floor of the result. For example, in
var result = frac( -0.125 , 9, true);
the result will be [ -1, 7, 8 ]. This is interpreted as
-0.125 ~ (-1) + (7/8)
:::
- 
See https://cdn.sheetjs.com/frac/ for more details. ↩︎ 
- 
See issue #83511 in the LibreOffice bug tracker. ↩︎