import { Tag } from "./constants"; import { stringify } from "./text"; import { Value } from "./values"; import type { GenericEmbedded } from "./embedded"; import type { Encoder, Preservable } from "./encoder"; import type { Writer, PreserveWritable } from "./writer"; import { Bytes, dataview, underlying } from "./bytes"; export type FloatType = 'Single' | '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; } toString() { return stringify(this); } 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 isSingle = (x: any): x is SingleFloat => x?.[FloatType] === 'Single'; 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'; } export class SingleFloat extends Float implements Preservable, PreserveWritable { __as_preserve__(): Value { return this; } 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)); } } static __from_preserve__(v: Value): undefined | SingleFloat { return Float.isSingle(v) ? v : void 0; } __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); } } __preserve_on__(encoder: Encoder) { encoder.state.emitbyte(Tag.Float); encoder.state.makeroom(4); this.__w(encoder.state.view, encoder.state.index); encoder.state.index += 4; } toBytes(): Bytes { const bs = new Bytes(4); this.__w(bs.dataview(), 0); return bs; } __preserve_text_on__(w: Writer) { if (Number.isFinite(this.value)) { w.state.pieces.push(floatlikeString(this.value) + 'f'); } else { w.state.pieces.push('#xf"', this.toBytes().toHex(), '"'); } } get [FloatType](): 'Single' { return 'Single'; } } export function Single(value: number | Float): SingleFloat { return new SingleFloat(value); } 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.Double); 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; } __preserve_text_on__(w: Writer) { if (Number.isFinite(this.value)) { w.state.pieces.push(floatlikeString(this.value)); } else { w.state.pieces.push('#xd"', this.toBytes().toHex(), '"'); } } get [FloatType](): 'Double' { return 'Double'; } } export function Double(value: number | Float): DoubleFloat { return new DoubleFloat(value); }