138 lines
4.3 KiB
TypeScript
138 lines
4.3 KiB
TypeScript
import { Tag } from "./constants";
|
|
import { Value } from "./values";
|
|
import type { GenericEmbedded } from "./embedded";
|
|
import type { Encoder, Preservable } from "./encoder";
|
|
import type { Writer, PreserveWritable } from "./writer";
|
|
import { Bytes, dataview } from "./bytes";
|
|
|
|
// v Previously included 'Single'; may again in future. Also, 'Half', 'Quad'?
|
|
export type FloatType = 'Double';
|
|
export const FloatType = Symbol.for('FloatType');
|
|
|
|
export abstract class Float {
|
|
readonly value: number;
|
|
|
|
constructor(value: number | Float) {
|
|
this.value = typeof value === 'number' ? value : value.value;
|
|
}
|
|
|
|
__preserve_text_on__(w: Writer<any>) {
|
|
w.state.pieces.push(this.toString());
|
|
}
|
|
|
|
abstract toBytes(): Bytes;
|
|
|
|
equals(other: any): boolean {
|
|
if (!Object.is(other.constructor, this.constructor)) return false;
|
|
if (Number.isNaN(this.value) && Number.isNaN(other.value)) {
|
|
return other.toBytes().equals(this.toBytes());
|
|
} else {
|
|
return Object.is(other.value, this.value);
|
|
}
|
|
}
|
|
|
|
hashCode(): number {
|
|
return (this.value | 0); // TODO: something better?
|
|
}
|
|
|
|
abstract get [FloatType](): FloatType;
|
|
|
|
static isFloat = (x: any): x is Float => x?.[FloatType] !== void 0;
|
|
static isDouble = (x: any): x is DoubleFloat => x?.[FloatType] === 'Double';
|
|
}
|
|
|
|
export function floatValue(f: any): number {
|
|
if (typeof f === 'number') {
|
|
return f;
|
|
} else if (Float.isFloat(f)) {
|
|
return f.value;
|
|
} else {
|
|
return NaN;
|
|
}
|
|
}
|
|
|
|
export function floatlikeString(f: number): string {
|
|
if (Object.is(f, -0)) return '-0.0';
|
|
const s = '' + f;
|
|
if (s.includes('.') || s.includes('e') || s.includes('E')) return s;
|
|
return s + '.0';
|
|
}
|
|
|
|
// -- These snippets are useful to keep in mind for promoting 4-byte, single-precision floats
|
|
// -- to 8-byte, double-precision floats *while preserving NaN bit-patterns*:
|
|
//
|
|
// static fromBytes(bs: Bytes | DataView): SingleFloat {
|
|
// const view = dataview(bs);
|
|
// const vf = view.getInt32(0, false);
|
|
// if ((vf & 0x7f800000) === 0x7f800000) {
|
|
// // NaN or inf. Preserve quiet/signalling bit by manually expanding to double-precision.
|
|
// const sign = vf >> 31;
|
|
// const payload = vf & 0x007fffff;
|
|
// const dbs = new Bytes(8);
|
|
// const dview = dataview(dbs);
|
|
// dview.setInt16(0, (sign << 15) | 0x7ff0 | (payload >> 19), false);
|
|
// dview.setInt32(2, (payload & 0x7ffff) << 13, false);
|
|
// return new SingleFloat(dview.getFloat64(0, false));
|
|
// } else {
|
|
// return new SingleFloat(dataview(bs).getFloat32(0, false));
|
|
// }
|
|
// }
|
|
//
|
|
// __w(v: DataView, offset: number) {
|
|
// if (Number.isNaN(this.value)) {
|
|
// const dbs = new Bytes(8);
|
|
// const dview = dataview(dbs);
|
|
// dview.setFloat64(0, this.value, false);
|
|
// const sign = dview.getInt8(0) >> 7;
|
|
// const payload = (dview.getInt32(1, false) >> 5) & 0x007fffff;
|
|
// const vf = (sign << 31) | 0x7f800000 | payload;
|
|
// v.setInt32(offset, vf, false);
|
|
// } else {
|
|
// v.setFloat32(offset, this.value, false);
|
|
// }
|
|
// }
|
|
|
|
export class DoubleFloat extends Float implements Preservable<any>, PreserveWritable<any> {
|
|
__as_preserve__<T = GenericEmbedded>(): Value<T> {
|
|
return this;
|
|
}
|
|
|
|
static fromBytes(bs: Bytes | DataView): DoubleFloat {
|
|
return new DoubleFloat(dataview(bs).getFloat64(0, false));
|
|
}
|
|
|
|
static __from_preserve__<T>(v: Value<T>): undefined | DoubleFloat {
|
|
return Float.isDouble(v) ? v : void 0;
|
|
}
|
|
|
|
__preserve_on__(encoder: Encoder<any>) {
|
|
encoder.state.emitbyte(Tag.Ieee754);
|
|
encoder.state.emitbyte(8);
|
|
encoder.state.makeroom(8);
|
|
encoder.state.view.setFloat64(encoder.state.index, this.value, false);
|
|
encoder.state.index += 8;
|
|
}
|
|
|
|
toBytes(): Bytes {
|
|
const bs = new Bytes(8);
|
|
bs.dataview().setFloat64(0, this.value, false);
|
|
return bs;
|
|
}
|
|
|
|
toString(): string {
|
|
if (Number.isFinite(this.value)) {
|
|
return floatlikeString(this.value);
|
|
} else {
|
|
return '#xd"' + this.toBytes().toHex() + '"';
|
|
}
|
|
}
|
|
|
|
get [FloatType](): 'Double' {
|
|
return 'Double';
|
|
}
|
|
}
|
|
|
|
export function Double(value: number | Float): DoubleFloat {
|
|
return new DoubleFloat(value);
|
|
}
|