Skip to content

Instantly share code, notes, and snippets.

@scriptype
Last active March 20, 2016 19:23
Show Gist options
  • Save scriptype/df2c6b6ff0e4315439d7 to your computer and use it in GitHub Desktop.
Save scriptype/df2c6b6ff0e4315439d7 to your computer and use it in GitHub Desktop.
Calculate PI digits (multiple workers)

Calculate PI digits

A little late to the party but here's a widget you can calculate as many digits of π as you want (actually, do not try with huge parameters).

Update: It's now working concurrently with as many workers as cpu cores. So it gets faster if your computer has more cpus.

A Pen by Mustafa Enes on CodePen.

License.

<div id="app-output" class="pi"></div>
<div class="form-container" id="app-form-container">
<form>
<label class="form-field">
<span class="form-field-label">
digits:
</span>
<input type="number"
id="app-form-digits"
class="form-field-input"
min="0"
placeholder="e.g. 16" />
</label>
<label class="form-field">
<span class="form-field-label">
precision:
</span>
<input type="number"
id="app-form-precision"
class="form-field-input"
min="0"
placeholder="e.g. 50" />
<span class="form-field-info">
correctness of the output will increase as precision gets higher. but it will be slower.
</span>
</label>
</form>
</div>
function getWorkerURLFromFunction(fn) {
var bodyPattern = /\'WORKER_START\'((\s+)?(.*(\s+)?)*)\'WORKER_END\'/
var content = fn.toString().match(bodyPattern)[1]
var blob = new Blob([content], {type: 'text/javascript'})
return URL.createObjectURL(blob)
}
function workerThread() {
'WORKER_START'
importScripts('http://cdnjs.cloudflare.com/ajax/libs/mathjs/3.1.0/math.min.js')
self.addEventListener('message', function (event) {
var message = event.data
self.postMessage({
output: pi(message.digits, message.precision, message.index, message.tail),
digits: message.digits,
precision: message.precision,
index: message.index,
tail: message.tail,
id: message.id,
timestamp: Date.now()
})
}, false)
var add = math.add
var multiply = math.multiply
var divide = math.divide
var factorial = math.factorial
var pow = math.pow
var bignumber = math.bignumber
// Uses Newton's approximation
// https://tr.wikipedia.org/wiki/Pi_say%C4%B1s%C4%B1#cite_ref-6
function pi (digits, sliceSize, index, tail) {
math.config({ precision: digits })
var sliceTop = sliceSize * (index + 1) + tail
var sliceBottom = sliceSize * index
var total = 0
while (sliceTop-- > sliceBottom) {
let n = bignumber(sliceTop)
total = add(
total,
divide(
bignumber(
multiply(
pow( 2, add(n, 1) ),
pow( factorial(n), 2 ))),
factorial(
add( multiply(2, n ), 1 ))))
}
return total.toString()
}
'WORKER_END'
}
// Concurrency
var Api = {
init(options) {
this.onmessage = options.onmessage || new Function()
this.spawn()
return this
},
_divisableByCoreCount(n) {
if (!(n % this.cores)) return n
return this._divisableByCoreCount(n - 1)
},
cores: navigator.hardwareConcurrency || navigator.cores || 4,
workers: [],
pending: {},
resolved: {},
// Spawn as many workers as cpu cores.
spawn() {
this.workers = new Array(this.cores).join(' ').split(' ').map(e => (
new Worker(getWorkerURLFromFunction(workerThread))
))
},
makeRequest(message) {
this.pending[message.id] = this.pending[message.id] || []
this.pending[message.id].push(message)
this.workers.forEach((worker, index, workers) => {
var messageCopy = JSON.parse(JSON.stringify(message))
var precision = this._divisableByCoreCount(message.precision)
var diff = index + 1 === workers.length ?
message.precision - precision : 0
messageCopy.precision = precision / this.cores
messageCopy.tail = diff
messageCopy.index = index
worker.postMessage(messageCopy)
worker.onmessage = this.onReceiveMessage.bind(this)
})
},
onReceiveMessage(event) {
var message = event.data
this.pending[message.id].pop()
this.resolved[message.id] = this.resolved[message.id] || []
this.resolved[message.id].push(message)
if (this.resolved[message.id].length === this.cores) {
math.config({ precision: message.digits })
this.onmessage(
this.resolved[message.id].reduce((memo, curr) => {
return Object.assign(memo, curr, {
output: math.add(
math.bignumber(memo.output || 0),
math.bignumber(curr.output)
).toString()
})
}, {})
)
}
}
}
var App = {
UI: {
digits: document.getElementById('app-form-digits'),
precision: document.getElementById('app-form-precision'),
output: document.getElementById('app-output'),
container: document.getElementById('app-form-container')
},
state: {
digits: 100,
precision: 300,
lastMessageId: null,
lastReceivedMessageId: null,
output: ''
},
setState(newState) {
Object.keys(newState).forEach(key => {
this.state[key] = newState[key]
})
this.render()
},
bindEvents() {
this.UI.digits.addEventListener(
'input', this.onChangeDigits.bind(this))
this.UI.precision.addEventListener(
'input', this.onChangePrecision.bind(this))
},
onMessageReceived(message) {
console.log('took (ms)', message.timestamp - (+message.id.split('_')[0]))
this.setState({
lastReceivedMessageId: message.id,
output: message.output
})
},
makeUpdateRequest() {
var id = `${ Date.now() }_${ Math.random() * 999 }`
this.setState({ lastMessageId: id })
this.api.makeRequest({ id,
digits: this.state.digits,
precision: this.state.precision
})
},
onChangeDigits(event) {
this.setState({
digits: parseInt(event.target.value, 10)
})
this.makeUpdateRequest()
},
onChangePrecision(event) {
this.setState({
precision: parseInt(event.target.value, 10)
})
this.makeUpdateRequest()
},
init() {
this.UI.digits.value = this.state.digits
this.UI.precision.value = this.state.precision
this.bindEvents()
this.api = Api.init({
onmessage: this.onMessageReceived.bind(this)
})
this.makeUpdateRequest()
},
render() {
// Update feedback message
var feedbackClass = 'working'
var { lastMessageId, lastReceivedMessageId } = this.state
if (lastMessageId === lastReceivedMessageId) {
this.UI.container.classList.remove(feedbackClass)
} else {
this.UI.container.classList.add(feedbackClass)
}
// Update output
this.UI.output.innerText = this.state.output
}
}
App.init()
<script src="http://cdnjs.cloudflare.com/ajax/libs/mathjs/3.1.0/math.min.js"></script>
body {
background: #222;
font: 100 14px/1.4 monospace
}
.pi {
color: lime;
font-size: 48px;
word-break: break-all;
margin-bottom: 100px
}
.form-container {
display: block;
position: fixed;
bottom: 0;
left: 0;
width: 100%;
padding: 1.5em;
background: #111;
border: 1px solid transparent;
transition: all .3s;
&.working {
border-color: salmon
}
}
.form-field {
&:not(:last-of-type) {
margin-right: 1em
}
&-label, &-info {
text-transform: uppercase;
}
&-label {
display: inline-block;
min-width: 100px;
color: #ddd
}
&-info {
display: block;
margin-top: 1rem;
color: #666;
font-size: .8em
}
&-input {
width: 100px;
padding: .125em;
font-size: 1em;
line-height: 1.4;
border: none;
font-family: monospace;
background: #ddd;
color: #222;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment