Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: Does it work with floats? #37

Open
theodesp opened this issue Nov 21, 2023 · 1 comment
Open

Question: Does it work with floats? #37

theodesp opened this issue Nov 21, 2023 · 1 comment

Comments

@theodesp
Copy link

Thank you for the library. I was wondering to know if this library works with floats or is a way to serialize single or double precision numbers and restore them for later ( with some loss of precision).

@fudgepop01
Copy link

fudgepop01 commented Jan 16, 2024

unless I'm mistaken, this project does not appear to be maintained much these days. However, it's fortunately fairly easy to make whatever custom construct class you wish thanks to how construct-js works - and I happened to have had to tackle that issue in the past myself using typescript and I can share it here 😄

the particularly unfortunate thing is, every number in javascript is internally a 64 bit number, and as such, only values of (2 ^ 53 - 1) will accurately serialize to double precision floating point numbers. It is also LUDICRIOUSLY difficult to serialize to half-precision IEE754 floats in javascript alone. The only webapp I've seen that can do this uses a gargantuan library that I don't fully understand. 🧙

(see: https://evanw.github.io/float-toy/)

As for implementation, this is what I did for 32-bit floats using a package called ieee754:

(https://www.npmjs.com/package/ieee754)

import { write as floatWrite } from 'ieee754';

/////////////////////////////////////////////////////
// basically a trimmed down version of the field class in construct-js' source code
/////////////////////////////////////////////////////

import { Endian } from "construct-js";

const assert = (condition, message) => {
  if (!condition) {
      throw new Error(message);
  }
};

export default class FloatField {
  public width;
  public min;
  public max;
  public toBytesFn;
  public value;
  public endian;

  constructor(width, min: number, max: number, toBytesFn: (vals: number[], isLE: boolean) => Uint8Array, value: number, endian: Endian) {
      this.width = width;
      this.min = min;
      this.max = max;
      this.toBytesFn = toBytesFn;
      this.assertInvariants(value);
      this.value = value;
      this.endian = endian;
  }
  assertInvariants(value) {
      assert(value >= this.min && value <= this.max, `value must be an integer between ${this.min} and ${this.max}`);
  }
  computeBufferSize() { return this.width; }
  toUint8Array() {
      return this.toBytesFn([this.value], this.endian === Endian.Little);
  }
  set(value) {
      this.assertInvariants(value);
      this.value = value;
  }
  get() { return this.value; }
}

/////////////////////////////////////////////////////
// the implementation of the F32Type
/////////////////////////////////////////////////////

const IEEE754_FLOAT32_MAX = (2 - (2 ** -23)) * (2 ** 127);
const IEEE754_FLOAT32_MIN = IEEE754_FLOAT32_MAX * -1;

const f32Tou8s = (vals: number[], isLittleEndian: boolean) => {
  const stride = 4;
  const buff = new Uint8Array(vals.length * stride);
  for (let [i, val] of vals.entries()) {
    floatWrite(buff, val, i * stride, isLittleEndian, 23, 4);
  }
  return buff;
}

export class F32Type extends BaseField {
  constructor(value: number, endian: Endian = Endian.Little) {
    super(4, IEEE754_FLOAT32_MIN, IEEE754_FLOAT32_MAX, f32Tou8s, value, endian);
  }
}

export const F32 = (value: number, endian: Endian = Endian.Little) => new F32Type(value, endian);

Hope this helps!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants