tanszek:oktatas:techcomm:floating-point_representation
Differences
This shows you the differences between two versions of the page.
| Both sides previous revisionPrevious revision | |||
| tanszek:oktatas:techcomm:floating-point_representation [2026/10/12 18:38] – knehez | tanszek:oktatas:techcomm:floating-point_representation [2026/10/12 18:40] (current) – [Sources and further reading] knehez | ||
|---|---|---|---|
| Line 456: | Line 456: | ||
| - Range limits the magnitude of values, while the length of the significand limits precision. For large values, the spacing between consecutive representable numbers can be greater than one. | - Range limits the magnitude of values, while the length of the significand limits precision. For large values, the spacing between consecutive representable numbers can be greater than one. | ||
| - | ==== Sources and further reading ==== | + | < |
| + | <div class=" | ||
| + | < | ||
| + | .ieee-game { | ||
| + | max-width: 1000px; | ||
| + | margin: 24px auto; | ||
| + | padding: 24px; | ||
| + | border: 1px solid #cbd5e1; | ||
| + | border-radius: | ||
| + | background: #f8fafc; | ||
| + | color: #172033; | ||
| + | font: 16px/1.6 system-ui, sans-serif; | ||
| + | } | ||
| + | .ieee-game * { box-sizing: border-box; } | ||
| + | .ieee-game h2, .ieee-game h3 { color: #172033; margin: 0 0 12px; } | ||
| + | .ieee-game p { margin: 10px 0; } | ||
| + | .ieee-game button, .ieee-game input { font: inherit; } | ||
| + | .ieee-game button { | ||
| + | padding: 7px 12px; | ||
| + | border: 1px solid #94a3b8; | ||
| + | border-radius: | ||
| + | background: #fff; | ||
| + | color: #172033; | ||
| + | cursor: pointer; | ||
| + | } | ||
| + | .ieee-game button: | ||
| + | .ieee-game button: | ||
| + | outline: 3px solid #2563eb; | ||
| + | outline-offset: | ||
| + | } | ||
| + | .ieee-game .ig-card { | ||
| + | padding: 20px; | ||
| + | margin-top: 18px; | ||
| + | border: 1px solid #dbe3ee; | ||
| + | border-radius: | ||
| + | background: #fff; | ||
| + | } | ||
| + | .ieee-game .ig-controls { | ||
| + | display: flex; | ||
| + | flex-wrap: wrap; | ||
| + | align-items: | ||
| + | gap: 8px; | ||
| + | margin: 12px 0; | ||
| + | } | ||
| + | .ieee-game input { | ||
| + | width: 180px; | ||
| + | padding: 7px 10px; | ||
| + | border: 1px solid #94a3b8; | ||
| + | border-radius: | ||
| + | background: #fff; | ||
| + | color: #172033; | ||
| + | } | ||
| + | .ieee-game .ig-field { | ||
| + | margin: 12px 0; | ||
| + | padding: 10px; | ||
| + | border-radius: | ||
| + | } | ||
| + | .ieee-game .ig-sign { background: #fff7ed; } | ||
| + | .ieee-game .ig-exponent { background: #eff6ff; } | ||
| + | .ieee-game .ig-fraction { background: #f0fdf4; } | ||
| + | .ieee-game .ig-label { font-weight: | ||
| + | .ieee-game .ig-bits { display: flex; flex-wrap: wrap; gap: 4px; } | ||
| + | .ieee-game .ig-bit { | ||
| + | width: 30px; | ||
| + | padding: 8px 0; | ||
| + | font: bold 19px/1.2 monospace; | ||
| + | border: 1px solid #94a3b8; | ||
| + | } | ||
| + | .ieee-game .ig-sign .ig-bit[aria-pressed=" | ||
| + | background: #c2410c; color: #fff; border-color: | ||
| + | } | ||
| + | .ieee-game .ig-exponent .ig-bit[aria-pressed=" | ||
| + | background: #1d4ed8; color: #fff; border-color: | ||
| + | } | ||
| + | .ieee-game .ig-fraction .ig-bit[aria-pressed=" | ||
| + | background: #15803d; color: #fff; border-color: | ||
| + | } | ||
| + | .ieee-game .ig-mono { | ||
| + | font-family: | ||
| + | white-space: | ||
| + | overflow-wrap: | ||
| + | } | ||
| + | .ieee-game .ig-value { | ||
| + | font-size: 25px; | ||
| + | font-weight: | ||
| + | overflow-wrap: | ||
| + | } | ||
| + | .ieee-game .ig-note { font-size: 14px; color: #475569; } | ||
| + | .ieee-game .ig-feedback { | ||
| + | padding: 12px; | ||
| + | margin-top: 12px; | ||
| + | border-left: | ||
| + | background: #f1f5f9; | ||
| + | } | ||
| + | .ieee-game .ig-feedback[data-kind=" | ||
| + | border-color: | ||
| + | } | ||
| + | .ieee-game .ig-feedback[data-kind=" | ||
| + | border-color: | ||
| + | } | ||
| + | .ieee-game .ig-primary { | ||
| + | background: #1d4ed8; color: #fff; border-color: | ||
| + | } | ||
| + | .ieee-game .ig-primary: | ||
| + | .ieee-game .ig-target { font-size: 28px; color: #1d4ed8; font-weight: | ||
| + | .ieee-game [hidden] { display: none !important; } | ||
| + | @media (max-width: 560px) { | ||
| + | .ieee-game { padding: 12px; } | ||
| + | .ieee-game .ig-card { padding: 12px; } | ||
| + | .ieee-game .ig-bit { width: 27px; } | ||
| + | } | ||
| + | </ | ||
| - | | + | |
| - | | + | <p> |
| + | Explore the three fields of a < | ||
| + | then build the bit pattern for a target value. | ||
| + | </p> | ||
| + | | ||
| + | Sign: 1 bit · Exponent: 8 bits · Fraction: 23 bits. | ||
| + | Within each field, bits are shown from most significant to least significant. | ||
| + | Click any bit to change it. | ||
| + | </p> | ||
| - | This section | + | <section |
| + | < | ||
| + | |||
| + | <div class=" | ||
| + | < | ||
| + | Decimal value: | ||
| + | <input data-role=" | ||
| + | | ||
| + | </ | ||
| + | <button type=" | ||
| + | </ | ||
| + | |||
| + | <div class=" | ||
| + | <button type=" | ||
| + | <button type=" | ||
| + | <button type=" | ||
| + | <button type=" | ||
| + | <button type=" | ||
| + | <button type=" | ||
| + | <button type=" | ||
| + | <button type=" | ||
| + | <button type=" | ||
| + | </ | ||
| + | |||
| + | <div data-role=" | ||
| + | |||
| + | < | ||
| + | <div data-role=" | ||
| + | <p class=" | ||
| + | Finite values are displayed with enough decimal digits to identify | ||
| + | stored value, not necessarily its full exact decimal expansion. | ||
| + | </ | ||
| + | <div data-role=" | ||
| + | <div data-role=" | ||
| + | |||
| + | <p class=" | ||
| + | Try 0.1 to observe rounding. Try changing only the sign bit, or increase | ||
| + | the exponent while keeping it between 1 and 254. | ||
| + | Exponent values 0 and 255 have special meanings. | ||
| + | </ | ||
| + | </ | ||
| + | |||
| + | <section class=" | ||
| + | < | ||
| + | <p> | ||
| + | Represent this value in IEEE 754 binary32: | ||
| + | <span data-role=" | ||
| + | </ | ||
| + | <p> | ||
| + | Set the sign, add 127 to the actual exponent, and store the fraction | ||
| + | after the implicit leading 1. | ||
| + | </ | ||
| + | |||
| + | <div data-role=" | ||
| + | |||
| + | <div class=" | ||
| + | <button type=" | ||
| + | <button type=" | ||
| + | <button type=" | ||
| + | <button type=" | ||
| + | <button type=" | ||
| + | </ | ||
| + | |||
| + | <p data-role=" | ||
| + | <div data-role=" | ||
| + | <div data-role=" | ||
| + | <div data-role=" | ||
| + | <p class=" | ||
| + | Challenge values are normal numbers that can be represented exactly. | ||
| + | Hints are allowed. Revealing a solution | ||
| + | </ | ||
| + | </ | ||
| + | |||
| + | < | ||
| + | (function () { | ||
| + | "use strict"; | ||
| + | const root = document.currentScript.parentElement; | ||
| + | const get = role => root.querySelector(' | ||
| + | const buffer = new ArrayBuffer(4); | ||
| + | const view = new DataView(buffer); | ||
| + | |||
| + | let explorerWord = 0; | ||
| + | let answerWord = 0; | ||
| + | let targetWord = 0; | ||
| + | let target = -13.25; | ||
| + | let finished = false; | ||
| + | let points = 0; | ||
| + | let completed = 0; | ||
| + | |||
| + | const targets = [ | ||
| + | -13.25, 6.5, 0.15625, -2.5, 1.5, -0.75, | ||
| + | 10.25, -6.5, 0.625, 3.25, -0.125, 16, | ||
| + | -1, 0.5, 12.75, -8.5, 2.25, -0.3125 | ||
| + | ]; | ||
| + | |||
| + | function toWord(value) { | ||
| + | view.setFloat32(0, | ||
| + | return view.getUint32(0, | ||
| + | } | ||
| + | |||
| + | function toNumber(word) { | ||
| + | view.setUint32(0, | ||
| + | return view.getFloat32(0, | ||
| + | } | ||
| + | |||
| + | function fields(word) { | ||
| + | return { | ||
| + | s: word >>> | ||
| + | e: (word >>> | ||
| + | f: word & 0x7fffff | ||
| + | }; | ||
| + | } | ||
| + | |||
| + | const bits = (value, width) => value.toString(2).padStart(width, | ||
| + | |||
| + | function numberText(value) { | ||
| + | if (Number.isNaN(value)) return " | ||
| + | if (Object.is(value, | ||
| + | if (value === Infinity) return " | ||
| + | if (value === -Infinity) return " | ||
| + | return String(value).replace(/ | ||
| + | } | ||
| + | |||
| + | function fieldText(word) { | ||
| + | const p = fields(word); | ||
| + | return p.s + " " + bits(p.e, 8) + " " + bits(p.f, 23); | ||
| + | } | ||
| + | |||
| + | function fractionText(f) { | ||
| + | return bits(f, 23).replace(/ | ||
| + | } | ||
| + | |||
| + | function setFeedback(role, | ||
| + | const element = get(role); | ||
| + | element.textContent = message; | ||
| + | element.dataset.kind = kind || ""; | ||
| + | } | ||
| + | |||
| + | function createEditor(container, | ||
| + | const buttons = []; | ||
| + | [ | ||
| + | { name: " | ||
| + | { name: " | ||
| + | { name: " | ||
| + | ].forEach(group => { | ||
| + | const field = document.createElement(" | ||
| + | field.className = " | ||
| + | |||
| + | const label = document.createElement(" | ||
| + | label.className = " | ||
| + | const count = group.high - group.low + 1; | ||
| + | label.textContent = group.name + " — " + count + | ||
| + | (count === 1 ? " bit" : " bits" | ||
| + | |||
| + | const row = document.createElement(" | ||
| + | row.className = " | ||
| + | |||
| + | for (let position = group.high; position >= group.low; position--) { | ||
| + | const button = document.createElement(" | ||
| + | button.type = " | ||
| + | button.className = " | ||
| + | button.addEventListener(" | ||
| + | row.append(button); | ||
| + | buttons.push({ button, position, name: group.name }); | ||
| + | } | ||
| + | |||
| + | field.append(label, | ||
| + | container.append(field); | ||
| + | }); | ||
| + | return buttons; | ||
| + | } | ||
| + | |||
| + | function renderEditor(editor, | ||
| + | editor.forEach(item => { | ||
| + | const value = (word >>> | ||
| + | item.button.textContent = value; | ||
| + | item.button.setAttribute(" | ||
| + | item.button.setAttribute( | ||
| + | " | ||
| + | item.name + ", bit " + item.position + ", value " + value + | ||
| + | ". Click to toggle." | ||
| + | ); | ||
| + | }); | ||
| + | } | ||
| + | |||
| + | const explorerEditor = createEditor(get(" | ||
| + | explorerWord = (explorerWord ^ (1 << position)) >>> | ||
| + | renderExplorer(); | ||
| + | get(" | ||
| + | ? " | ||
| + | setFeedback(" | ||
| + | }); | ||
| + | |||
| + | const challengeEditor = createEditor(get(" | ||
| + | answerWord = (answerWord ^ (1 << position)) >>> | ||
| + | renderEditor(challengeEditor, | ||
| + | if (!finished) { | ||
| + | setFeedback(" | ||
| + | } | ||
| + | }); | ||
| + | |||
| + | function renderExplorer() { | ||
| + | renderEditor(explorerEditor, | ||
| + | const p = fields(explorerWord); | ||
| + | get(" | ||
| + | |||
| + | let detail = | ||
| + | "Sign s = " + p.s + " | ||
| + | " | ||
| + | " | ||
| + | |||
| + | if (p.e === 255) { | ||
| + | detail += p.f === 0 | ||
| + | ? " | ||
| + | : "NaN (Not a Number): E = 255 and F is nonzero.\nThere is no ordinary numerical value."; | ||
| + | } else if (p.e === 0 && p.f === 0) { | ||
| + | detail += "Zero: E = 0 and F = 0.\n" + | ||
| + | "The sign bit distinguishes +0 from −0."; | ||
| + | } else if (p.e === 0) { | ||
| + | detail += | ||
| + | " | ||
| + | "Fixed exponent = −126\n" | ||
| + | " | ||
| + | "Value = (−1)^s × (F / 2^23) × 2^(−126)"; | ||
| + | } else { | ||
| + | detail += | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | "Value = " + (p.s ? " | ||
| + | (1 + p.f / 8388608) + " × 2^(" + (p.e - 127) + " | ||
| + | } | ||
| + | get(" | ||
| + | } | ||
| + | |||
| + | function convert(text) { | ||
| + | const normalized = text.trim().replace(/ | ||
| + | const decimalPattern = / | ||
| + | const specialPattern = / | ||
| + | |||
| + | if (!decimalPattern.test(normalized) && !specialPattern.test(normalized)) { | ||
| + | get(" | ||
| + | setFeedback( | ||
| + | " | ||
| + | "Enter a decimal number such as −13.25, 0.1, or 1e-8. Infinity and NaN are also accepted.", | ||
| + | " | ||
| + | ); | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | get(" | ||
| + | const source = Number(normalized); | ||
| + | explorerWord = toWord(source); | ||
| + | const stored = toNumber(explorerWord); | ||
| + | renderExplorer(); | ||
| + | |||
| + | if (decimalPattern.test(normalized) && !Number.isFinite(source)) { | ||
| + | setFeedback(" | ||
| + | } else if (Number.isFinite(source) && !Number.isFinite(stored)) { | ||
| + | setFeedback(" | ||
| + | } else if (Number.isFinite(source) && source !== 0 && stored === 0) { | ||
| + | setFeedback(" | ||
| + | } else if (Number.isFinite(source) && !Object.is(source, | ||
| + | setFeedback( | ||
| + | " | ||
| + | " | ||
| + | ". Stored binary32 value: " + numberText(stored) + " | ||
| + | " | ||
| + | ); | ||
| + | } else { | ||
| + | setFeedback( | ||
| + | " | ||
| + | Number.isFinite(source) | ||
| + | ? "No additional rounding occurred when converting the browser' | ||
| + | : "A special | ||
| + | ); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | root.querySelector(' | ||
| + | convert(get(" | ||
| + | }); | ||
| + | |||
| + | get(" | ||
| + | if (event.key === " | ||
| + | event.preventDefault(); | ||
| + | convert(get(" | ||
| + | } | ||
| + | }); | ||
| + | |||
| + | root.querySelectorAll(" | ||
| + | button.addEventListener(" | ||
| + | get(" | ||
| + | convert(button.dataset.value); | ||
| + | }); | ||
| + | }); | ||
| + | |||
| + | root.querySelector(' | ||
| + | explorerWord = 1; | ||
| + | get(" | ||
| + | renderExplorer(); | ||
| + | setFeedback( | ||
| + | " | ||
| + | " | ||
| + | ); | ||
| + | }); | ||
| + | |||
| + | function updateScore() { | ||
| + | get(" | ||
| + | " | ||
| + | } | ||
| + | |||
| + | function newChallenge(first) { | ||
| + | if (!first) { | ||
| + | const choices = targets.filter(value => value !== target); | ||
| + | target = choices[Math.floor(Math.random() * choices.length)]; | ||
| + | } | ||
| + | targetWord = toWord(target); | ||
| + | answerWord = 0; | ||
| + | finished = false; | ||
| + | get(" | ||
| + | get(" | ||
| + | get(" | ||
| + | renderEditor(challengeEditor, | ||
| + | setFeedback(" | ||
| + | updateScore(); | ||
| + | } | ||
| + | |||
| + | root.querySelector(' | ||
| + | const actual = fields(answerWord); | ||
| + | const expected = fields(targetWord); | ||
| + | |||
| + | if (answerWord === targetWord) { | ||
| + | if (!finished) { | ||
| + | points++; | ||
| + | completed++; | ||
| + | finished = true; | ||
| + | } | ||
| + | updateScore(); | ||
| + | setFeedback( | ||
| + | " | ||
| + | " | ||
| + | numberText(target) + ". Try the next number.", | ||
| + | " | ||
| + | ); | ||
| + | } else { | ||
| + | const status = (name, match) => name + ": " + (match ? " | ||
| + | setFeedback( | ||
| + | " | ||
| + | status(" | ||
| + | status(" | ||
| + | status(" | ||
| + | "Your current pattern represents " + numberText(toNumber(answerWord)) + " | ||
| + | " | ||
| + | ); | ||
| + | } | ||
| + | }); | ||
| + | |||
| + | root.querySelector(' | ||
| + | const p = fields(targetWord); | ||
| + | get(" | ||
| + | get(" | ||
| + | " | ||
| + | " | ||
| + | " × 2^(" + (p.e - 127) + " | ||
| + | "1. Choose the sign bit.\n" | ||
| + | "2. Add 127 to the actual exponent and write the result in 8 bits.\n" | ||
| + | "3. Omit the leading 1. Store the bits after the point and pad with zeros on the right."; | ||
| + | }); | ||
| + | |||
| + | root.querySelector(' | ||
| + | if (!finished) { | ||
| + | completed++; | ||
| + | finished = true; | ||
| + | } | ||
| + | const p = fields(targetWord); | ||
| + | get(" | ||
| + | get(" | ||
| + | "Sign: " + p.s + " | ||
| + | " | ||
| + | " | ||
| + | " → " + bits(p.e, 8) + " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | setFeedback(" | ||
| + | updateScore(); | ||
| + | }); | ||
| + | |||
| + | root.querySelector(' | ||
| + | answerWord = 0; | ||
| + | renderEditor(challengeEditor, | ||
| + | setFeedback(" | ||
| + | }); | ||
| + | |||
| + | root.querySelector(' | ||
| + | newChallenge(false); | ||
| + | }); | ||
| + | |||
| + | convert(" | ||
| + | newChallenge(true); | ||
| + | })(); | ||
| + | </ | ||
| + | </ | ||
| + | </ | ||
tanszek/oktatas/techcomm/floating-point_representation.txt · Last modified: by knehez
