User Tools

Site Tools


tanszek:oktatas:techcomm:floating-point_representation

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
tanszek:oktatas:techcomm:floating-point_representation [2026/10/12 18:38] – kneheztanszek: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 ====+<html> 
 +<div class="ieee-game"> 
 +  <style> 
 +    .ieee-game { 
 +      max-width: 1000px; 
 +      margin: 24px auto; 
 +      padding: 24px; 
 +      border: 1px solid #cbd5e1; 
 +      border-radius: 16px; 
 +      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: 7px; 
 +      background: #fff; 
 +      color: #172033; 
 +      cursor: pointer; 
 +    } 
 +    .ieee-game button:hover { background: #e2e8f0; } 
 +    .ieee-game button:focus-visible, .ieee-game input:focus-visible { 
 +      outline: 3px solid #2563eb; 
 +      outline-offset: 3px; 
 +    } 
 +    .ieee-game .ig-card { 
 +      padding: 20px; 
 +      margin-top: 18px; 
 +      border: 1px solid #dbe3ee; 
 +      border-radius: 12px; 
 +      background: #fff; 
 +    } 
 +    .ieee-game .ig-controls { 
 +      display: flex; 
 +      flex-wrap: wrap; 
 +      align-items: center; 
 +      gap: 8px; 
 +      margin: 12px 0; 
 +    } 
 +    .ieee-game input { 
 +      width: 180px; 
 +      padding: 7px 10px; 
 +      border: 1px solid #94a3b8; 
 +      border-radius: 7px; 
 +      background: #fff; 
 +      color: #172033; 
 +    } 
 +    .ieee-game .ig-field { 
 +      margin: 12px 0; 
 +      padding: 10px; 
 +      border-radius: 9px; 
 +    } 
 +    .ieee-game .ig-sign { background: #fff7ed; } 
 +    .ieee-game .ig-exponent { background: #eff6ff; } 
 +    .ieee-game .ig-fraction { background: #f0fdf4; } 
 +    .ieee-game .ig-label { font-weight: bold; margin-bottom: 7px; } 
 +    .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="true"] { 
 +      background: #c2410c; color: #fff; border-color: #c2410c; 
 +    } 
 +    .ieee-game .ig-exponent .ig-bit[aria-pressed="true"] { 
 +      background: #1d4ed8; color: #fff; border-color: #1d4ed8; 
 +    } 
 +    .ieee-game .ig-fraction .ig-bit[aria-pressed="true"] { 
 +      background: #15803d; color: #fff; border-color: #15803d; 
 +    } 
 +    .ieee-game .ig-mono { 
 +      font-family: ui-monospace, monospace; 
 +      white-space: pre-wrap; 
 +      overflow-wrap: anywhere; 
 +    } 
 +    .ieee-game .ig-value { 
 +      font-size: 25px; 
 +      font-weight: bold; 
 +      overflow-wrap: anywhere; 
 +    } 
 +    .ieee-game .ig-note { font-size: 14px; color: #475569; } 
 +    .ieee-game .ig-feedback { 
 +      padding: 12px; 
 +      margin-top: 12px; 
 +      border-left: 4px solid #64748b; 
 +      background: #f1f5f9; 
 +    } 
 +    .ieee-game .ig-feedback[data-kind="success"] { 
 +      border-color: #15803d; background: #f0fdf4; 
 +    } 
 +    .ieee-game .ig-feedback[data-kind="error"] { 
 +      border-color: #b45309; background: #fffbeb; 
 +    } 
 +    .ieee-game .ig-primary { 
 +      background: #1d4ed8; color: #fff; border-color: #1d4ed8; 
 +    } 
 +    .ieee-game .ig-primary:hover { background: #1e40af; } 
 +    .ieee-game .ig-target { font-size: 28px; color: #1d4ed8; font-weight: bold; } 
 +    .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; } 
 +    } 
 +  </style>
  
-  * [[https://docs.oracle.com/cd/E77782_01/html/E77791/z4000ac019878.html|Oracle Numerical Computation Guide: IEEE formats and special values]] +  <h2>IEEE 754: Build a Floating-Point Number</h2> 
-  * [[https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html|David Goldberg: What Every Computer Scientist Should Know About Floating-Point Arithmetic]]+  <p> 
 +    Explore the three fields of a <strong>32-bit floating-point number</strong>, 
 +    then build the bit pattern for a target value. 
 +  </p> 
 +  <p class="ig-note"> 
 +    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 introduces the basic principles of binary floating-point representation. It does not cover all IEEE 754 formats or the full set of arithmetic rules.+  <section class="ig-card"> 
 +    <h3>1. Explore binary32</h3> 
 + 
 +    <div class="ig-controls"> 
 +      <label> 
 +        Decimal value: 
 +        <input data-role="input" type="text" value="-13.25" 
 +               aria-label="Decimal value to convert"> 
 +      </label> 
 +      <button type="button" data-action="convert" class="ig-primary">Convert</button> 
 +    </div> 
 + 
 +    <div class="ig-controls"> 
 +      <button type="button" data-value="-13.25">−13.25</button> 
 +      <button type="button" data-value="0.15625">0.15625</button> 
 +      <button type="button" data-value="0.1">0.1</button> 
 +      <button type="button" data-value="16777217">16 777 217</button> 
 +      <button type="button" data-value="0">+0</button> 
 +      <button type="button" data-value="-0">−0</button> 
 +      <button type="button" data-special="tiny">Smallest positive</button> 
 +      <button type="button" data-value="Infinity">+Infinity</button> 
 +      <button type="button" data-value="NaN">NaN</button> 
 +    </div> 
 + 
 +    <div data-role="explorer"></div> 
 + 
 +    <p>Stored value:</p> 
 +    <div data-role="value" class="ig-value" aria-live="polite"></div> 
 +    <p class="ig-note"> 
 +      Finite values are displayed with enough decimal digits to identify the 
 +      stored value, not necessarily its full exact decimal expansion. 
 +    </p> 
 +    <div data-role="details" class="ig-feedback ig-mono"></div> 
 +    <div data-role="conversion" class="ig-feedback" aria-live="polite"></div> 
 + 
 +    <p class="ig-note"> 
 +      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. 
 +    </p> 
 +  </section> 
 + 
 +  <section class="ig-card"> 
 +    <h3>2. Build the target number</h3> 
 +    <p> 
 +      Represent this value in IEEE 754 binary32: 
 +      <span data-role="target" class="ig-target"></span> 
 +    </p> 
 +    <p> 
 +      Set the sign, add 127 to the actual exponent, and store the fraction 
 +      after the implicit leading 1. 
 +    </p> 
 + 
 +    <div data-role="challenge"></div> 
 + 
 +    <div class="ig-controls"> 
 +      <button type="button" data-action="check" class="ig-primary">Check answer</button> 
 +      <button type="button" data-action="hint">Show hint</button> 
 +      <button type="button" data-action="solution">Show solution</button> 
 +      <button type="button" data-action="clear">Clear bits</button> 
 +      <button type="button" data-action="next">Next number</button> 
 +    </div> 
 + 
 +    <p data-role="score" class="ig-note"></p> 
 +    <div data-role="feedback" class="ig-feedback" aria-live="polite"></div> 
 +    <div data-role="hint" class="ig-feedback ig-mono" hidden></div> 
 +    <div data-role="solution" class="ig-feedback ig-mono" hidden></div> 
 +    <p class="ig-note"> 
 +      Challenge values are normal numbers that can be represented exactly. 
 +      Hints are allowed. Revealing a solution does not earn a point. 
 +    </p> 
 +  </section> 
 + 
 +  <script> 
 +    (function () { 
 +      "use strict"; 
 +      const root = document.currentScript.parentElement; 
 +      const get = role => root.querySelector('[data-role="' + role + '"]'); 
 +      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, value, false); 
 +        return view.getUint32(0, false); 
 +      } 
 + 
 +      function toNumber(word) { 
 +        view.setUint32(0, word >>> 0, false); 
 +        return view.getFloat32(0, false); 
 +      } 
 + 
 +      function fields(word) { 
 +        return { 
 +          s: word >>> 31, 
 +          e: (word >>> 23) & 255, 
 +          f: word & 0x7fffff 
 +        }; 
 +      } 
 + 
 +      const bits = (value, width) => value.toString(2).padStart(width, "0"); 
 + 
 +      function numberText(value) { 
 +        if (Number.isNaN(value)) return "NaN"; 
 +        if (Object.is(value, -0)) return "−0"; 
 +        if (value === Infinity) return "+Infinity"; 
 +        if (value === -Infinity) return "−Infinity"; 
 +        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(/0+$/, "") || "0"; 
 +      } 
 + 
 +      function setFeedback(role, message, kind) { 
 +        const element = get(role); 
 +        element.textContent = message; 
 +        element.dataset.kind = kind || ""; 
 +      } 
 + 
 +      function createEditor(container, toggle) { 
 +        const buttons = []; 
 +        [ 
 +          { name: "Sign", type: "sign", high: 31, low: 31 }, 
 +          { name: "Exponent", type: "exponent", high: 30, low: 23 }, 
 +          { name: "Fraction", type: "fraction", high: 22, low: 0 } 
 +        ].forEach(group => { 
 +          const field = document.createElement("div"); 
 +          field.className = "ig-field ig-" + group.type; 
 + 
 +          const label = document.createElement("div"); 
 +          label.className = "ig-label"; 
 +          const count = group.high - group.low + 1; 
 +          label.textContent = group.name + " — " + count + 
 +                              (count === 1 ? " bit" : " bits"); 
 + 
 +          const row = document.createElement("div"); 
 +          row.className = "ig-bits"; 
 + 
 +          for (let position = group.high; position >= group.low; position--) { 
 +            const button = document.createElement("button"); 
 +            button.type = "button"; 
 +            button.className = "ig-bit"; 
 +            button.addEventListener("click", () => toggle(position)); 
 +            row.append(button); 
 +            buttons.push({ button, position, name: group.name }); 
 +          } 
 + 
 +          field.append(label, row); 
 +          container.append(field); 
 +        }); 
 +        return buttons; 
 +      } 
 + 
 +      function renderEditor(editor, word) { 
 +        editor.forEach(item => { 
 +          const value = (word >>> item.position) & 1; 
 +          item.button.textContent = value; 
 +          item.button.setAttribute("aria-pressed", String(Boolean(value))); 
 +          item.button.setAttribute( 
 +            "aria-label", 
 +            item.name + ", bit " + item.position + ", value " + value + 
 +            ". Click to toggle." 
 +          ); 
 +        }); 
 +      } 
 + 
 +      const explorerEditor = createEditor(get("explorer"), position => { 
 +        explorerWord = (explorerWord ^ (1 << position)) >>> 0; 
 +        renderExplorer(); 
 +        get("input").value = Object.is(toNumber(explorerWord), -0) 
 +          ? "-0" : String(toNumber(explorerWord)); 
 +        setFeedback("conversion", "Bit changed. The value above follows the current bit pattern."); 
 +      }); 
 + 
 +      const challengeEditor = createEditor(get("challenge"), position => { 
 +        answerWord = (answerWord ^ (1 << position)) >>> 0; 
 +        renderEditor(challengeEditor, answerWord); 
 +        if (!finished) { 
 +          setFeedback("feedback", "Ready? Check your answer."); 
 +        } 
 +      }); 
 + 
 +      function renderExplorer() { 
 +        renderEditor(explorerEditor, explorerWord); 
 +        const p = fields(explorerWord); 
 +        get("value").textContent = numberText(toNumber(explorerWord)); 
 + 
 +        let detail = 
 +          "Sign s = " + p.s + "\n" + 
 +          "Stored exponent E = " + p.e + "\n" + 
 +          "Fraction field as an unsigned integer F = " + p.f + "\n\n"; 
 + 
 +        if (p.e === 255) { 
 +          detail += p.f === 0 
 +            ? "Infinity: E = 255 and F = 0.\nThe sign bit selects +Infinity or −Infinity." 
 +            : "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 += 
 +            "Subnormal number: no implicit leading 1.\n" + 
 +            "Fixed exponent = −126\n" + 
 +            "Significand = 0." + bits(p.f, 23) + " (binary)\n" + 
 +            "Value = (−1)^s × (F / 2^23) × 2^(−126)"; 
 +        } else { 
 +          detail += 
 +            "Normal number\n" + 
 +            "Actual exponent = " + p.e + " − 127 = " + (p.e - 127) + "\n" + 
 +            "Significand = 1." + fractionText(p.f) + " (binary)\n" + 
 +            "Significand in decimal = " + (1 + p.f / 8388608) + "\n" + 
 +            "Value = " + (p.s ? "−" : "+") + 
 +            (1 + p.f / 8388608) + " × 2^(" + (p.e - 127) + ")"; 
 +        } 
 +        get("details").textContent = detail; 
 +      } 
 + 
 +      function convert(text) { 
 +        const normalized = text.trim().replace(/\u2212/g, "-"); 
 +        const decimalPattern = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i; 
 +        const specialPattern = /^[+-]?Infinity$|^NaN$/; 
 + 
 +        if (!decimalPattern.test(normalized) && !specialPattern.test(normalized)) { 
 +          get("input").setAttribute("aria-invalid", "true"); 
 +          setFeedback( 
 +            "conversion", 
 +            "Enter a decimal number such as −13.25, 0.1, or 1e-8. Infinity and NaN are also accepted.", 
 +            "error" 
 +          ); 
 +          return; 
 +        } 
 + 
 +        get("input").setAttribute("aria-invalid", "false"); 
 +        const source = Number(normalized); 
 +        explorerWord = toWord(source); 
 +        const stored = toNumber(explorerWord); 
 +        renderExplorer(); 
 + 
 +        if (decimalPattern.test(normalized) && !Number.isFinite(source)) { 
 +          setFeedback("conversion", "This input is too large even for the browser's numeric input conversion. Binary32 stores infinity.", "error"); 
 +        } else if (Number.isFinite(source) && !Number.isFinite(stored)) { 
 +          setFeedback("conversion", "Binary32 overflow: this finite input rounds to infinity.", "error"); 
 +        } else if (Number.isFinite(source) && source !== 0 && stored === 0) { 
 +          setFeedback("conversion", "The input is too small in magnitude and rounds to signed zero in binary32.", "error"); 
 +        } else if (Number.isFinite(source) && !Object.is(source, stored)) { 
 +          setFeedback( 
 +            "conversion", 
 +            "Rounding occurs. Input: " + normalized + 
 +            ". Stored binary32 value: " + numberText(stored) + ".", 
 +            "error" 
 +          ); 
 +        } else { 
 +          setFeedback( 
 +            "conversion", 
 +            Number.isFinite(source) 
 +              ? "No additional rounding occurred when converting the browser's parsed number to binary32." 
 +              : "A special IEEE 754 value is displayed." 
 +          ); 
 +        } 
 +      } 
 + 
 +      root.querySelector('[data-action="convert"]').addEventListener("click", () => { 
 +        convert(get("input").value); 
 +      }); 
 + 
 +      get("input").addEventListener("keydown", event => { 
 +        if (event.key === "Enter") { 
 +          event.preventDefault(); 
 +          convert(get("input").value); 
 +        } 
 +      }); 
 + 
 +      root.querySelectorAll("[data-value]").forEach(button => { 
 +        button.addEventListener("click", () => { 
 +          get("input").value = button.dataset.value; 
 +          convert(button.dataset.value); 
 +        }); 
 +      }); 
 + 
 +      root.querySelector('[data-special="tiny"]').addEventListener("click", () => { 
 +        explorerWord = 1; 
 +        get("input").value = String(toNumber(1)); 
 +        renderExplorer(); 
 +        setFeedback( 
 +          "conversion", 
 +          "Smallest positive binary32 value: exactly 2^(−149). Only the lowest fraction bit is 1." 
 +        ); 
 +      }); 
 + 
 +      function updateScore() { 
 +        get("score").textContent = 
 +          "Points: " + points + " | Completed challenges: " + completed; 
 +      } 
 + 
 +      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("target").textContent = numberText(target); 
 +        get("hint").hidden = true; 
 +        get("solution").hidden = true; 
 +        renderEditor(challengeEditor, answerWord); 
 +        setFeedback("feedback", "Build all three fields, then press Check answer."); 
 +        updateScore(); 
 +      } 
 + 
 +      root.querySelector('[data-action="check"]').addEventListener("click", () => { 
 +        const actual = fields(answerWord); 
 +        const expected = fields(targetWord); 
 + 
 +        if (answerWord === targetWord) { 
 +          if (!finished) { 
 +            points++; 
 +            completed++; 
 +            finished = true; 
 +          } 
 +          updateScore(); 
 +          setFeedback( 
 +            "feedback", 
 +            "Correct! " + fieldText(answerWord) + " represents " + 
 +            numberText(target) + ". Try the next number.", 
 +            "success" 
 +          ); 
 +        } else { 
 +          const status = (name, match) => name + ": " + (match ? "correct" : "check this field"); 
 +          setFeedback( 
 +            "feedback", 
 +            status("Sign", actual.s === expected.s) + ". " + 
 +            status("Exponent", actual.e === expected.e) + ". " + 
 +            status("Fraction", actual.f === expected.f) + ". " + 
 +            "Your current pattern represents " + numberText(toNumber(answerWord)) + ".", 
 +            "error" 
 +          ); 
 +        } 
 +      }); 
 + 
 +      root.querySelector('[data-action="hint"]').addEventListener("click", () => { 
 +        const p = fields(targetWord); 
 +        get("hint").hidden = false; 
 +        get("hint").textContent = 
 +          "Magnitude in binary: " + Math.abs(target).toString(2) + "\n" + 
 +          "Normalized magnitude: 1." + fractionText(p.f) + 
 +          " × 2^(" + (p.e - 127) + ")\n\n" + 
 +          "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('[data-action="solution"]').addEventListener("click", () => { 
 +        if (!finished) { 
 +          completed++; 
 +          finished = true; 
 +        } 
 +        const p = fields(targetWord); 
 +        get("solution").hidden = false; 
 +        get("solution").textContent = 
 +          "Sign: " + p.s + "\n" + 
 +          "Actual exponent: " + (p.e - 127) + "\n" + 
 +          "Stored exponent: " + (p.e - 127) + " + 127 = " + p.e + 
 +          " → " + bits(p.e, 8) + "\n" + 
 +          "Significand: 1." + fractionText(p.f) + " (binary)\n" + 
 +          "Stored fraction: " + bits(p.f, 23) + "\n\n" + 
 +          "Complete pattern:\n" + fieldText(targetWord); 
 +        setFeedback("feedback", "Solution shown. Rebuild it yourself, or try the next number."); 
 +        updateScore(); 
 +      }); 
 + 
 +      root.querySelector('[data-action="clear"]').addEventListener("click", () => { 
 +        answerWord = 0; 
 +        renderEditor(challengeEditor, answerWord); 
 +        setFeedback("feedback", "Bits cleared. All-zero binary32 represents +0."); 
 +      }); 
 + 
 +      root.querySelector('[data-action="next"]').addEventListener("click", () => { 
 +        newChallenge(false); 
 +      }); 
 + 
 +      convert("-13.25"); 
 +      newChallenge(true); 
 +    })(); 
 +  </script> 
 +</div> 
 +</html>
tanszek/oktatas/techcomm/floating-point_representation.txt · Last modified: by knehez