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) { 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, PreserveWritable { __as_preserve__(): Value { return this; } static fromBytes(bs: Bytes | DataView): DoubleFloat { return new DoubleFloat(dataview(bs).getFloat64(0, false)); } static __from_preserve__(v: Value): undefined | DoubleFloat { return Float.isDouble(v) ? v : void 0; } __preserve_on__(encoder: Encoder) { 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); }