├── .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 | 2−1023
235 |
236 | ×
237 | ()
238 |
239 |
240 | n
241 | =
242 |
243 | −1s
244 |
245 | ×
246 |
247 | 2e−1023
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 | };
--------------------------------------------------------------------------------