Numbers in a DWG file, read and written bit for bit
Reads and writes the number codes of a DWG file: full and compressed 64-bit values, whole numbers of several sizes and object handles.
How the precision is kept
A 64-bit value is moved between the file and memory as its eight bytes through a Float64Array view, with no arithmetic, so what is read is exactly what the file holds and what is written is exactly the value in the model. The compressed forms (a two-bit code for 0.0 and 1.0, a value that shares bytes with a default) are exact as well. Whole numbers wider than 53 bits are the only place a value can be changed.
Calculations under this policy
16 calculations in 2 source files. The code is read from the released app when this page loads. It is the calculation people run, not a copy kept for this page.
A 64-bit value read from eight bytes of the bit stream.
The eight bytes are placed into a Float64Array's byte view and the number read back, with no arithmetic: every finite value, negative zero included, is exactly the stored one. The byte order is the machine's, so the code assumes a little-endian machine.
dwg/bits-read.js
lines 78 to 81
CadCraft 0.8.2
RD() {
for (let i = 0; i < 8; i++) f64b[i] = this.RC();
return f64[0];
}
Bit double, read
64-bit
A value stored as a two-bit code: a full 64-bit value, exactly 1.0 or exactly 0.0.
Codes 1 and 2 return the constants 1.0 and 0.0; code 0 reads a raw double. Code 3 is an error, not a value.
dwg/bits-read.js
lines 115 to 123
CadCraft 0.8.2
/** 2.5 BITDOUBLE. */
BD() {
switch (this.BB()) {
case 0: return this.RD();
case 1: return 1.0;
case 2: return 0.0;
default: throw new DwgReadError(msg('ccdwg.err.bitdouble'));
}
}
Double with default, read
64-bit
A value stored as the bytes in which it differs from a default value.
The default's bytes are loaded and the low four, or the low four and the next two, are replaced by bytes from the file; no arithmetic touches the value. The result is exact when the default passed in is the one the writer used.
dwg/bits-read.js
lines 124 to 141
CadCraft 0.8.2
/** 2.9 BitDouble with default. */
DD(def) {
switch (this.BB()) {
case 0: return def;
case 1: {
f64[0] = def;
for (let i = 0; i < 4; i++) f64b[i] = this.RC();
return f64[0];
}
case 2: {
f64[0] = def;
f64b[4] = this.RC(); f64b[5] = this.RC();
for (let i = 0; i < 4; i++) f64b[i] = this.RC();
return f64[0];
}
default: return this.RD();
}
}
64-bit whole number, read
64-bit
An unsigned 64-bit whole number from two 32-bit halves.
value = high × 2³² + low
The halves are combined as an ordinary 64-bit number, which holds whole numbers exactly only up to 2^53. A larger value is rounded to the nearest representable number.
dwg/bits-read.js
line 77
CadCraft 0.8.2
RLL() { const lo = this.RL(), hi = this.RL(); return hi * 4294967296 + lo; }
Bit long long, read
64-bit
A whole number of up to seven bytes, the count given by three bits.
value = Σ byteᵢ × 256ⁱ
Summed as an ordinary number, low byte first. Seven bytes can hold up to 2^56, so a value above 2^53 is rounded.
dwg/bits-read.js
lines 108 to 114
CadCraft 0.8.2
/** 2.4 BITLONGLONG. */
BLL() {
const n = this.bits(3);
let v = 0, m = 1;
for (let i = 0; i < n; i++) { v += this.RC() * m; m *= 256; }
return v;
}
Handle reference, read
64-bit
An object handle, absolute or as an offset from another handle.
value = Σ byte × 256ᵏ; codes 6, 8, A, C: ref + 1, ref − 1, ref + value, ref − value
Handles are ordinary numbers, exact up to 2^53; the code comment says larger ones do not occur in practice. A relative code below a small reference can give a negative handle; the function does not check.
dwg/bits-read.js
lines 167 to 184
CadCraft 0.8.2
/**
* 2.13 handle reference → absolute handle (Number; handles above 2^53 do not occur in practice).
* `ref` is the handle offsets are taken from (codes 6, 8, A, C). `this.hcode` keeps the code.
*/
H(ref = 0) {
const b = this.RC();
const code = b >> 4, n = b & 15;
let v = 0;
for (let i = 0; i < n; i++) v = v * 256 + this.RC();
this.hcode = code;
switch (code) {
case 6: return ref + 1;
case 8: return ref - 1;
case 0xA: return ref + v;
case 0xC: return ref - v;
default: return v;
}
}
Modular char, read
64-bit
A signed whole number stored seven bits per byte.
value = Σ (byte & 0x7F) × 2^(7i); the last byte carries 6 bits and the sign
The groups are added with multiplications by powers of two, not with 32-bit shifts, so values above 2^31 are not truncated; exact up to 2^53.
dwg/bits-read.js
lines 267 to 278
CadCraft 0.8.2
/** Modular char, signed (2.6). */
export function readMC(b, pos) {
let v = 0, shift = 0, byte;
for (;;) {
byte = b[pos++];
if (byte & 0x80) { v += (byte & 0x7F) * 2 ** shift; shift += 7; continue; }
const neg = byte & 0x40;
v += (byte & 0x3F) * 2 ** shift;
mod.pos = pos;
return neg ? -v : v;
}
}
The value is stored into a Float64Array and its eight bytes are written in order: no rounding and no conversion to text.
dwg/bits-write.js
line 54
CadCraft 0.8.2
RD(v) { f64[0] = v; for (let i = 0; i < 8; i++) this.RC(f64b[i]); }
Bit double, written
64-bit
A value written as a two-bit code for exactly 0.0 or 1.0, or as a full 64-bit value.
The short codes are used only for values exactly equal to 0 or 1. Negative zero is written in full, so its sign survives.
dwg/bits-write.js
lines 80 to 82
CadCraft 0.8.2
BD(v) {
if (v === 0 && !Object.is(v, -0)) this.BB(2); else if (v === 1) this.BB(1); else { this.BB(0); this.RD(v); }
}
Double with default, written
64-bit
A value written as a two-bit code when it equals its default, or in full.
Only two forms are written: equal to the default, or all eight bytes. The partial forms that share bytes with the default are never written. Negative zero compares equal to a default of zero and is then written as the default.
dwg/bits-write.js
lines 83 to 84
CadCraft 0.8.2
/** BitDouble with default (2.9): same as the default → 00, else a full RD (11). */
DD(v, def) { if (v === def || (Number.isNaN(v) && Number.isNaN(def))) this.BB(0); else { this.BB(3); this.RD(v); } }
Thickness, written
64-bit
A thickness written as one bit when it is zero, or as a bit double.
The zero test is a truth test, so a thickness that is not a number is written as zero as well.
dwg/bits-write.js
lines 85 to 86
CadCraft 0.8.2
/** BitThickness (2.10): R2000+ one bit for 0.0. */
BT(v) { if (!v) this.B(1); else { this.B(0); this.BD(v); } }
Bit long, written
Exact
A 32-bit whole number in its short or full form.
The value is truncated toward zero to a whole number first, and a value that is not a number becomes 0. A value outside 32 bits is wrapped by the unsigned shift, not refused.
dwg/bits-write.js
lines 61 to 66
CadCraft 0.8.2
BL(v) {
v = Math.trunc(v) || 0;
if (v === 0) this.BB(2);
else if (v > 0 && v < 256) { this.BB(1); this.RC(v); }
else { this.BB(0); this.RL(v >>> 0); }
}
Bit short, written
Exact
A 16-bit whole number in its short or full form.
Truncated toward zero to a whole number; the full form keeps the low 16 bits, so a value outside 16 bits is wrapped.
dwg/bits-write.js
lines 55 to 60
CadCraft 0.8.2
BS(v) {
v = Math.trunc(v) || 0;
if (v === 0) this.BB(2); else if (v === 256) this.BB(3);
else if (v > 0 && v < 256) { this.BB(1); this.RC(v); }
else { this.BB(0); this.RS(v & 0xFFFF); }
}
Bit long long, written
Exact
A 64-bit whole number as a three-bit byte count and its bytes.
The value goes through BigInt, limited to 64 unsigned bits, and is split into bytes with BigInt shifts, so every bit of a BigInt input is kept. An ordinary number passed in is exact only up to 2^53, and one with a fraction is refused by BigInt with an error.
dwg/bits-write.js
lines 73 to 79
CadCraft 0.8.2
BLL(v) {
let x = BigInt.asUintN(64, BigInt(v || 0));
const bytes = [];
while (x > 0n) { bytes.push(Number(x & 0xFFn)); x >>= 8n; }
this.bits(bytes.length, 3);
for (const b of bytes) this.RC(b);
}
Point, written
64-bit
The three coordinates of a point as bit doubles.
A missing coordinate, and one that is not a number, is written as 0; every other value is written unchanged.
Division and remainder on an ordinary number: exact for handles up to 2^53. A handle given as text is read as hexadecimal; a negative one is written as 0.
dwg/bits-write.js
lines 179 to 184
CadCraft 0.8.2
export function handleBytes(value) {
const bytes = [];
let v = typeof value === 'string' ? parseInt(value, 16) || 0 : Math.max(0, Math.trunc(value || 0));
while (v > 0) { bytes.unshift(v % 256); v = Math.floor(v / 256); }
return bytes;
}
In detail
Method
Bit codes of the Open Design Specification for .dwg files v5.4.1, chapter 2, as the file headers cite: RD, BD, DD, BT, BE, BS, BL, BLL, RLL, handles, modular chars and shorts.
Number types
64-bit values as raw bytes; whole numbers as 64-bit numbers on reading, BigInt for the 64-bit forms on writing.
Limits
A 64-bit whole number or handle above 2^53 read from a file is rounded, because it is assembled as an ordinary number. The byte copy assumes a little-endian machine. A whole-number field wider than its code is wrapped, not refused.
Approach: 64-bit, direct
Every value is a 64-bit number from input to result. Formulas are closed form or a direct solve, and every comparison uses a stated tolerance.