| 
									
										
										
										
											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` < `den` ≤ `D` | 
					
						
							| 
									
										
										
										
											2024-04-24 08:40:31 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 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 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```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. |