Created
October 31, 2024 15:43
-
-
Save Dan-Q/363887e3cc2407b1c862e159e00f27a2 to your computer and use it in GitHub Desktop.
A graphical implementation of Hero's algorithm for derivation of square roots. https://danq.me/2024/10/31/hero-of-alexandria/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* A graphical implementation of Hero's algorithm for derivation of square roots. | |
* Assumes existence of a <div id="p246620-hero"></div> | |
* | |
* Author: Dan Q <https://danq.me/> | |
* Explanation: https://danq.me/2024/10/31/hero-of-alexandria/ | |
* License: Public Domain / CC0 / The Unlicense (your choice) | |
*/ | |
(function(){ | |
const p24662hero = document.getElementById('p24662-hero'); | |
p24662hero.innerHTML = ` | |
<p style="display: flex; gap: 5px; align-items: stretch;"> | |
<label for="p24662hero-in" style="font-weight: bold; align-self: center;">Calculate: √</label> | |
<input id="p24662hero-in" type="number" maxlength="3" min="1" max="999"> | |
<button id="p24662hero-go">Start</button> | |
</p> | |
<canvas id="p24662hero-graph" width="1000" height="500" style="width: 100%; background: #c5eded; border: 1px solid #333; margin: 0 0 14px;"></canvas> | |
<output id="p24662hero-out" style="font-family: monospace; font-size: 12px; background: gainsboro; padding: 8px 4px; border: 1px solid #333; min-height: 196px;">Ready to calculate!</output> | |
`; | |
p24662hero.style.display = 'flex'; | |
p24662hero.style.flexDirection = 'column'; | |
const decimalPlaceAccuracy = 4; | |
const graphXPositionStepSize = 95; | |
const p24662heroGraph = p24662hero.querySelector('#p24662hero-graph'); | |
const p24662heroOut = p24662hero.querySelector('#p24662hero-out'); | |
const p24662heroGraphContext = p24662heroGraph.getContext('2d'); | |
let calculateRootOf; | |
let calculatorTick; | |
let lastGuess; | |
let graphTop; | |
let guesses = []; | |
/** | |
* Round the specified number to the requested number of decimal places. | |
*/ | |
function roundTo(number, decimalPlaces){ | |
const decimalPlacesMultiplier = 10 ** decimalPlaces; | |
return Math.round( number * decimalPlacesMultiplier ) / decimalPlacesMultiplier; | |
} | |
function drawIndicatorLine(lineValue) { | |
const yPosition = 460 - ( (lineValue / graphTop) * 460 ); | |
p24662heroGraphContext.setLineDash([3, 3]); // line 3, gap 3 | |
p24662heroGraphContext.strokeStyle = '#95b8b8'; | |
p24662heroGraphContext.beginPath(); | |
p24662heroGraphContext.moveTo(40, yPosition); | |
p24662heroGraphContext.lineTo(1000, yPosition); | |
p24662heroGraphContext.stroke(); | |
// text for final line | |
p24662heroGraphContext.font = '15px monospace'; | |
p24662heroGraphContext.textAlign = 'right'; | |
p24662heroGraphContext.fillText(Math.round(lineValue), 35, yPosition + 5); | |
} | |
function drawGraph(finalLine = null){ | |
p24662heroGraphContext.clearRect(0, 0, p24662heroGraph.width, p24662heroGraph.height); | |
p24662heroGraphContext.lineWidth = 1; | |
p24662heroGraphContext.strokeStyle = '#555'; | |
// y-axis | |
p24662heroGraphContext.beginPath(); | |
p24662heroGraphContext.moveTo(40, 0); | |
p24662heroGraphContext.lineTo(40, 500); | |
p24662heroGraphContext.stroke(); | |
// x-axis | |
p24662heroGraphContext.beginPath(); | |
p24662heroGraphContext.moveTo(0, 460); | |
p24662heroGraphContext.lineTo(1000, 460); | |
p24662heroGraphContext.stroke(); | |
// first line | |
drawIndicatorLine(guesses[0]); | |
// second line if applicable | |
if((guesses.length > 1) && (calculateRootOf > 3)) drawIndicatorLine(guesses[1]); | |
// final line, if finished | |
if(finalLine) drawIndicatorLine(finalLine); | |
// line | |
p24662heroGraphContext.lineWidth = 2; | |
p24662heroGraphContext.fillStyle = '#062d4d'; | |
p24662heroGraphContext.strokeStyle = '#062d4d'; | |
p24662heroGraphContext.setLineDash([]); // undashed line | |
let xPosition = 40; | |
let lastYPosition; | |
for(guess of guesses){ | |
const yPosition = 460 - ( (guess / graphTop) * 460 ); | |
p24662heroGraphContext.beginPath(); | |
p24662heroGraphContext.arc(xPosition, yPosition, 6, 0, 2 * Math.PI); | |
p24662heroGraphContext.fill(); | |
if(lastYPosition) { | |
p24662heroGraphContext.beginPath(); | |
p24662heroGraphContext.moveTo(xPosition - graphXPositionStepSize, lastYPosition); | |
p24662heroGraphContext.lineTo(xPosition, yPosition); | |
p24662heroGraphContext.stroke(); | |
} | |
lastYPosition = yPosition; | |
xPosition += graphXPositionStepSize; | |
} | |
} | |
function calculateStep(){ | |
const newGuess = ( lastGuess + calculateRootOf / lastGuess ) / 2; | |
const roundedNewGuess = roundTo( newGuess, decimalPlaceAccuracy + 1); | |
guesses.push( roundedNewGuess ); | |
const roundingOccurred = newGuess != roundedNewGuess; | |
p24662heroOut.innerText += `Guessing ${lastGuess} gives a next guess of ( ${lastGuess} + ${calculateRootOf} / ${lastGuess} ) / 2 ${roundingOccurred ? '≈' : '='} ${roundedNewGuess}.\n`; | |
if( roundedNewGuess == lastGuess ) { | |
// solved! | |
setTimeout(function(){ | |
p24662heroOut.innerText += `${lastGuess} = ${roundedNewGuess}, so that's the approximate answer (accurate as far as ${roundTo(lastGuess, decimalPlaceAccuracy)}).\n`; | |
}, 400); | |
setTimeout(function(){ | |
p24662heroOut.innerText += `(${roundedNewGuess}² = ${roundedNewGuess ** 2})\n`; | |
}, 800); | |
clearInterval(calculatorTick); | |
drawGraph(roundedNewGuess); | |
return; | |
} | |
drawGraph(); | |
lastGuess = roundedNewGuess; | |
} | |
document.getElementById('p24662hero-go').addEventListener('click', function(){ | |
calculateRootOf = p24662hero.querySelector('#p24662hero-in').value; | |
clearInterval(calculatorTick); | |
graphTop = Math.ceil( calculateRootOf / 2 ) + 12; | |
lastGuess = 1; | |
guesses = [ lastGuess ]; | |
drawGraph(); | |
calculatorTick = setInterval(calculateStep, 400); | |
p24662heroOut.innerText = `Estimating square root of ${calculateRootOf} accurate to ${decimalPlaceAccuracy} decimal places.\n`; | |
}); | |
drawGraph(); | |
// Start with a demonstrative query: | |
p24662hero.querySelector('#p24662hero-in').value = 9; | |
document.getElementById('p24662hero-go').click(); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment