├── .gitignore ├── README.md ├── css ├── bits.css ├── dynks.css ├── math.css └── visualization.css ├── index.html ├── package.json ├── server.js └── src ├── bits.js ├── dom.js ├── dynks.js ├── exponential.js └── ieee754.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | js 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IEEE 754 Double Precision Floating Point Visualization 2 | 3 | It's an interactive visualization of how double precision floating point representation works. 4 | 5 | As presented during my [Everything you never wanted to know about JavaScript numbers](http://2013.jsconf.eu/speakers/bartek-szopka-everything-you-never-wanted-to-know-about-javascript-numbers-and-you-didnt-know-you-could-ask.html) talk at JSConf EU 2013. 6 | 7 | Available at http://bartaz.github.io/ieee754-visualization/ 8 | 9 | 10 | ## Disclaimer 11 | 12 | The code is not perfect nor pretty ;) 13 | 14 | I was playing around with different concepts to get the idea ready for JSConf. I hope to clean it up soon. 15 | 16 | 17 | ## Contributing 18 | 19 | If you are a developer and want to help making it better, or a designer who wants to make it look prettier or 20 | you know IEEE 754 and can see some error or a field for improvement - let me know by reporting an issue, sending 21 | pull request or contact me at [@bartaz](http://twitter.com/bartaz) on Twitter. 22 | 23 | 24 | ## Acknowledgments and Resources 25 | 26 | This tool wouldn't be possible without great work of others. 27 | 28 | 29 | ### Learning resources 30 | 31 | Everything I know about numbers in IEEE 754 representation I've learnt from resources freely available on-line. 32 | 33 | Nearly everything about JS numbers is described in details by Axel Rauschmayer in his [this series of articles about numbers](http://www.2ality.com/search/label/numbers), such as: 34 | 35 | * [How numbers are encoded in JavaScript](http://www.2ality.com/2012/04/number-encoding.html) 36 | * [NaN and Infinity in JavaScript](http://www.2ality.com/2012/02/nan-infinity.html) 37 | * [Safe integers in JavaScript](http://www.2ality.com/2013/10/safe-integers.html) 38 | 39 | There is of course a lot about this topic on Wikipedia. Like this [Double-precision floating-point format](http://en.wikipedia.org/wiki/Double-precision_floating-point_format) article, or description of the [IEEE 754-1985](http://en.wikipedia.org/wiki/IEEE_754-1985) standard. 40 | 41 | You may also want to know what ECMAScript standard has to say about [Number type](http://people.mozilla.org/~jorendorff/es6-draft.html#sec-ecmascript-language-types-number-type). 42 | 43 | This is also not the first attempt to provide visualisation or conversion tools between numbers and their binary representation in IEEE 754 standard. 44 | 45 | * [IEEE-754 Analysis](http://babbage.cs.qc.cuny.edu/IEEE-754/index.xhtml) 46 | * [float.js](http://dherman.github.io/float.js/) 47 | 48 | 49 | ### Tools 50 | 51 | Creating this visualization would be much harder without great tools such as: 52 | 53 | * [webmake](https://github.com/medikoo/modules-webmake/) by Mariusz Nowak 54 | * [CSS MathML fallback](http://lea.verou.me/2013/03/use-mathml-today-with-css-fallback/) by Lea Verou 55 | * [Tangle](http://worrydream.com/Tangle/) by Bret Victor and his great work on [explorable explanations](http://worrydream.com/ExplorableExplanations/) 56 | 57 | 58 | ## License 59 | 60 | Copyright 2013 Bartek Szopka. 61 | 62 | Released under the MIT License. 63 | -------------------------------------------------------------------------------- /css/bits.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Binary switcher 3 | */ 4 | 5 | .bit { 6 | font-family: monospace; 7 | font-size: 20px; 8 | 9 | display: block; 10 | float: left; 11 | width: 15px; 12 | height: 50px; 13 | 14 | text-align: center; 15 | line-height: 50px; 16 | 17 | background-image: linear-gradient(transparent, transparent 0px, black 0px, black 2px, transparent 2px, transparent); 18 | 19 | -webkit-transition: background-position 200ms ease-in-out, background-color 200ms; 20 | transition: background-position 200ms ease-in-out, background-color 200ms; 21 | 22 | position: relative; 23 | overflow: hidden; 24 | 25 | cursor: pointer; 26 | -webkit-user-select: none; 27 | -moz-user-select:none; 28 | user-select: none; 29 | } 30 | 31 | .hidden { 32 | cursor: default; 33 | } 34 | .zero { 35 | background-color: rgba(100,100,100,0.2); 36 | background-position: 0 48px; 37 | } 38 | 39 | .one { 40 | background-color: rgba(100,100,100,.6); 41 | background-position: 0px 0px; 42 | } 43 | 44 | .zero.sign { 45 | background-color: rgba(0,255,255,0.2); 46 | } 47 | 48 | .one.sign { 49 | background-color: rgba(0,255,255,0.6); 50 | } 51 | 52 | .zero.exponent { 53 | background-color: rgba(0,255,0,.2); 54 | } 55 | 56 | .one.exponent { 57 | background-color: rgba(0,255,0,.6); 58 | } 59 | 60 | .zero.significand { 61 | background-color: rgba(255,0,0,.2); 62 | } 63 | 64 | .one.significand { 65 | background-color: rgba(255,0,0,.6); 66 | } 67 | 68 | .zero::after, 69 | .one::after { 70 | display: block; 71 | position: absolute; 72 | width: 100%; 73 | height: 100%; 74 | content: ""; 75 | top: 0; 76 | left: 0; 77 | 78 | background-image: linear-gradient(90deg, transparent, transparent 0px, black 0px, black 2px, transparent 2px, transparent); 79 | background-repeat: no-repeat; 80 | 81 | -webkit-transition: -webkit-transform 200ms ease-in-out; 82 | transition: transform 200ms ease-in-out; 83 | } 84 | 85 | .zero::after { 86 | -webkit-transform: translateY(48px); 87 | transform: translateY(48px); 88 | } 89 | 90 | .one::after { 91 | -webkit-transform: translateY(-48px); 92 | transform: translateY(-48px); 93 | } 94 | 95 | .one + .zero:not(.prev-zero)::after, 96 | .prev-one.zero::after { 97 | -webkit-transform: translateY(0px); 98 | transform: translateY(0px); 99 | } 100 | 101 | .zero + .one::after, 102 | .prev-zero.one::after { 103 | -webkit-transform: translateY(0px); 104 | transform: translateY(0px); 105 | } 106 | 107 | .zero::before { 108 | content: "0"; 109 | color: rgba(0,0,0,0.5); 110 | } 111 | 112 | .one::before { 113 | content: "1"; 114 | } 115 | 116 | .visualisation-bits { 117 | overflow: hidden; 118 | position: relative; 119 | } 120 | 121 | .bits-wrapper { 122 | display: block; 123 | margin-bottom: 20px; 124 | position: absolute; 125 | } 126 | 127 | .significand-wrapper { 128 | margin-left: 180px; 129 | padding-bottom: 400px; 130 | padding-right: 1em; 131 | } 132 | 133 | .bits { 134 | margin-top: 0px; 135 | height: 50px; 136 | } 137 | 138 | .significand-wrapper .bits { 139 | margin-bottom: 20px; 140 | position: relative; 141 | } 142 | 143 | .exponent-wrapper { 144 | margin-left: 15px; 145 | } 146 | 147 | .bit.hidden { 148 | margin-left: -15px; 149 | } 150 | 151 | .sign-title { 152 | position: absolute; 153 | font-size: 0.7em; 154 | -webkit-transform-origin: 10px 85%; 155 | transform-origin: 10px 85%; 156 | -webkit-transform: rotate(-90deg); 157 | transform: rotate(-90deg); 158 | } 159 | 160 | .bits-wrapper-title { 161 | margin-bottom: 0.2em; 162 | display: block; 163 | text-align: center; 164 | border: 1px solid transparent; 165 | border-width: 0 0 2px; 166 | } 167 | 168 | .bits-wrapper-title:hover { 169 | border-color: #777; 170 | } 171 | 172 | .sign-wrapper .bits-wrapper-title { 173 | width: 15px; 174 | } 175 | 176 | .exponent-wrapper .bits-wrapper-title { 177 | width: 165px; 178 | } 179 | 180 | .significand-wrapper .bits-wrapper-title, 181 | .significand-wrapper .math { 182 | width: 780px; 183 | } 184 | 185 | .point-slider { 186 | position: absolute; 187 | left: 0; 188 | top: 50px; 189 | opacity: 0; 190 | pointer-events: none; 191 | width: 780px; 192 | } 193 | 194 | label[for=point-slider] { 195 | position: absolute; 196 | width: 30px; 197 | height: 10px; 198 | text-align: center; 199 | line-height: 20px; 200 | top: 55px; 201 | left: 0; 202 | transition: 200ms left; 203 | } 204 | 205 | label[for=point-slider]::after { 206 | content: ""; 207 | vertical-align: top; 208 | background: #000; 209 | width: 8px; 210 | height: 8px; 211 | display: inline-block; 212 | border-radius: 5px; 213 | } -------------------------------------------------------------------------------- /css/dynks.css: -------------------------------------------------------------------------------- 1 | .dynks-enabled, .dynks-moving { 2 | cursor: ew-resize; 3 | } 4 | 5 | .number-input, 6 | .dynks-enabled { 7 | padding: 0 0.1em; 8 | border-radius: 0.1em; 9 | 10 | transition: 200ms box-shadow; 11 | -webkit-transition: 200ms box-shadow; 12 | } 13 | 14 | .number-input:hover, 15 | .dynks-enabled:hover { 16 | box-shadow: inset 0 0 5px #000; 17 | } 18 | 19 | .number-input:focus, 20 | .dynks-active { 21 | box-shadow: inset 0 0 5px #000; 22 | background: #FFC; 23 | } 24 | 25 | .dynks-out-of-range { 26 | box-shadow: inset 0 0 10px #000; 27 | } -------------------------------------------------------------------------------- /css/math.css: -------------------------------------------------------------------------------- 1 | /* 2 | deeply inspired by CSS MathML fallback by Lea Verou 3 | http://lea.verou.me/2013/03/use-mathml-today-with-css-fallback/ 4 | */ 5 | 6 | .math { 7 | font-family: 'Cambria Math', Cambria, MathJax_Main, 8 | STIXGeneral, DejaVu Serif, DejaVu Sans, 9 | Times, Lucida Sans Unicode, OpenSymbol, 10 | Standard Symbols L, serif; 11 | 12 | display: block; 13 | clear: both; 14 | text-align: left; 15 | line-height: 1.5; 16 | } 17 | 18 | .math .nowrap { 19 | white-space: nowrap; 20 | } 21 | 22 | .math .mrow { 23 | display: block; 24 | text-indent: -1.5em; 25 | margin-left: 1.5em; 26 | margin-bottom: 0.1em; 27 | } 28 | 29 | .math .indented > .mi:first-child { 30 | visibility: hidden; 31 | } 32 | 33 | .math .mi { 34 | font-style: italic; 35 | } 36 | 37 | .math .mo { 38 | font-size: 0.75em; 39 | text-indent: 0; 40 | } 41 | 42 | .math .msub, 43 | .math .msup { 44 | white-space: nowrap; 45 | } 46 | 47 | .math .msub > :last-child { 48 | vertical-align: sub; 49 | font-size: 65%; 50 | } 51 | 52 | .math .msup > :last-child { 53 | vertical-align: super; 54 | font-size: 65%; 55 | } 56 | 57 | .math .mfrac { 58 | display: inline-block; 59 | vertical-align: middle; 60 | text-align: center; 61 | 62 | font-size: 0.8em; 63 | text-indent: 0; 64 | } 65 | 66 | .math .mfrac > :first-child { 67 | display: block; 68 | border-bottom: .08em solid; 69 | text-align: center; 70 | } 71 | 72 | .math .mfrac > :last-child { 73 | display: block; 74 | text-align: center; 75 | } 76 | 77 | 78 | .math .msum { 79 | position: relative; 80 | display: inline-block; 81 | text-indent: 0; 82 | font-size: 1.2em; 83 | } 84 | 85 | .math .msum::before { 86 | content: attr(data-from); 87 | font-size: 0.4em; 88 | position: absolute; 89 | bottom: -1em; 90 | left: 0; 91 | text-align: center; 92 | width: 100%; 93 | } 94 | 95 | .math .msum::after { 96 | content: attr(data-to); 97 | font-size: 0.4em; 98 | position: absolute; 99 | top: -0.6em; 100 | left: 0; 101 | width: 100%; 102 | text-align: center; 103 | } -------------------------------------------------------------------------------- /css/visualization.css: -------------------------------------------------------------------------------- 1 | html { 2 | cursor: default; 3 | } 4 | 5 | body { 6 | margin: 0; 7 | font-size: 2em; 8 | } 9 | 10 | .visualization-exponential { 11 | font-size: 2.5em; 12 | margin: 2em 0 0 2em; 13 | } 14 | 15 | .number-wrapper { 16 | position: fixed; 17 | padding: 0.5em 1em 0.7em; 18 | border-bottom: 1px dashed #AAA; 19 | background: rgba(255,255,255,0.9); 20 | top: 0; 21 | left: 0; 22 | right: 0; 23 | z-index: 10; 24 | } 25 | 26 | .number-input { 27 | -webkit-appearance: none; 28 | border: none; 29 | font-size: 2em; 30 | width: 100%; 31 | text-align: center; 32 | font-family: inherit; 33 | 34 | box-sizing: border-box; 35 | background: transparent; 36 | } 37 | 38 | .number-input:focus { 39 | outline: none; 40 | } 41 | 42 | 43 | .toggle-details-button { 44 | position: absolute; 45 | width: 100%; 46 | background: none; 47 | border: none; 48 | height: 22px; 49 | font-size: 0.4em; 50 | left: 0; 51 | bottom: 0px; 52 | line-height: 15px; 53 | width: 100%; 54 | cursor: pointer; 55 | color: #777; 56 | } 57 | 58 | .toggle-details-button:hover, .toggle-details-button:focus { 59 | color: #333; 60 | background: rgba(245,245,245,0.7); 61 | } 62 | 63 | 64 | .bits-wrapper { 65 | -webkit-transition: 300ms top; 66 | transition: 300ms top; 67 | } 68 | 69 | 70 | .visualization-bits { 71 | position: relative; 72 | margin: 5em 1em; 73 | } 74 | 75 | .visualization-bits .bits-wrapper .math { 76 | opacity: 0; 77 | transition: 200ms opacity; 78 | } 79 | 80 | .visualization-bits.expanded .bits-wrapper .math { 81 | opacity: 1; 82 | transition: 300ms 100ms opacity; 83 | } 84 | 85 | .visualization-bits .significand-wrapper .hidden { visibility: hidden; } 86 | .visualization-bits.expanded .significand-wrapper .hidden { visibility: visible; } 87 | 88 | .visualization-bits .significand-wrapper { top: 0px; } 89 | .visualization-bits.expanded .significand-wrapper { top: 570px; } 90 | 91 | .visualization-bits .exponent-wrapper { top: 0px; } 92 | .visualization-bits.expanded .exponent-wrapper { top: 220px; } 93 | 94 | 95 | .visualization-bits .full-equation { 96 | opacity: 1; 97 | position: absolute; 98 | top: 130px; 99 | pointer-events: auto; 100 | width: 960px; 101 | transition: 300ms opacity, 300ms top; 102 | } 103 | 104 | .visualization-bits.expanded .full-equation { 105 | opacity: 0; 106 | pointer-events: none; 107 | top: 1000px; 108 | } 109 | 110 | 111 | .significand-wrapper .bits::before, 112 | .significand-wrapper .bits::after { 113 | content: "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; 114 | position: absolute; 115 | font-size: 20px; 116 | font-family: monospace; 117 | line-height: 50px; 118 | letter-spacing: 3px; 119 | color: #BBB; 120 | 121 | pointer-events: none; 122 | opacity: 0; 123 | 124 | transition: 300ms opacity; 125 | } 126 | 127 | .significand-wrapper .bits::before { 128 | right: 794px; 129 | } 130 | 131 | .significand-wrapper .bits::after { 132 | left: 782px; 133 | } 134 | 135 | .visualization-bits.expanded .significand-wrapper .bits::before, 136 | .visualization-bits.expanded .significand-wrapper .bits::after { 137 | opacity: 1; 138 | } 139 | 140 | .full-equation.collapsed .mrow { visibility: hidden } 141 | .full-equation.collapsed .mrow:first-child { visibility: visible } 142 | 143 | .full-equation .significand-sum { padding: 0.6em 0.1em; } 144 | 145 | /* hover styles for part of equations related to hovered bits */ 146 | 147 | [class*=significand-bit-] { 148 | padding: 0 0.1em; 149 | } 150 | 151 | .hover { 152 | border-radius: 0.1em; 153 | background: rgba(255, 255, 0, 0.2); 154 | box-shadow: 1px 1px 5px rgba(0,0,0,0.3); 155 | } 156 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IEEE-754 Floating Point representation explained 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 |
20 | 21 |
22 |
23 | 24 |
25 | sign  26 |
27 | 28 |
29 | 30 |
31 | 32 | 33 | s 34 | = 35 | 36 | −10 37 | 38 | 39 | 40 | s 41 | = 42 | 0 43 | 44 |
45 | 46 |
47 |
48 | exponent 49 |
50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 |
62 | 63 |
64 | 65 | 66 | e 67 | = 68 | 69 | 70 | 71 | 72 | e 73 | = 74 | 75 | 76 | 77 | 78 | e 79 | = 80 | 81 | 82 | 83 | 84 | p 85 | = 86 | e 87 | 88 | 1023 89 | 90 | 91 | p 92 | = 93 | 94 | 95 | 96 |
97 |
98 |
99 | significand 100 |
101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 |
164 | 165 |
166 | 167 | n 168 | = 169 | 170 | 171 | 172 | 173 | n 174 | = 175 | 176 | 177 | 178 | 179 | 180 | n 181 | = 182 | 183 | 184 | 185 | 186 | n 187 | = 188 | 189 | 190 | 191 | 192 | n 193 | = 194 | 195 | 196 |
197 |
198 | 199 |
200 | 201 | n 202 | = 203 | 204 | 205 | 206 | n 207 | = 208 | 209 | × 210 | () 211 | 212 | 213 | n 214 | = 215 | 216 | −10 217 | 218 | × 219 | 220 | 2 221 | 222 | × 223 | () 224 | 225 | 226 | 227 | n 228 | = 229 | 230 | −10 231 | 232 | × 233 | 234 | 21023 235 | 236 | × 237 | () 238 | 239 | 240 | n 241 | = 242 | 243 | −1s 244 | 245 | × 246 | 247 | 2e1023 248 | 249 | × 250 | ( 251 | 252 | + 253 | 254 | 255 | b52-i 256 | 2-i 257 | 258 | ) 259 | 260 |
261 |
262 |
263 | 264 | 265 | 266 | 267 | 268 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ieee754-visualization", 3 | "version": "0.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "dependencies": { 7 | "node-static": "~0.7.0", 8 | "webmake": "~0.3.20", 9 | "uglify-js": "~2.4.1" 10 | }, 11 | "devDependencies": {}, 12 | "scripts": { 13 | "start": "node server.js", 14 | "build": "mkdir -p js && ./node_modules/webmake/bin/webmake src/bits.js js/all.debug.js && node_modules/uglify-js/bin/uglifyjs js/all.debug.js -m -o js/all.js" 15 | }, 16 | "repository": "https://github.com/bartaz/ieee754-visualization", 17 | "author": "Bartek Szopka", 18 | "license": "MIT" 19 | } 20 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | // Dependencies: 2 | var createServer = require('http').createServer; 3 | var staticServer = require('node-static').Server; 4 | var webmake = require('webmake'); 5 | 6 | 7 | // Project path: 8 | var projectPath = __dirname; 9 | // Public folder path (statics) 10 | var staticsPath = projectPath; 11 | 12 | // Server port: 13 | var port = 8000; 14 | 15 | staticServer = new staticServer(staticsPath); 16 | 17 | var bundles = { 18 | '/js/all.js': __dirname + '/src/bits.js' 19 | }; 20 | 21 | // Initialize http server 22 | createServer(function (req, res) { 23 | // Start the flow (new Stream API demands that) 24 | req.resume(); 25 | // Respond to request 26 | req.on('end', function () { 27 | if ( bundles[req.url] ) { 28 | // Generate bundle with Webmake 29 | var programPath = bundles[ req.url ]; 30 | // Send headers 31 | res.writeHead(200, { 32 | 'Content-Type': 'application/javascript; charset=utf-8', 33 | // Do not cache generated bundle 34 | 'Cache-Control': 'no-cache' 35 | }); 36 | 37 | var time = Date.now(); 38 | webmake(bundles[req.url], { cache: true }, function (err, content) { 39 | if (err) { 40 | console.error("Webmake error: " + err.message); 41 | res.end('console.error("Webmake error: ' + err.message + '");'); 42 | return; 43 | } 44 | 45 | // Send script 46 | console.log("Webmake OK (" + ((Date.now() - time)/1000).toFixed(3) + "s)"); 47 | res.end(content); 48 | }); 49 | } else { 50 | staticServer.serve(req, res); 51 | } 52 | }); 53 | }).listen(port); 54 | 55 | console.log("Server started"); -------------------------------------------------------------------------------- /src/bits.js: -------------------------------------------------------------------------------- 1 | var ieee754 = require("./ieee754"); 2 | var dom = require("./dom"); 3 | 4 | var visualization = dom.$(".visualization-bits"); 5 | var numberInput = dom.$("#number-input"); 6 | 7 | 8 | function classNameFilter( className ){ 9 | return function( bit ){ return bit.classList.contains(className); }; 10 | } 11 | 12 | var bits = dom.$$(".bit", visualization); 13 | var bitsSign = bits.filter( classNameFilter("sign") ); 14 | var bitsExponent = bits.filter( classNameFilter("exponent") ); 15 | var bitsHidden = bits.filter( classNameFilter("hidden") ); 16 | var bitsSignificand = bits.filter( classNameFilter("significand") ); 17 | 18 | var pointSlider = dom.$("#point-slider"); 19 | var pointSliderLabel = dom.$("#point-slider-label"); 20 | 21 | function getInputNumberValue() { 22 | return Number( numberInput.value.replace(/\u2212/g, "-") ); 23 | } 24 | 25 | function setNumberInputValue( value ) { 26 | value = Number( value ); 27 | if (value === 0 && (1/value < 0)) { 28 | // special case to detect and show negative zero 29 | value = "-0"; 30 | } else { 31 | value = Number.prototype.toString.call(value); 32 | } 33 | 34 | value = value.replace(/-/g, "\u2212"); //pretty minus 35 | 36 | if (value !== numberInput.value) { 37 | numberInput.value = value; 38 | } 39 | updateVisualizatoin(); 40 | } 41 | 42 | 43 | function updateBitElementClasses( bitElements, bits, prevBit ) { 44 | prevBit = typeof prevBit == "string" ? prevBit.slice(-1) : "0"; 45 | for (var i = 0; i < bits.length; i++) { 46 | var bitElement = bitElements[ i ]; 47 | bitElement.classList.remove("one"); 48 | bitElement.classList.remove("zero"); 49 | bitElement.classList.remove("prev-one"); 50 | bitElement.classList.remove("prev-zero"); 51 | 52 | bitElement.classList.add(bits[i] == "1" ? "one" : "zero"); 53 | if (i === 0) { 54 | bitElement.classList.add(prevBit == "1" ? "prev-one" : "prev-zero"); 55 | } 56 | } 57 | } 58 | 59 | 60 | function updateBinary( parsed ) { 61 | var isExpandedMode = visualization.classList.contains("expanded"); 62 | 63 | updateBitElementClasses( bitsSign, parsed.bSign ); 64 | updateBitElementClasses( bitsExponent, parsed.bExponent, isExpandedMode ? "0" : parsed.bSign ); 65 | updateBitElementClasses( bitsHidden, parsed.bHidden ); 66 | updateBitElementClasses( bitsSignificand, parsed.bSignificand, isExpandedMode ? parsed.bHidden : parsed.bExponent ); 67 | 68 | pointSliderLabel.style.left = (parsed.exponentNormalized - 1) * 15 + "px"; 69 | 70 | if (parsed.exponent !== Number(pointSlider.value)) { 71 | pointSlider.value = parsed.exponent; 72 | } 73 | } 74 | 75 | 76 | function classNamesToBinaryString( binaryString, bitSpan ) { 77 | binaryString += bitSpan.classList.contains("zero") ? "0" : "1"; 78 | return binaryString; 79 | } 80 | 81 | function updateNumber( values ) { 82 | var b = ""; 83 | 84 | var exponent, significand; 85 | if (values) { 86 | exponent = values.exponent; 87 | significand = values.significand; 88 | } 89 | 90 | if (typeof exponent == "number") { 91 | exponent = ieee754.intToBinaryString( exponent, 11 ); 92 | } else { 93 | exponent = bitsExponent.reduce( classNamesToBinaryString, "" ); 94 | } 95 | 96 | if (typeof significand != "string") { 97 | significand = bitsSignificand.reduce( classNamesToBinaryString, "" ); 98 | } 99 | 100 | b += bitsSign.reduce( classNamesToBinaryString, "" ); 101 | b += exponent; 102 | b += significand; 103 | 104 | var f = ieee754.binaryStringToFloat64( b ); 105 | setNumberInputValue( f ); 106 | } 107 | 108 | 109 | function generatePowersHtml( b, startPower, classPrefix, useOne ) { 110 | if (typeof startPower != "number") { 111 | startPower = b.length - 1; 112 | } 113 | 114 | classPrefix = classPrefix || "exponent-bit-"; 115 | 116 | var htmlPowers = ""; 117 | var htmlComputed = ""; 118 | var htmlFractions = ""; 119 | var htmlFractionsComputed = ""; 120 | 121 | var allZeros = true; 122 | for (var i = 0, l = b.length; i < l; i++) { 123 | if (b[i] == "1") { 124 | allZeros = false; 125 | var p = startPower - i; 126 | var j = b.length - 1 -i; 127 | if (htmlPowers.length > 0) { 128 | htmlPowers += " + "; 129 | htmlComputed += " + "; 130 | htmlFractions += " + "; 131 | htmlFractionsComputed += " + "; 132 | } 133 | 134 | var powerHtml = '2'+ p +''; 135 | 136 | if (useOne && p == 0) { 137 | powerHtml = '1'; 138 | } 139 | htmlPowers += powerHtml; 140 | htmlComputed += '' + Math.pow(2,p)+ ''; 141 | 142 | if (p >= 0) { 143 | htmlFractions += powerHtml; 144 | htmlFractionsComputed += '' + Math.pow(2,p)+ ''; 145 | } else { 146 | htmlFractions += '12' + -p + ''; 147 | htmlFractionsComputed += '1'+ Math.pow(2,-p) +''; 148 | } 149 | } 150 | } 151 | 152 | if (allZeros) { 153 | htmlPowers = htmlComputed = htmlFractions = htmlFractionsComputed = '0'; 154 | } 155 | 156 | htmlFractionsComputed = htmlFractionsComputed.replace(/Infinity/g, "∞"); 157 | 158 | return { 159 | powers: htmlPowers, 160 | computed: htmlComputed, 161 | fractions: htmlFractions, 162 | fractionsComputed: htmlFractionsComputed 163 | }; 164 | } 165 | 166 | function updateMath( representation ) { 167 | // enrich representation with powers HTML 168 | 169 | var htmlExponent = generatePowersHtml( representation.bExponent ); 170 | 171 | representation.exponentPowers = htmlExponent.powers; 172 | representation.exponentPowersComputed = htmlExponent.computed; 173 | 174 | var significandBits = representation.bHidden + representation.bSignificand; 175 | 176 | representation.exponentZero = representation.exponent; 177 | representation.exponentNormalizedZero = representation.exponentNormalized; 178 | 179 | 180 | // [...] subnormal numbers are encoded with a biased exponent of 0, 181 | // but are interpreted with the value of the smallest allowed exponent, 182 | // which is one greater (i.e., as if it were encoded as a 1). 183 | // 184 | // -- http://en.wikipedia.org/wiki/Denormal_number 185 | 186 | if (representation.exponentNormalizedZero == -1023) { 187 | 188 | representation.exponentZero = representation.exponent + 1; 189 | representation.exponentNormalizedZero = representation.exponentNormalized + 1; 190 | } 191 | 192 | var htmlSignificand = generatePowersHtml( significandBits, representation.exponentNormalizedZero, "significand-bit-" ); 193 | var htmlSignificandNormalized = generatePowersHtml( significandBits, 0, "significand-bit-" ); 194 | var htmlSignificandNormalizedOne = generatePowersHtml( significandBits, 0, "significand-bit-", true ); 195 | 196 | representation.significandPowersNormalized = htmlSignificandNormalized.powers; 197 | representation.significandPowersNormalizedOne = htmlSignificandNormalizedOne.powers; 198 | 199 | var equation = dom.$(".full-equation"); 200 | 201 | if (isNaN(representation.value)) { 202 | representation.significandPowers = representation.significandPowersFractions 203 | = representation.significandPowersFractionsComputed = representation.significandPowersComputed 204 | = 'NaN'; 205 | } else { 206 | representation.significandPowers = htmlSignificand.powers; 207 | representation.significandPowersFractions = htmlSignificand.fractions; 208 | representation.significandPowersFractionsComputed = htmlSignificand.fractionsComputed; 209 | representation.significandPowersComputed = htmlSignificand.computed; 210 | } 211 | 212 | if (representation.sign < 0) 213 | representation.signHtml = String(representation.sign).replace("-", "−"); 214 | else 215 | representation.signHtml = "+" + representation.sign; 216 | 217 | representation.absValue = Math.abs( representation.value ); 218 | 219 | if (isNaN(representation.absValue)) { 220 | representation.absValue = "NaNNaNNaNNaN Batman!" 221 | } 222 | 223 | var dynamic = dom.$$(".math [data-ieee754-value]"); 224 | 225 | dynamic.forEach( function(element){ 226 | element.innerHTML = representation[ element.dataset.ieee754Value ]; 227 | }); 228 | 229 | if (isNaN(representation.value) || !isFinite(representation.value)) { 230 | equation.classList.add("collapsed"); 231 | } else { 232 | equation.classList.remove("collapsed"); 233 | } 234 | } 235 | 236 | function updateVisualizatoin() { 237 | var number = getInputNumberValue(); 238 | var representation = ieee754.toIEEE754Parsed( number ); 239 | 240 | updateBinary( representation ); 241 | updateMath( representation ); 242 | } 243 | 244 | 245 | // EVENT HANDLERS 246 | 247 | numberInput.addEventListener( "change", function() { 248 | setNumberInputValue( getInputNumberValue() ); 249 | }, false); 250 | 251 | 252 | numberInput.addEventListener("keydown", function ( event ) { 253 | var diff = 0; 254 | if ( event.keyCode === 38 || event.keyCode === 40 ) { 255 | 256 | if ( event.keyCode === 38 ) diff = +1; 257 | else diff = -1; 258 | 259 | if ( event.shiftKey ) { 260 | diff *= 10; 261 | if ( event.altKey ) { 262 | diff *= 10; 263 | } 264 | } else if ( event.altKey ) { 265 | diff /= 10; 266 | } 267 | 268 | setNumberInputValue( diff + getInputNumberValue() ); 269 | 270 | event.preventDefault(); 271 | } 272 | }, false); 273 | 274 | 275 | pointSlider.addEventListener( "change", function() { 276 | var exponent = Number(pointSlider.value); 277 | updateNumber( { exponent: exponent } ); 278 | }, false); 279 | 280 | pointSlider.addEventListener( "click", function() { 281 | pointSlider.focus(); 282 | }, false); 283 | 284 | document.body.addEventListener( "click", function( event ){ 285 | var target = event.target; 286 | 287 | if (target.classList.contains("zero") || target.classList.contains("one")) { 288 | target.classList.toggle("zero"); 289 | target.classList.toggle("one"); 290 | 291 | updateNumber(); 292 | updateVisualizatoin(); 293 | 294 | hoverRelatedExponentHandler( event ); 295 | hoverRelatedSignificandHandler( event ); 296 | hoverRelatedSignHandler( event ); 297 | } 298 | 299 | }, false); 300 | 301 | 302 | // toggle hover class on parts of equation related to hovered bit 303 | function createHoverRelatedHandler( selector, classPrefix ) { 304 | return function (event) { 305 | var target = event.target; 306 | if (dom.matchesSelector( target, selector )) { 307 | 308 | var siblings = dom.arrayify(target.parentNode.children).filter(classNameFilter("bit")); 309 | var n = siblings.length - siblings.indexOf( target ) - 1; 310 | 311 | var related = dom.$$((classPrefix+n)+","+(classPrefix+"any")); 312 | related.forEach( function(r){ 313 | r.classList[ event.type == "mouseout" ? "remove" : "add"]("hover"); 314 | }); 315 | } 316 | }; 317 | } 318 | 319 | var hoverRelatedExponentHandler = createHoverRelatedHandler( ".bit.exponent", ".exponent-bit-"); 320 | document.body.addEventListener( "mouseover", hoverRelatedExponentHandler, false ); 321 | document.body.addEventListener( "mouseout", hoverRelatedExponentHandler, false ); 322 | 323 | var hoverRelatedSignificandHandler = createHoverRelatedHandler( ".bit.significand, .bit.hidden", ".significand-bit-"); 324 | document.body.addEventListener( "mouseover", hoverRelatedSignificandHandler, false ); 325 | document.body.addEventListener( "mouseout", hoverRelatedSignificandHandler, false ); 326 | 327 | var hoverRelatedSignHandler = createHoverRelatedHandler( ".bit.sign", ".sign-bit-"); 328 | document.body.addEventListener( "mouseover", hoverRelatedSignHandler, false ); 329 | document.body.addEventListener( "mouseout", hoverRelatedSignHandler, false ); 330 | 331 | 332 | // toggle nowrap class on a equation row when equals sign is clicked 333 | 334 | document.body.addEventListener( "click", function( event ){ 335 | var target = event.target; 336 | 337 | if (dom.matchesSelector(target, ".mrow > .mo")) { 338 | target.parentNode.classList.toggle("nowrap"); 339 | } 340 | 341 | }, false); 342 | 343 | 344 | // make exponent value editable 345 | 346 | var dynks = require( "./dynks" ); 347 | 348 | var exponentElement = dom.$("#exponent-dynks"); 349 | var exponentNormalizedElement = dom.$("#exponent-normalized-dynks"); 350 | 351 | function getCurrentExponentValue() { 352 | return +exponentElement.innerHTML; 353 | } 354 | 355 | function updateExponentValue( value ) { 356 | var exponent = Number(value); 357 | updateNumber( { exponent: exponent } ); 358 | } 359 | dynks( exponentElement, getCurrentExponentValue, updateExponentValue ); 360 | 361 | function getCurrentExponentNormalizedValue() { 362 | return +exponentNormalizedElement.innerHTML; 363 | } 364 | 365 | function updateExponentNormalizedValue( value ) { 366 | var exponent = Number(value); 367 | updateNumber( { exponent: exponent + 1023 } ); 368 | } 369 | dynks( exponentNormalizedElement, getCurrentExponentNormalizedValue, updateExponentNormalizedValue ); 370 | 371 | 372 | dom.$(".toggle-details-button").addEventListener("click", function(){ 373 | visualization.classList.toggle("expanded"); 374 | }, false); 375 | 376 | // GO! 377 | 378 | updateVisualizatoin(); 379 | -------------------------------------------------------------------------------- /src/dom.js: -------------------------------------------------------------------------------- 1 | // `arraify` takes an array-like object and turns it into real Array 2 | // to make all the Array.prototype goodness available. 3 | var arrayify = function ( a ) { 4 | return [].slice.call( a ); 5 | }; 6 | 7 | // `$` returns first element for given CSS `selector` in the `context` of 8 | // the given element or whole document. 9 | var $ = function ( selector, context ) { 10 | context = context || document; 11 | return context.querySelector(selector); 12 | }; 13 | 14 | // `$$` return an array of elements for given CSS `selector` in the `context` of 15 | // the given element or whole document. 16 | var $$ = function ( selector, context ) { 17 | context = context || document; 18 | return arrayify( context.querySelectorAll(selector) ); 19 | }; 20 | 21 | exports.arrayify = arrayify; 22 | exports.$ = $; 23 | exports.$$ = $$; 24 | 25 | 26 | // cross-browser matchesSelector based on 27 | // https://gist.github.com/jonathantneal/3062955 28 | var ElementPrototype = window.Element.prototype; 29 | 30 | var matchesSelector = ElementPrototype.matchesSelector || 31 | ElementPrototype.mozMatchesSelector || 32 | ElementPrototype.msMatchesSelector || 33 | ElementPrototype.oMatchesSelector || 34 | ElementPrototype.webkitMatchesSelector || 35 | function (selector) { 36 | var node = this, nodes = (node.parentNode || node.document).querySelectorAll(selector), i = -1; 37 | 38 | while (nodes[++i] && nodes[i] != node); 39 | 40 | return !!nodes[i]; 41 | } 42 | 43 | exports.matchesSelector = function( element, selector ) { 44 | return matchesSelector.call( element, selector ); 45 | } 46 | -------------------------------------------------------------------------------- /src/dynks.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function ( target, getCurrentValue, callback ) { 3 | 4 | target.classList.add("dynks-enabled"); 5 | 6 | var options = { 7 | gap: target.dataset.dynksGap || 5, 8 | min: !isNaN(target.dataset.dynksMin) ? +target.dataset.dynksMin : -Infinity, 9 | max: !isNaN(target.dataset.dynksMax) ? +target.dataset.dynksMax : +Infinity 10 | }; 11 | 12 | target.addEventListener( "mousedown", function( mouseDownEvent ){ 13 | var initialPosition = mouseDownEvent.pageX; 14 | var lastValue = Number( getCurrentValue() ); 15 | var lastSlot = 0; 16 | 17 | function handleMouseMove( mouseMoveEvent ) { 18 | var currentSlot = (mouseMoveEvent.pageX - initialPosition) / options.gap; 19 | currentSlot = ~~currentSlot; 20 | 21 | var slotDiff = currentSlot - lastSlot; 22 | 23 | if (slotDiff !== 0) { 24 | var multiplier = 1; 25 | if (mouseMoveEvent.shiftKey) multiplier = 10; 26 | 27 | var currentValue = lastValue + slotDiff * multiplier; 28 | 29 | if (currentValue < options.min) { 30 | currentValue = options.min; 31 | target.classList.add("dynks-out-of-range"); 32 | } else if (currentValue > options.max) { 33 | currentValue = options.max; 34 | target.classList.add("dynks-out-of-range"); 35 | } else { 36 | target.classList.remove("dynks-out-of-range"); 37 | } 38 | 39 | callback( currentValue ); 40 | 41 | if (lastValue != currentValue) { 42 | lastValue = currentValue; 43 | lastSlot = currentSlot; 44 | } 45 | } 46 | 47 | mouseMoveEvent.preventDefault(); 48 | } 49 | 50 | function handleMouseUp() { 51 | target.classList.remove("dynks-active"); 52 | target.classList.remove("dynks-out-of-range"); 53 | document.documentElement.classList.remove("dynks-moving"); 54 | document.removeEventListener("mousemove", handleMouseMove, false ); 55 | document.removeEventListener("mouseup", handleMouseUp, false ); 56 | } 57 | 58 | document.addEventListener( "mousemove", handleMouseMove, false ); 59 | document.addEventListener( "mouseup", handleMouseUp, false ); 60 | 61 | target.classList.add("dynks-active"); 62 | document.documentElement.classList.add("dynks-moving"); 63 | 64 | mouseDownEvent.preventDefault(); 65 | }, false ); 66 | 67 | }; 68 | 69 | -------------------------------------------------------------------------------- /src/exponential.js: -------------------------------------------------------------------------------- 1 | var dom = require( "./dom" ); 2 | var dynks = require( "./dynks" ); 3 | 4 | var exponentElement = dom.$("#exponent"); 5 | var exponentTwinElement = dom.$("#exponent-twin"); 6 | var significandElement = dom.$("#significand"); 7 | var valueElement = dom.$("#value"); 8 | 9 | // take value of significand and make it integer 10 | var significand = Number(significandElement.innerHTML) * 10000; 11 | 12 | function getCurrentValue() { 13 | return +exponentElement.innerHTML; 14 | } 15 | 16 | function updateValue( exponent ) { 17 | exponentElement.innerHTML = exponent; 18 | exponentTwinElement.innerHTML = exponent; 19 | 20 | // compute value based on significand and exponent 21 | // there is some trickery forced to avoid computation errors 22 | var pow = exponent - 4; 23 | var value; 24 | if (pow >= 0) { 25 | value = significand * Math.pow( 10, pow ); 26 | } else { 27 | value = significand / Math.pow( 10, -pow ); 28 | } 29 | 30 | // turn value into fixed precision string 31 | value = value.toFixed(15); 32 | 33 | // and cut all unnecessary zeros 34 | // unfortunately .toFixed makes some computation errors visible 35 | // so we need to not only cut zeros but any insignificand digits 36 | // that appear 37 | var lastDigit = value.indexOf( significand % 10 ); 38 | var point = value.indexOf( "." ); 39 | var cut = point > lastDigit ? point+2 : lastDigit + 1; 40 | 41 | valueElement.innerHTML = value.substr(0, cut); 42 | } 43 | 44 | dynks( exponentElement, getCurrentValue, updateValue ); 45 | -------------------------------------------------------------------------------- /src/ieee754.js: -------------------------------------------------------------------------------- 1 | 2 | // float64ToOctets( 123.456 ) -> [ 64, 94, 221, 47, 26, 159, 190, 119 ] 3 | function float64ToOctets(number) { 4 | var buffer = new ArrayBuffer(8); 5 | new DataView(buffer).setFloat64(0, number, false); 6 | return [].slice.call( new Uint8Array(buffer) ); 7 | } 8 | 9 | // octetsToFloat64( [ 64, 94, 221, 47, 26, 159, 190, 119 ] ) -> 123.456 10 | function octetsToFloat64( octets ) { 11 | var buffer = new ArrayBuffer(8); 12 | new Uint8Array( buffer ).set( octets ); 13 | return new DataView( buffer ).getFloat64(0, false); 14 | } 15 | 16 | // intToBinaryString( 8 ) -> "00001000" 17 | function intToBinaryString( i, length ) { 18 | length = length || 8; 19 | for(i = i.toString(2); i.length < length; i="0"+i); 20 | return i; 21 | } 22 | 23 | // binaryStringToInt( "00001000" ) -> 8 24 | function binaryStringToInt( b ) { 25 | return parseInt(b, 2); 26 | } 27 | 28 | function octetsToBinaryString( octets ) { 29 | return octets.map( function(i){ return intToBinaryString(i); } ).join(""); 30 | } 31 | 32 | function float64ToBinaryString( number ) { 33 | return octetsToBinaryString( float64ToOctets( number ) ); 34 | } 35 | 36 | function binaryStringToFloat64( string ) { 37 | return octetsToFloat64( string.match(/.{8}/g).map( binaryStringToInt ) ); 38 | } 39 | 40 | function toIEEE754Parsed(v) { 41 | var string = octetsToBinaryString( float64ToOctets(v) ); 42 | var parts = string.match(/^(.)(.{11})(.{52})$/); // sign{1} exponent{11} fraction{52} 43 | 44 | var bSign = parts[1]; 45 | var sign = Math.pow( -1, parseInt(bSign,2) ); 46 | 47 | var bExponent = parts[2]; 48 | var exponent = parseInt( bExponent, 2 ); 49 | 50 | var exponentNormalized = exponent - 1023; 51 | var bSignificand = parts[3]; 52 | 53 | var bHidden = (exponent === 0) ? "0" : "1"; 54 | 55 | return { 56 | value: v, 57 | bFull: bSign + bExponent + bHidden + bSignificand, 58 | bSign: bSign, 59 | bExponent: bExponent, 60 | bHidden: bHidden, 61 | bSignificand: bSignificand, 62 | sign: sign, 63 | exponent: exponent, 64 | exponentNormalized: exponentNormalized, 65 | }; 66 | } 67 | 68 | module.exports = { 69 | float64ToOctets: float64ToOctets, 70 | octetsToFloat64: octetsToFloat64, 71 | intToBinaryString: intToBinaryString, 72 | binaryStringToInt: binaryStringToInt, 73 | octetsToBinaryString: octetsToBinaryString, 74 | float64ToBinaryString: float64ToBinaryString, 75 | binaryStringToFloat64: binaryStringToFloat64, 76 | toIEEE754Parsed: toIEEE754Parsed 77 | }; --------------------------------------------------------------------------------