こちらは倍精度のみだったので様々な精度に対応したものを作ってみた。
- binary16
- シェーディング言語など
- binary32
- C/C++ の float 型
- binary64
- C/C++ の double 型
- Python の float 型
- JavaScript の Number 型
- binary128
- 普及するのは何時だろう?
拡張倍精度(80ビット)は非対応です。
See the Pen IEEE 754 浮動小数点数 詳細表示 by Ikiuo (@ikiuo) on CodePen.
HTML単体版
HTML ファイル(長いので折りたたみ)
sample.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>IEEE 754 浮動小数点数 詳細表示</title>
<style>
table {
border: none;
border-collapse: collapse;
overflow-x: auto;
}
th, td {
border: 1px solid #dfdfdf;
padding: 2px 4px;
}
.left { text-align: left; }
.center { text-align: center; }
.right { text-align: right; }
.vtop { vertical-align: top; }
.large { font-size: large; }
.small { font-size: small; }
.xsmall { font-size: x-small; }
.xxsmall { font-size: xx-small; }
.ctext:hover {
cursor: pointer;
background-color: #e2e2e2;
}
.ftext { color: #006; }
.htext { color: #600; }
.ntext {
word-break: break-all;
}
.eline { background: #edefef; }
.oline { background: #f5f6f6; }
.inpdata { background: #f5ffff; }
</style>
</head>
<body>
<script>
const LOG10_2 = 0.3010299956639812;
const gAbs = ((a, b) => a >= b ? a - b : b - a);
function power5n(n) {
var a = 1n;
var b = 5n;
while (n) {
if (n & 1)
a *= b;
b *= b;
n >>= 1;
}
return a;
}
const power10n = (n => power5n(n) << BigInt(n));
const BitLength = (x => x ? x.toString(2).length : 0);
const toString16N = ((v, n) => {
const l = Number(n);
return ('0'.repeat(l) + v.toString(16)).slice(-l);
});
const toString16B = ((v, b) => toString16N(v, (b + 3n) >> 2n));
/*
* 分数表現型
*/
class FractionBI {
#S;
nan; // 非数のとき true
finite; // 有限値のとき true
sign; // 負のとき true
numerator; // 分子 (BigInt)
denominator; // 分母 (BigInt)
constructor(value) {
const S = FractionBI;
this.#S = S;
switch (typeof value) {
case 'bigint':
this.#fromBigInt(value);
break;
case 'number':
this.#fromNumber(value);
break;
case 'string':
this.#fromString(value);
break;
default:
if (FractionBI.prototype.isPrototypeOf(value)) {
this.#fromRational(value);
break;
}
throw new Error("invalid type", {cause: value});
}
}
// 非数を設定
#setNaN(sign) {
this.nan = true;
this.finite = false;
if (sign != null)
this.sign = sign;
this.numerator = null;
this.denominator = null;
}
// 無限大を設定
#setInfinity(sign) {
this.nan = false;
this.finite = false;
if (sign != null)
this.sign = sign;
this.numerator = null;
this.denominator = null;
}
// 有限値を設定
#setFinite(sign, numerator, denominator) {
if (!denominator)
throw new Error("invalid denominator", {cause: denominator});
this.nan = false;
this.finite = true;
if (sign != null)
this.sign = sign;
let fnum = numerator;
let fden = denominator;
if (fnum) {
while ((fnum % 5n) == 0n &&
(fden % 5n) == 0n) {
fnum /= 5n;
fden /= 5n;
}
const t = (fnum | fden);
const s = BigInt(BitLength(t & -t) - 1);
fnum >>= s;
fden >>= s;
}
this.numerator = fnum;
this.denominator = fden;
}
// 0 を設定
#setZero(sign) {
this.nan = false;
this.finite = true;
if (sign != null)
this.sign = sign;
this.numerator = 0n;
this.denominator = 1n;
}
// FractionBI のコピー
#fromRational(value) {
this.nan = value.nan;
this.finite = value.finite;
this.sign = value.sign;
this.numerator = value.numerator;
this.denominator = value.denominator;
}
// BigInt 型を変換
#fromBigInt(value) {
this.nan = false;
this.finite = true;
this.sign = (value < 0n);
this.numerator = (value < 0n) ? -value : value;
this.denominator = 1n;
}
// Number 型を変換
#fromNumber(value) {
const mb64 = (1n << 64n) - 1n;
const mexp = ((1n << 11n) - 1n);
const mman = ((1n << 52n) - 1n);
const view = new DataView(new ArrayBuffer(8));
view.setFloat64(0, value, true);
const bin = mb64 & view.getBigInt64(0, true);
const sign = (1n & (bin >> 63n));
const exp = mexp & (bin >> 52n);
const man = mman & bin;
const fsign = sign != 0n;
if (exp == 0n && man == 0n) {
this.#setFinite(fsign, 0n, 1n);
return;
}
if (exp == mexp) {
if (mman != 0)
this.#setNaN(fsign);
else
this.#setInfinity(fsign);
return;
}
const nnum = man | (exp ? (1n << 52n) : 0n);
const nden = 1n << (exp ? 52n : 51n);
const fnum = nnum << (exp > 1023n ? exp - 1023n : 0n);
const fden = nden << (exp < 1023n ? 1023n - exp : 0n);
this.#setFinite(fsign, fnum, fden);
}
// 文字列から変換
#fromString(text) {
const S = this.#S;
text = text.trim().toLowerCase();
let fsign = false;
let fnum = 0n;
let fden = 1n;
let tpos = 0;
if (tpos >= text.length) {
this.#setNaN(fsign);
return;
}
switch (text[tpos]) {
case '-':
fsign = true;
// FALLTHROUGH
case '+':
if (++tpos < text.length)
break;
this.#setNaN(fsign);
return;
}
switch (text.slice(tpos)) {
case 'nan':
this.#setNaN(fsign);
return;
case 'inf':
case 'infinity':
this.#setInfinity(fsign);
return;
}
const dtab = S.#base10Table;
let btab = dtab;
let base = 10n;
if (tpos >= text.length) {
this.#setNaN(fsign);
return;
}
if (text[tpos] == '0') {
if (++tpos >= text.length) {
this.#setFinite(fsign, fnum, fden);
return;
}
switch (text[tpos]) {
case 'b':
btab = S.#base2Table;
base = 2n;
break;
case 'o':
btab = S.#base8Table;
base = 8n;
break;
case 'x':
btab = S.#base16Table;
base = 16n;
break;
}
if (base != 10n) {
if (++tpos >= text.length) {
this.#setNaN(fsign);
return;
}
}
}
let dot = false;
for (;; tpos++) {
if (tpos >= text.length) {
this.#setFinite(fsign, fnum, fden);
return;
}
const c = text[tpos];
const n = btab[c];
if (n != null) {
fnum = fnum * base + n;
if (dot)
fden *= base;
continue;
}
if (c == '_' || c == ',')
continue;
if (c != '.')
break;
if (dot) {
this.#setNaN(fsign);
return;
}
dot = true;
}
let expm = base;
switch (text[tpos]) {
case 'p':
if (expm == 10n) {
this.#setNaN(fsign);
return
}
expm = 2n;
// FALLTHROUGH
case 'e':
if (++tpos < text.length)
break;
// FALLTHROUGH
default:
this.#setNaN(fsign);
return;
}
let esign = false;
switch (text[tpos]) {
case '-':
esign = true;
// FALLTHROUGH
case '+':
if (++tpos < text.length)
break;
this.#setNaN(fsign);
return;
}
let xnum = 0n;
for (; tpos < text.length; tpos++) {
const c = text[tpos];
const n = dtab[c];
if (n == null) {
this.#setNaN(fsign);
return;
}
xnum = xnum * 10n + n;
}
if (xnum >= 65536n) {
if (esign)
this.#setZero(fsign);
else
this.#setInfinity(fsign);
return;
}
let expv = 1n;
while (xnum) {
if ((xnum & 1n))
expv *= expm;
expm *= expm;
xnum >>= 1n;
}
if (esign)
fden *= expv;
else
fnum *= expv;
this.#setFinite(fsign, fnum, fden);
}
static #base2Table = {
'0': 0n, '1': 1n,
}
static #base8Table = {
'0': 0n, '1': 1n, '2': 2n, '3': 3n,
'4': 4n, '5': 5n, '6': 6n, '7': 7n,
}
static #base10Table = {
'0': 0n, '1': 1n, '2': 2n, '3': 3n, '4': 4n,
'5': 5n, '6': 6n, '7': 7n, '8': 8n, '9': 9n,
}
static #base16Table = {
'0': 0n, '1': 1n, '2': 2n, '3': 3n,
'4': 4n, '5': 5n, '6': 6n, '7': 7n,
'8': 8n, '9': 9n, 'a': 10n, 'b': 11n,
'c': 12n, 'd': 13n, 'e': 14n, 'f': 15n,
}
}
/*
* IEEE 754 要素
*/
class IEEE754Parameter {
//
// 2進形式
//
static #makeBinary(exponentBits, mantissaBits) {
const mantissaMask = (1n << mantissaBits) - 1n;
const exponentMask = (1n << exponentBits) - 1n;
const signPosition = exponentBits + mantissaBits;
return {
mantissaBits,
mantissaMask,
mantissaPosition: 0n,
mantissaInt: 1n << mantissaBits,
exponentBits,
exponentMask,
exponentPosition: mantissaBits,
exponentBias: exponentMask >> 1n,
signBit: 1n << signPosition,
signPosition,
totalBits: signPosition + 1n,
getMantissa: (data => data & mantissaMask),
getExponent: (data => (data >> mantissaBits) & exponentMask),
getSign: (data => (data >> signPosition) & 1n),
}
}
static #binaryCache = {
16n: IEEE754Parameter.#makeBinary(5n, 10n),
32n: IEEE754Parameter.#makeBinary(8n, 23n),
64n: IEEE754Parameter.#makeBinary(11n, 52n),
128n: IEEE754Parameter.#makeBinary(15n, 112n),
}
static getBinary(n) {
const S = IEEE754Parameter;
const bn = BigInt(n);
const cache = S.#binaryCache
const cached = cache[bn];
if (cached != null)
return cached;
if (bn % 32n)
return null;
const nn = Number(n);
const p = BigInt(nn - Math.round(Math.log2(nn) * 4) + 13);
const param = S.#makeBinary(bn - p - 1n, p);
cache[n] = param;
return param;
}
}
/*
* IEEE 754 - 2進形式
*/
class IEEE754Binary extends FractionBI {
#S;
#P;
#round0;
exponent;
mantissa;
get data() {
const P = this.#P;
return (this.sign ? P.signBit : 0n) |
(this.exponent << P.exponentPosition) |
this.mantissa;
}
get exponent2() {
const dexp = this.exponent;
return (dexp ? dexp : 1n) - this.#P.exponentBias;
}
get mantissaBits() { return this.#P.mantissaBits; }
get exponentBits() { return this.#P.exponentBits; }
get dataBits() { return this.#P.totalBits; }
constructor(data_bits, value, round0) {
super(value);
this.#S = IEEE754Binary;
this.#P = IEEE754Parameter.getBinary(data_bits);
this.#round0 = Boolean(round0);
this.#initialize();
}
#initialize() {
const P = this.#P;
if (!this.finite) {
this.exponent = P.exponentMask;
this.mantissa = this.nan ? P.mantissaInt >> 1n : 0n;
return;
}
let fnum = this.numerator;
let fden = this.denominator;
if (!fnum) {
this.exponent = 0n;
this.mantissa = 0n;
return;
}
const bnum = BitLength(fnum);
const bden = BitLength(fden);
let exp = BigInt(bnum - bden);
if (exp >= 0)
fden <<= exp;
else
fnum <<= -exp;
if (fnum < fden) {
fnum <<= 1n;
exp -= 1n;
}
exp += P.exponentBias;
if (exp <= 0n) {
fden <<= 1n - exp;
exp = 0n;
}
const xnum = fnum << P.mantissaBits;
const xdiv = xnum / fden;
const xrem = xnum % fden;
const xcmp = (xrem << 1n) - fden;
const xrnd = !this.#round0 && ((xcmp > 0n) || ((xcmp == 0n) && (xdiv & 1n))) ? 1n : 0n;
let man = xdiv + xrnd;
const mshift = exp > 0n ? 1n : 0n;
if (man >= (P.mantissaInt << mshift)) {
man >>= mshift;
exp += 1n;
}
if (exp < P.exponentMask) {
this.exponent = exp;
this.mantissa = man & P.mantissaMask;
} else {
this.finite = false;
this.exponent = P.exponentMask;
this.mantissa = 0n;
}
}
#getMantissaEx() {
return this.mantissa | (this.exponent ? this.#P.mantissaInt : 0n);
}
isZero() {
return this.exponent == 0n && this.mantissa == 0n;
}
isPRN() {
return this.finite && !this.isZero();
}
toHexData() {
return '0x' + toString16B(this.data, this.dataBits);
}
#toStringSign(plus) {
return this.sign ? '-': plus ? '+' : '';
}
#toStringNPRN(plus) {
if (this.nan) return 'NaN';
const vsign = this.#toStringSign(plus);
if (!this.finite) return `${vsign}Infinity`;
if (this.isZero()) return `${vsign}0`;
return '';
}
static #toStringBN(dval, nexp, base) {
const dstr = dval.toString(base);
const mstr = '0'.repeat(Math.max(0, nexp - dstr.length)) + dstr;
const mint = mstr.slice(0, mstr.length - nexp);
const mdec = nexp ? mstr.slice(-nexp).replace(/0+$/, '') : '';
const vint = mint.length ? mint : '0';
const vdec = mdec.length ? `.${mdec}` : '';
return vint + vdec;
}
static #s10ToExp(s) {
const sign = (s[0] == '+' || s[0] == '-') ? s[0] : '';
if (sign) s = s.slice(1);
const dot = s.replace(/0+$/, '').split('.');
const sint = dot[0];
const sdec = dot[1] ?? '';
if (sint == '0') {
const rdec = sdec.replace(/^0+/, '');
const tnum = `${sign}${rdec[0]}.${rdec.slice(1)}`;
const rnum = tnum.replace(/\.$/, '');
return `${rnum}e${rdec.length - sdec.length - 1}`;
} else {
const rint = sint.replace(/^0+/, '');
const tnum = `${rint[0]}.${rint.slice(1)}${sdec}`;
const rnum = tnum.replace(/\.$/, '');
return `${rnum}e+${rint.length - 1}`
}
}
toExponential(plus) {
if (!this.isPRN())
return this.#toStringNPRN(plus);
return this.#S.#s10ToExp(this.toString10(plus));
}
#toStringBF(plus, base) {
if (!this.isPRN())
return this.#toStringNPRN(plus);
const bexp = this.exponent2 - this.mantissaBits;
const pexp = bexp > 0n ? bexp : 0n;
const nexp = bexp < 0n ? Number(-bexp) : 0;
const dbin = this.#getMantissaEx() << pexp;
const dval = base == 10 ? dbin * power5n(nexp) : dbin;
return this.#toStringSign(plus) + this.#S.#toStringBN(dval, nexp, base);
}
toString2F(plus) { return this.#toStringBF(plus, 2); }
toString10F(plus) { return this.#toStringBF(plus, 10); }
toString10(plus) {
if (!this.isPRN())
return this.#toStringNPRN(plus);
const S = this.#S;
const bits = this.dataBits;
const rnd0 = this.#round0;
const toS = S.#toStringBN;
const bexp = this.exponent2 - this.mantissaBits;
const pexp = bexp > 0n ? bexp : 0n;
const nexp = bexp < 0n ? Number(-bexp) : 0;
const dp5n = power5n(nexp);
const dval = (this.#getMantissaEx() << pexp) * dp5n;
const dcol = Math.trunc(LOG10_2 * (BitLength(dp5n << pexp) - 1));
let dmod = power10n(dcol);
let dcan = [dval];
for (;;) {
const fcan = dcan.map(v => v - v % dmod);
const ccan = fcan.map(v => [v, v + dmod]).flat();
const ncan = ccan.filter(v => {
const c = new S(bits, toS(v, nexp), rnd0);
return (c.finite &&
c.exponent == this.exponent &&
c.mantissa == this.mantissa);
});
if (!ncan.length)
break;
dcan = ncan;
dmod *= 10n;
}
dcan.sort((a, b) => {
const xa = gAbs(a, dval);
const xb = gAbs(b, dval);
return xa == xb ? 0 : xa < xb ? -1 : 1;
});
return this.#toStringSign(plus) + this.#S.#toStringBN(dcan[0], nexp);
}
toString16(plus) {
if (!this.isPRN())
return this.#toStringNPRN(plus);
const mbsz = this.mantissaBits;
const mshl = -mbsz & 3n;
const hdec = toString16B(this.mantissa << mshl, mbsz + mshl);
const hman = `0x${this.exponent ? 1 : 0}.${hdec}`;
const exp2 = this.exponent2;
const hexp = (exp2 >= 0 ? '+' : '') + `${exp2}`;
return this.#toStringSign(plus) + `${hman}p${hexp}`;
}
toSepSignExpMan() {
const sign = this.sign ? 1 : 0;
const exp = toString16B(this.exponent, this.exponentBits);
const man = toString16B(this.mantissa, this.mantissaBits);
return `${sign}:${exp}:${man}`
}
}
class IEEE754Binary16 extends IEEE754Binary { constructor() { super(16, ...arguments); } }
class IEEE754Binary32 extends IEEE754Binary { constructor() { super(32, ...arguments); } }
class IEEE754Binary64 extends IEEE754Binary { constructor() { super(64, ...arguments); } }
class IEEE754Binary128 extends IEEE754Binary { constructor() { super(128, ...arguments); } }
/*
*
*/
const viewOption = {}
const binaryName = {
16: '半精度',
32: '単精度',
64: '倍精度',
128: '四倍精度',
}
const binaryList = Object.keys(binaryName).map(v => Number(v));
const viewList = { bin: binaryList }
const valueName = {
'dat': '記憶域',
'sem': '負:指:仮',
'hex': '16進数',
'exp': '指数',
'dec': '非指数',
'xct': '厳密値',
'bin': '2進数',
}
const valueNameKeys = Object.keys(valueName);
const optionName = {
'rnd0': 'ゼロ方向丸め',
'flip': '表の転置',
}
const optionNameKeys = Object.keys(optionName);
/**/
let lastInput;
function toClipboard(text) {
navigator.clipboard.writeText(text).then(() => {
if (text.length >= 60)
text = text.slice(0, 57) + '⋅⋅⋅';
tagClipboard.innerText = text;
}, () => {
tagClipboard.innerText = 'コピー失敗';
});
}
const tagToClipboard = (tag => toClipboard(tag.innerText));
function updateViewer() {
const color_dis = 'lightgray';
const getTag = (n => document.getElementById(n));
const setfmt = ((m, n) => {
const tagTd = getTag(`tagMode_${n}`);
const tagInp = getTag(`chkbox_${n}`);
tagInp.checked = viewOption[n];
tagInp.disabled = !m;
tagTd.style.backgroundColor = m ? null : color_dis;
});
viewOption['fbin'] = binaryList.filter(n => viewOption.bin && viewOption[`bin${n}`]);
binaryList.forEach(n => setfmt(viewOption.bin, `bin${n}`));
[['val', valueNameKeys],
['opt', optionNameKeys],
].forEach(l => {
const [pfx, keys] = l;
keys.forEach(k => {
const key = `${pfx}${k}`;
const tagName = getTag(`chkbox_${key}`);
tagName.checked = viewOption[key];
});
});
}
/**/
const genCbTag = ((tag, cls, txt) =>
`<${tag} class="${cls}" onclick="toClipboard('${txt}')">${txt}</${tag}>`
);
const genCodeCbTag = ((cls, txt) => genCbTag('code', cls, txt));
function getValueNameList() {
return valueNameKeys.map(
k => viewOption[`val${k}`] ? valueName[k] : null
).filter(v=>v);
}
function getData(inp) {
const rnum = new FractionBI(inp);
const rnd0 = viewOption.optrnd0;
return {
inp: inp,
bin: {
list: viewOption.fbin.map(v => {
const bin = new IEEE754Binary(v, rnum, rnd0);
return {
data: bin,
tag: [
viewOption.valdat ? ['ctext htext', bin.toHexData()] : null,
viewOption.valsem ? ['ctext', bin.toSepSignExpMan()] : null,
viewOption.valhex ? ['ctext', bin.toString16()] : null,
viewOption.valexp ? ['ctext', bin.toExponential()] : null,
viewOption.valdec ? ['ctext', bin.toString10()] : null,
viewOption.valxct ? ['ctext ftext', bin.toString10F()] : null,
viewOption.valbin ? ['ctext ftext', bin.toString2F()] : null,
].filter(v=>v).map(l => genCodeCbTag(l[0], l[1])),
}
}),
},
}
}
function genOutputBinBlock1(nlist) {
const binf = nlist[0].bin; if (!binf.list.length) return '';
const getBinTag = ((i, b, n) => nlist[i].bin.list[b].tag[n]);
const cline = ['eline', 'oline'];
const tagHeader = getValueNameList();
return [
'<tr>',
'<th>形式</th>',
viewOption.fbin.map(n => `<th>binary${n}</th>`),
'</tr>',
nlist.map((s, i) => [
'<tr class="inpdata">',
'<th style="min-width: 3em;">入力</th>',
`<td class="ntext" colspan="${viewOption.fbin.length}">`,
genCodeCbTag('ctext large', s.inp),
'</td>',
'</tr>',
tagHeader.map((s, n) => [
`<tr class="${cline[n & 1]}">`,
`<th class="small">${s}</th>`,
viewOption.fbin.map((_, b) => [
'<td class="vtop ntext">', getBinTag(i, b, n), '</td>',
]),
'</tr>',
])]),
];
}
function genOutputBinBlock2(nlist) {
const binf = nlist[0].bin; if (!binf.list.length) return '';
const getBinTag = ((i, b, n) => nlist[i].bin.list[b].tag[n]);
const cline = ['eline', 'oline'];
const tagHeader = getValueNameList();
return [
'<tr>',
'<th>形式</th>',
tagHeader.map(n => `<th>${n}</th>`),
'</tr>',
nlist.map((s, i) => [
'<tr class="inpdata">',
'<th style="min-width: 3em;">入力</th>',
`<td class="ntext" colspan="${tagHeader.length}">`,
genCodeCbTag('ctext large', s.inp),
'</td>',
'</tr>',
viewOption.fbin.map((w, b) => [
`<tr class="${cline[b & 1]}">`,
`<th class="small">binary${w}</th>`,
tagHeader.map((_, n) => [
'<td class="vtop ntext">', getBinTag(i, b, n), '</td>',
]),
'</tr>',
])]),
];
}
const input_separator = [
[...Array(33)].map((_, i) => String.fromCodePoint(i)),
'!"#$%&\'()*,/:;<=>?@[\\]^`{|}~',
].join('');
function onUpdate(inp) {
inp = inp ?? tagNumber.innerText;
const nums = [...input_separator].reduce(
(p, c) => p.map(s => s.split(c)).flat(), [inp]
).filter(v => v != null && v.length);
if (!nums.length)
return;
lastInput = inp;
const nlist = nums.map(s => getData(s));
const bintags = (viewOption.optflip ? genOutputBinBlock2 : genOutputBinBlock1)(nlist);
tagOutput.innerHTML = [
'<table>', bintags, '</table>', '<p></p>',
].flat(8).join('');
}
function onChangeViewer(tag) {
viewOption[tag.id.split('_')[1]] = tag.checked;
updateViewer();
onUpdate(lastInput);
}
/*
*
*/
window.onload = (() => {
const chkbox = ((n, l) => [
`<input id="chkbox_${n}" name="chkbox_${n}" type="checkbox"`,
` onchange="onChangeViewer(this)">`,
`<label id="label_${n}" for="chkbox_${n}">${l}</label>`,
]);
const exact = ((n) => new IEEE754Binary64(n).toString16());
document.body.innerHTML = [
'<h3>IEEE 754 浮動小数点数 詳細表示</h3>',
'<div><textarea',
' id="tagNumber"', ' cols="72"', ' rows="5"',
' placeholder="数値を入力してください"',
' oninput="onUpdate(value)">',
'</textarea></div>',
/**/
'<details id="tagSettings"><summary>表示設定</summary>',
'<table>',
'<tr>', '<th>データ型</th>',
Object.entries(binaryName).map(prec => {
const [b, l] = prec;
return [`<td id="tagMode_bin${b}">`, chkbox(`bin${b}`, l), `(${b})</td>`];
}),
'</tr>',
'</table>',
'<table style="margin-top: 2px;">',
'<tr>', '<th>表示項目</th>',
Object.entries(valueName).map(v => [
`<td id="tagView_val${v[0]}">`, chkbox(`val${v[0]}`, v[1]), '</td>'
]),
'</tr>',
'</table>',
'<table style="margin-top: 2px;">',
'<tr>', '<th>その他</th>',
Object.entries(optionName).map(v => [
`<td id="tagOption_opt${v[0]}">`, chkbox(`opt${v[0]}`, v[1]), '</td>'
]),
'</table>',
'</details>',
/**/
'<div> </div>',
'<div class="xsmall">クリップボード:<code id="tagClipboard">(数値をクリック)</code></div>',
'<div> </div>',
'<div id="tagOutput"></div>',
'<div> </div>',
'<table>',
'<tr><th colspan="4">定数テスト(binary64)</th></tr>',
[['PI', 'LN2', 'LOG2E', 'SQRT1_2'],
['E', 'LN10', 'LOG10E', 'SQRT2'],
].map(l => [
'<tr>',
l.map(n => [
'<td style="padding: 2px;">',
`<button onclick="onUpdate('${exact(Math[n])}')">Math.${n}</button>`,
'</td>',
]),
'</tr>',
]),
'</table>',
'<div> </div>',
'<table>',
'<tr class="small"><th colspan="2">説明</th></tr>',
'<tr class="small eline"><th>入力</th><td>改行などの区切りで複数値に対応</td></tr>',
'<tr class="small"><th>表示項目</th><th>浮動小数点数に対する内容</th></tr>',
'<tr class="small eline"><th>記憶域</th><td>メモリ上のデータ</td></tr>',
'<tr class="small oline"><th>負:指:仮</th><td>符号ビット、指数部(16進)、仮数部(16進)の値</td></tr>',
'<tr class="small eline"><th>16進数</th><td>指数形式16進数(厳密値)</td></tr>',
'<tr class="small oline"><th>指数</th><td>指数形式10進数(近似値)</td></tr>',
'<tr class="small eline"><th>非指数</th><td>10進数(近似値)</td></tr>',
'<tr class="small oline"><th>厳密値</th><td>10進数(厳密値)</td></tr>',
'<tr class="small eline"><th>2進数</th><td>2進数(厳密値) : 前置 \'0b\' は省略</td></tr>',
'</table>',
].flat(9).join('');
/*
* URLの引数
*
* on=<type> または off=<type> で表示を制御
*
* <type>:
* setting: 表示設定
*
* bin16: 2進数半精度
* bin32: 2進数単精度
* bin64: 2進数倍精度
* bin128: 2進数四倍精度
*
* valdat: 記憶域
* valsem: 負:指:仮
* valhex: 16進
* valexp: 指数
* valdec: 非指数
* valxct: 厳密値
* valbin: 2進数
*
* optrnd0: ゼロ方向丸め
* optflip: 表の転置
*/
const set_flag = ((f, v) => {
const p = v.slice(0, 3);
if (viewList[p]) {
if (f)
viewOption[p] = f;
viewOption[v] = f;
return;
}
const s = v.slice(3);
if (p == 'val') {
if (valueName[s])
viewOption[v] = f;
return;
}
if (p == 'opt') {
if (optionName[s])
viewOption[v] = f;
return;
}
if (v == 'setting') {
tagSettings.open = f;
return;
}
// ...
});
const set_flags = ((f, l) => l.forEach(v => set_flag(f, v)));
/**/
const url = new URL(window.location);
const search = url.searchParams;
const get_arglist = (l => l.map(n => search.getAll(n).map(v => v.split(','))).flat(5));
const args_on = get_arglist(['on', 's']);
const args_off = get_arglist(['off', 'u']);
const args_inp = search.getAll('n').join('\n');
set_flags(true, ['bin32', 'bin64']);
set_flags(true, valueNameKeys.map(k => `val${k}`));
set_flags(true, args_on);
set_flags(false, args_off);
updateViewer();
onUpdate(args_inp.length ? args_inp : `${exact(Math.PI)}`);
});
</script>
</body>
</html>
Python コマンドライン版
Python ソースコード(長いので折りたたみ)
sample.py
#!/usr/bin/env python3
import math
class IEEE754Binary:
"""IEEE 754 - Binary 形式"""
class _Parameter:
"""符号部、指数部、仮数部の情報"""
def __init__(self, exponent_bits: int, mantissa_bits: int):
self.mantissa_position = 0
self.mantissa_bits = mantissa_bits
self.mantissa_int = 1 << mantissa_bits
self.mantissa_mask = self.mantissa_int - 1
self.exponent_position = mantissa_bits
self.exponent_bits = exponent_bits
self.exponent_mask = (1 << exponent_bits) - 1
self.exponent_bias = self.exponent_mask >> 1
self.sign_position = self.exponent_position + exponent_bits
self.sign_bit = 1 << self.sign_position
# ----------
_BINARY_TABLE = {
16: _Parameter(5, 10),
32: _Parameter(8, 23),
64: _Parameter(11, 52),
128: _Parameter(15, 112),
}
# ----------
_BASE2_TABLE = {f'{n}': n for n in range(2)}
_BASE8_TABLE = {f'{n}': n for n in range(8)}
_BASE10_TABLE = {f'{n}': n for n in range(10)}
_BASE16_TABLE = {f'{n:x}': n for n in range(16)}
_BASE_TABLE = {
'b': (2, _BASE2_TABLE),
'o': (8, _BASE8_TABLE),
'x': (16, _BASE16_TABLE),
}
# ----------
def __init__(self, data_bits: int, value: any = None, round_toward_zero: bool = None):
self._round_toward_zero = round_toward_zero
self._data_bits = data_bits
self._parameter = IEEE754Binary._BINARY_TABLE[self._data_bits]
self._binary = None
self._nan = None
self._finite = None
self._sign = None
self._exponent = None
self._mantissa = None
self._numerator = None
self._denominator = None
if value is None:
self._set_zero(False)
elif isinstance(value, int):
self._from_int(value)
elif isinstance(value, float):
self._from_float(value)
elif isinstance(value, str):
self._from_string(value)
elif isinstance(value, IEEE754Binary):
self._from_binary(value)
else:
raise TypeError(value)
def __repr__(self) -> str:
"""10進数文字列を返す"""
return self._str_decimal()
# ----------
@property
def round_toward_zero(self) -> bool:
"""ゼロ方向丸めの場合は True"""
return self._round_toward_zero
@property
def data_bits(self) -> int:
"""浮動小数点データのビット数"""
return self._data_bits
@property
def binary(self) -> int:
"""記憶域用のデータ"""
return self._binary
@property
def nan(self) -> bool:
"""非数の場合は True"""
return self._nan
@property
def finite(self) -> bool:
"""有限値の場合は True"""
return self._finite
@property
def sign(self) -> (bool, None):
"""負の場合は True"""
return self._sign
@property
def sign_position(self) -> int:
"""符号のビット位置"""
return self._parameter.sign_position
@property
def exponent(self) -> int:
"""指数部のデータ"""
return self._exponent
@property
def exponent_position(self) -> int:
"""指数部のビット位置"""
return self._parameter.exponent_position
@property
def exponent_bias(self) -> int:
"""指数部のバイアス値"""
return self._parameter.exponent_bias
@property
def exponent_bits(self) -> int:
"""指数部のビット数"""
return self._parameter.exponent_bits
@property
def mantissa(self) -> int:
"""仮数部のデータ"""
return self._mantissa
@property
def mantissa_bits(self) -> int:
"""仮数部のビット数"""
return self._parameter.mantissa_bits
@property
def numerator(self) -> (int, None):
"""分数表現での分子"""
return self._numerator
@property
def denominator(self) -> (int, None):
"""分数表現での分母"""
return self._denominator
# ----------
def _set_binary(self) -> 'IEEE754Binary':
param = self._parameter
sign = param.sign_bit if self.sign else 0
exp = self._exponent << param.exponent_position
self._binary = sign | exp | self._mantissa
return self
def _set_nan(self, sign: bool) -> 'IEEE754Binary':
param = self._parameter
self._nan = True
self._finite = False
self._sign = sign
self._exponent = param.exponent_mask
self._mantissa = param.mantissa_int >> 1
self._numerator = None
self._denominator = None
return self._set_binary()
def _set_inf(self, sign: bool) -> 'IEEE754Binary':
self._nan = False
self._finite = False
self._sign = sign
self._exponent = self._parameter.exponent_mask
self._mantissa = 0
self._numerator = None
self._denominator = None
return self._set_binary()
def _set_zero(self, sign: bool) -> 'IEEE754Binary':
self._nan = False
self._finite = True
self._sign = sign
self._exponent = 0
self._mantissa = 0
self._numerator = 0
self._denominator = 1
return self._set_binary()
def _set_finite(self, sign: bool, numerator: int, denominator: int) -> 'IEEE754Binary':
if not denominator:
return (self._set_inf if numerator else self._set_nan)(sign)
if not numerator:
return self._set_zero(sign)
self._nan = False
self._finite = True
self._sign = sign
param = self._parameter
bmask = numerator | denominator
shift = (bmask & -bmask).bit_length() - 1
numerator >>= shift
denominator >>= shift
while True:
ndiv, nmod = divmod(numerator, 5)
ddiv, dmod = divmod(denominator, 5)
if nmod or dmod:
break
numerator = ndiv
denominator = ddiv
self._numerator = numerator
self._denominator = denominator
fnum = numerator
fden = denominator
exp = fnum.bit_length() - fden.bit_length()
if exp >= 0:
fden <<= exp
else:
fnum <<= -exp
if fnum < fden:
fnum <<= 1
exp -= 1
exp += param.exponent_bias
if exp <= 0:
fden <<= 1 - exp
exp = 0
xnum = fnum << param.mantissa_bits
xdiv = xnum // fden
xrem = xnum % fden
xcmp = (xrem << 1) - fden
xrnd = (1 if not self._round_toward_zero and
((xcmp > 0) or ((xcmp == 0) and (xdiv & 1))) else 0)
man = xdiv + xrnd
mshift = int(exp > 0)
if man >= (param.mantissa_int << mshift):
man >>= mshift
exp += 1
if exp < param.exponent_mask:
self._exponent = exp
self._mantissa = man & param.mantissa_mask
else:
self._finite = False
self._exponent = param.exponent_mask
self._mantissa = 0
return self._set_binary()
def setfrac(self, sign: bool, numerator: int, denominator: int) -> 'IEEE754Binary':
"""分数で値を設定する"""
return self._set_finite(sign, numerator, denominator)
@staticmethod
def fromfrac(data_bits: int, round_toward_zero: bool,
sign: bool, numerator: int, denominator: int) -> 'IEEE754Binary':
"""分数から IEEE754Binary を作る"""
fpval = IEEE754Binary(data_bits, None, round_toward_zero)
return fpval.setfrac(sign, numerator, denominator)
# ----------
def _from_int(self, rhs: int) -> 'IEEE754Binary':
return self._set_finite(rhs < 0, abs(rhs), 1)
def _from_float(self, rhs: float) -> 'IEEE754Binary':
sign = math.copysign(1.0, rhs) < 0
if not math.isfinite(rhs):
return (self._set_nan if math.isnan(rhs) else self._set_inf)(sign)
if not rhs:
return self._set_zero(sign)
man, exp = math.frexp(rhs)
iman = int(man * (1 << 53))
iexp = exp - 53
bnum = iman << max(0, iexp)
bden = 1 << max(0, -iexp)
return self._set_finite(sign, bnum, bden)
def _from_binary(self, rhs: 'IEEE754Binary') -> 'IEEE754Binary':
self._round_toward_zero = rhs.round_toward_zero
self._data_bits = rhs.data_bits
self._parameter = IEEE754Binary._BINARY_TABLE[self._data_bits]
self._binary = rhs.binary
self._nan = rhs.nan
self._finite = rhs.finite
self._sign = rhs.sign
self._exponent = rhs.exponent
self._mantissa = rhs.mantissa
self._numerator = rhs.numerator
self._denominator = rhs.denominator
return self
def _from_string(self, text: str) -> 'IEEE754Binary':
text = text.strip().lower()
fsign = False
fnum = 0
fden = 1
tpos = 0
if tpos >= len(text):
return self._set_nan(fsign)
tch = text[tpos]
if tch in ('-+'):
fsign = tch == '-'
tpos += 1
if tpos >= len(text):
return self._set_nan(fsign)
tch = text[tpos:]
if tch == 'nan':
return self._set_nan(fsign)
if tch in ('inf', 'infinity'):
return self._set_inf(fsign)
dtab = self._BASE10_TABLE
btab = dtab
base = 10
if tpos >= len(text):
return self._set_nan(fsign)
tch = text[tpos]
if tch == '0':
tpos += 1
if tpos >= len(text):
return self._set_finite(fsign, fnum, fden)
tch = text[tpos]
bmap = self._BASE_TABLE.get(tch)
if bmap:
base, btab = bmap
tpos += 1
if tpos >= len(text):
return self._set_nan(fsign)
tch = text[tpos]
dot = False
while True:
cnum = btab.get(tch)
if cnum is not None:
fnum = fnum * base + cnum
if dot:
fden *= base
elif tch not in ('_,'):
if tch != '.':
break
if dot:
return self._set_nan(fsign)
dot = True
tpos += 1
if tpos >= len(text):
return self._set_finite(fsign, fnum, fden)
tch = text[tpos]
expm = base
if tch not in ('ep'):
return self._set_nan(fsign)
if tch == 'p':
if expm == 10:
return self._set_nan(fsign)
expm = 2
tpos += 1
if tpos >= len(text):
return self._set_nan(fsign)
tch = text[tpos]
esign = False
if tch in ('-+'):
esign = tch == '-'
tpos += 1
if tpos >= len(text):
return self._set_nan(fsign)
tch = text[tpos]
xnum = 0
while True:
cnum = dtab.get(tch)
if cnum is None:
return self._set_nan(fsign)
xnum = xnum * 10 + cnum
tpos += 1
if tpos >= len(text):
break
tch = text[tpos]
if xnum >= 10000000:
return (self._set_zero if esign else self._set_inf)(fsign)
expv = 1
while xnum:
if xnum & 1:
expv *= expm
expm *= expm
xnum >>= 1
if esign:
fden *= expv
else:
fnum *= expv
return self._set_finite(fsign, fnum, fden)
# ----------
def _is_prn(self) -> bool:
return not self._finite or not (self._exponent or self._mantissa)
def _str_sign(self, plus: bool = None) -> str:
return '-' if self._sign else '+' if plus else ''
def _str_nan(self) -> str:
return 'nan'
def _str_inf(self, plus: bool = None) -> str:
return self._str_sign(plus) + 'inf'
def _str_zero(self, plus: bool = None) -> str:
return self._str_sign(plus) + '0'
def _str_prn(self, plus: bool = None) -> str:
return (self._str_nan() if self._nan else
(self._str_zero if self._finite else self._str_inf)(plus))
def _str_decimal(self, plus: bool = None, efmt: bool = None, exact: bool = None) -> str:
if self._is_prn():
return self._str_prn(plus)
param = self._parameter
dexp = self._exponent
dman = self._mantissa
bexp = max(1, dexp) - param.exponent_bias - param.mantissa_bits
pexp = max(0, bexp)
nexp = max(0, -bexp)
bman = dman | (param.mantissa_int if dexp else 0)
dp5n = pow(5, nexp)
fnum = (bman << pexp) * dp5n
fden = dp5n << nexp
log10_2 = 0.3010299956639812
dmod = pow(10, int(log10_2 * ((dp5n << pexp).bit_length() - 1)))
_new = self.fromfrac
bits = self._data_bits
frtz = self._round_toward_zero
def check(value):
chk = _new(bits, frtz, False, value, fden)
return chk.finite and chk.exponent == dexp and chk.mantissa == dman
dcan = [fnum]
while not exact:
fcan = [v - v % dmod for v in dcan]
ccan = [v + dmod for v in fcan]
ncan = list(filter(check, fcan + ccan))
if not ncan:
break
dcan = ncan
dmod *= 10
fnum = sorted(dcan, key=lambda v: abs(v - fnum))[0]
snum = self._format_decimal(fnum, nexp)
if efmt:
snum = self._str_dec_exp(snum)
return f'{self._str_sign(plus)}{snum}'
def _str_hexadecimal(self, plus: bool = None) -> str:
if self._is_prn():
return self._str_prn(plus)
param = self._parameter
dexp = self._exponent
dman = self._mantissa
mbsz = param.mantissa_bits
mshl = -mbsz & 3
hdec = self._format_hexadecimal(dman << mshl, mbsz + mshl)
hman = f'0x{int(bool(dexp))}.{hdec}'
exp2 = max(1, dexp) - param.exponent_bias
hexp = f'+{exp2}' if exp2 >= 0 else f'{exp2}'
return f'{self._str_sign(plus)}{hman}p{hexp}'
@staticmethod
def _format_decimal(dval: int, nexp: int) -> str:
dstr = str(dval)
if not nexp:
return dstr
mstr = '0' * max(0, nexp - len(dstr)) + dstr
mint, mdec = mstr[:-nexp], mstr[-nexp:].rstrip('0')
return ((mint if mint else '0') + f'.{mdec}').rstrip('.')
@staticmethod
def _str_dec_exp(nstr: str) -> str:
sint, sdec = (nstr.split('.') + [''])[:2]
if sint == '0':
edec = len(sdec)
sdec = sdec.lstrip('0')
snum = f'{sdec[:1]}.{sdec[1:]}'.rstrip('.')
return f'{snum}e-{edec - len(sdec) + 1}'
eint = len(sint)
snum = (sint + sdec).rstrip('0')
snum = f'{snum[:1]}.{snum[1:]}'.rstrip('.')
return f'{snum}e+{eint - 1}'
@staticmethod
def _format_hexadecimal(value: int, bits: int) -> str:
cnt = max(1, (bits + 3) >> 2)
return ('0' * cnt + f'{value:x}')[-cnt:]
# ----------
def data(self):
"""記憶域用の16進データ文字列を返す"""
return '0x' + self._format_hexadecimal(self._binary, self._data_bits)
def sep(self):
"""負:指:仮 分離形式を返す"""
param = self._parameter
sign = int(self._sign)
exp = self._format_hexadecimal(self._exponent, param.exponent_bits)
man = self._format_hexadecimal(self._mantissa, param.mantissa_bits)
return f'{sign}:{exp}:{man}'
def dec(self, plus: bool = None, efmt: bool = None) -> str:
"""10進文字列を返す"""
return self._str_decimal(plus=plus, efmt=efmt)
def full(self, plus: bool = None, efmt: bool = None) -> str:
"""10進全桁の文字列を返す"""
return self._str_decimal(plus=plus, efmt=efmt, exact=True)
def hex(self, plus: bool = None) -> str:
"""指数形式16進文字列を返す"""
return self._str_hexadecimal(plus=plus)
class IEEE754Binary16(IEEE754Binary):
def __init__(self, value: any = None, round_toward_zero: bool = None):
super().__init__(16, value, round_toward_zero)
class IEEE754Binary32(IEEE754Binary):
def __init__(self, value: any = None, round_toward_zero: bool = None):
super().__init__(32, value, round_toward_zero)
class IEEE754Binary64(IEEE754Binary):
def __init__(self, value: any = None, round_toward_zero: bool = None):
super().__init__(64, value, round_toward_zero)
class IEEE754Binary128(IEEE754Binary):
def __init__(self, value: any = None, round_toward_zero: bool = None):
super().__init__(128, value, round_toward_zero)
if __name__ == '__main__':
import argparse
import sys
def main():
parser = argparse.ArgumentParser()
# parser.add_argument('-v', '--verbose', action='store_true')
parser.add_argument('-H', '--binary16', action='store_true', help='半精度(binary16)を使用する')
parser.add_argument('-S', '--binary32', action='store_true', help='単精度(binary32)を使用する')
parser.add_argument('-D', '--binary64', action='store_true', help='倍精度(binary64)を使用する')
parser.add_argument('-Q', '--binary128', action='store_true', help='四倍精度(binary128)を使用する')
parser.add_argument('-s', '--storage', action='store_true', help='記憶域用データを表示する')
parser.add_argument('-p', '--parameter', action='store_true', help='負:指:仮 分離形式を表示する')
parser.add_argument('-x', '--hex', action='store_true', help='16進数を表示する')
parser.add_argument('-e', '--exp', action='store_true', help='指数形式10進数を表示する')
parser.add_argument('-d', '--dec', action='store_true', help='10進数を表示する')
parser.add_argument('-f', '--full', action='store_true', help='厳密値を表示する')
parser.add_argument('-0', '--roundtowardzero', action='store_true', help='ゼロ方向丸めを使用する')
parser.add_argument('numbers', metavar='N', nargs='+')
args = parser.parse_args()
binary_types = tuple(filter(None, [
('binary16', IEEE754Binary16) if args.binary16 else None,
('binary32', IEEE754Binary32) if args.binary32 else None,
('binary64', IEEE754Binary64) if args.binary64 else None,
('binary128', IEEE754Binary128) if args.binary128 else None,
]))
if not binary_types:
print('オプション -H, -S, -D, -Q から1つ以上指定してください')
parser.print_help()
sys.exit(2)
sep = '-' * 32
for num in args.numbers:
print('\n'.join([sep, f'入力文字列: "{num}"', sep]))
for bname, btype in binary_types:
fpval = btype(num, round_toward_zero=args.roundtowardzero)
[print(line) for line in filter(lambda s: s is not None, [
f'{bname}',
f' 記憶域: {fpval.data()}' if args.storage else None,
f' 分離 : {fpval.sep()}' if args.parameter else None,
f' 16進 : {fpval.hex()}' if args.hex else None,
f' 指数 : {fpval.dec(efmt=True)}' if args.exp else None,
f' 非指数: {fpval.dec()}' if args.dec else None,
f' 厳密値: {fpval.full()}' if args.full else None,
'',
])]
sys.exit(main())
実行結果(ゼロ方向丸め)
% ./sample.py -HSD -spxedfb -0 0.001
--------------------------------
入力文字列: "0.001"
--------------------------------
binary16
記憶域: 0x1418
分離 : 0:05:018
16進 : 0x1.060p-10
指数 : 1e-3
非指数: 0.001
厳密値: 0.00099945068359375
2進数 : 0.00000000010000011
binary32
記憶域: 0x3a83126e
分離 : 0:75:03126e
16進 : 0x1.0624dcp-10
指数 : 1e-3
非指数: 0.001
厳密値: 0.00099999993108212947845458984375
2進数 : 0.00000000010000011000100100110111
binary64
記憶域: 0x3f50624dd2f1a9fb
分離 : 0:3f5:0624dd2f1a9fb
16進 : 0x1.0624dd2f1a9fbp-10
指数 : 1e-3
非指数: 0.001
厳密値: 0.00099999999999999980397624721462079833145253360271453857421875
2進数 : 0.00000000010000011000100100110111010010111100011010100111111011
実行結果(近傍丸め)
% ./sample.py -HSD -spxedfb 0.001
--------------------------------
入力文字列: "0.001"
--------------------------------
binary16
記憶域: 0x1419
分離 : 0:05:019
16進 : 0x1.064p-10
指数 : 1e-3
非指数: 0.001
厳密値: 0.00100040435791015625
2進数 : 0.00000000010000011001
binary32
記憶域: 0x3a83126f
分離 : 0:75:03126f
16進 : 0x1.0624dep-10
指数 : 1e-3
非指数: 0.001
厳密値: 0.001000000047497451305389404296875
2進数 : 0.000000000100000110001001001101111
binary64
記憶域: 0x3f50624dd2f1a9fc
分離 : 0:3f5:0624dd2f1a9fc
16進 : 0x1.0624dd2f1a9fcp-10
指数 : 1e-3
非指数: 0.001
厳密値: 0.001000000000000000020816681711721685132943093776702880859375
2進数 : 0.000000000100000110001001001101110100101111000110101001111111