docs.sheetjs.com/docz/docs/12-constellation/02-frac/index.md

155 lines
4.9 KiB
Markdown
Raw Normal View History

2024-04-24 08:40:31 +00:00
---
title: Rational Approximation
2024-10-07 21:41:19 +00:00
pagination_prev: constellation/ssf
pagination_next: constellation/crc32
2024-04-24 08:40:31 +00:00
---
<head>
<script src="https://cdn.sheetjs.com/frac-1.1.3/package/dist/frac.min.js"></script>
</head>
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 CDN[^1].
2024-10-07 21:41:19 +00:00
Source code and project documentation are hosted on the SheetJS Git server at
2024-04-24 08:40:31 +00:00
https://git.sheetjs.com/sheetjs/frac
## Live Demo
2024-10-07 21:41:19 +00:00
Formatted texts are calculated from the value and maximum denominators.
2024-04-24 08:40:31 +00:00
Please [report an issue](https://git.sheetjs.com/sheetjs/frac/issues) if a
2024-10-07 21:41:19 +00:00
particular result does not align with expectations.
2024-04-24 08:40:31 +00:00
```jsx live
function SheetJSFrac() {
2024-10-07 21:41:19 +00:00
const [val, setVal] = React.useState(0.699450515);
2024-04-24 08:40:31 +00:00
const [text, setText] = React.useState("");
2024-05-21 17:16:30 +00:00
if(typeof frac == "undefined") return ( <b>ERROR: Reload this page</b> );
2024-10-07 21:41:19 +00:00
const fmt = arr => `${(""+arr[1]).padStart(5)} / ${(""+arr[2]).padEnd(5)}`;
2024-04-24 08:40:31 +00:00
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]);
2024-10-07 21:41:19 +00:00
const n = { textAlign:"right" };
2024-04-24 08:40:31 +00:00
const g = { backgroundColor:"#C6EFCE", color:"#006100", whiteSpace:"pre-wrap" };
const b = { backgroundColor:"#FFC7CE", color:"#9C0006" };
return ( <table>
2024-10-07 21:41:19 +00:00
<tr><td><b>Number Value</b></td><td colspan="4">
2024-04-24 08:40:31 +00:00
<input type="text" value={val} onChange={e => setVal(e.target.value)}/>
</td></tr>
2024-10-07 21:41:19 +00:00
<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> ))}
2024-04-24 08:40:31 +00:00
</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`
2024-10-07 21:41:19 +00:00
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](/docs/constellation/ssf) | 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 |
2024-04-24 08:40:31 +00:00
2024-10-07 21:41:19 +00:00
:::danger LibreOffice bugs
2024-04-24 08:40:31 +00:00
2024-10-07 21:41:19 +00:00
There are known rounding errors in LibreOffice[^2] which result in inaccurate
2024-04-24 08:40:31 +00:00
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:
```js
2024-05-21 17:16:30 +00:00
var frac_mediant = frac(value, denominator, mixed);
2024-04-24 08:40:31 +00:00
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:
```js
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:
```js
var estimate = int + num / den;
```
2024-04-25 08:39:55 +00:00
If `mixed` is `false`, then `int = 0` and `0` &lt; `den` &leq; `D`
2024-04-24 08:40:31 +00:00
If `mixed` is `true`, then `0` &leq; `num` &lt; `den` &leq; `D`
:::info Negative Values
When `mixed` is true, `int` will be the floor of the result. For example, in
```js
var result = frac( -0.125 , 9, true);
```
the result will be `[ -1, 7, 8 ]`. This is interpreted as
```
-0.125 ~ (-1) + (7/8)
```
:::
[^1]: See https://cdn.sheetjs.com/frac/ for more details.
[^2]: See [issue #83511](https://bugs.documentfoundation.org/show_bug.cgi?id=83511) in the LibreOffice bug tracker.