feat: add encodeFloat decodeFloat to typescript
This commit is contained in:
parent
9ccd76c7d9
commit
e29cb11b81
77
app/src/lib/helpers/encode.test.ts
Normal file
77
app/src/lib/helpers/encode.test.ts
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import { test, expect } from "vitest"
|
||||||
|
import { encodeFloat, decodeFloat } from "./encode"
|
||||||
|
|
||||||
|
test("encode_float", () => {
|
||||||
|
const input = 1.23;
|
||||||
|
const encoded = encodeFloat(input)
|
||||||
|
const output = decodeFloat(encoded[0], encoded[1])
|
||||||
|
console.log(input, output)
|
||||||
|
expect(output).toBeCloseTo(input);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("encode 2.0", () => {
|
||||||
|
const input = 2.0;
|
||||||
|
const encoded = encodeFloat(input)
|
||||||
|
expect(encoded).toEqual([0, 128])
|
||||||
|
});
|
||||||
|
|
||||||
|
test("floating point imprecision", () => {
|
||||||
|
let maxError = 0;
|
||||||
|
new Array(10_000).fill(null).forEach((_, i) => {
|
||||||
|
const input = i < 5_000 ? i : Math.random() * 100;
|
||||||
|
const encoded = encodeFloat(input);
|
||||||
|
const output = decodeFloat(encoded[0], encoded[1]);
|
||||||
|
|
||||||
|
const error = Math.abs(input - output);
|
||||||
|
if (error > maxError) {
|
||||||
|
maxError = error;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(maxError).toBeLessThan(0.00001);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test with negative numbers
|
||||||
|
test("negative numbers", () => {
|
||||||
|
const inputs = [-1, -0.5, -123.456, -0.0001];
|
||||||
|
inputs.forEach(input => {
|
||||||
|
const encoded = encodeFloat(input);
|
||||||
|
const output = decodeFloat(encoded[0], encoded[1]);
|
||||||
|
expect(output).toBeCloseTo(input);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test with very small numbers
|
||||||
|
test("very small numbers", () => {
|
||||||
|
const input = 1.2345e-38;
|
||||||
|
const encoded = encodeFloat(input)
|
||||||
|
const output = decodeFloat(encoded[0], encoded[1])
|
||||||
|
expect(output).toBeCloseTo(input);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test with zero
|
||||||
|
test("zero", () => {
|
||||||
|
const input = 0;
|
||||||
|
const encoded = encodeFloat(input)
|
||||||
|
const output = decodeFloat(encoded[0], encoded[1])
|
||||||
|
expect(output).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test with infinity
|
||||||
|
test("infinity", () => {
|
||||||
|
const input = Infinity;
|
||||||
|
const encoded = encodeFloat(input)
|
||||||
|
const output = decodeFloat(encoded[0], encoded[1])
|
||||||
|
expect(output).toBe(Infinity);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test with large numbers
|
||||||
|
test("large numbers", () => {
|
||||||
|
const inputs = [1e+5, 1e+10];
|
||||||
|
inputs.forEach(input => {
|
||||||
|
const encoded = encodeFloat(input);
|
||||||
|
const output = decodeFloat(encoded[0], encoded[1]);
|
||||||
|
// Note: Large numbers may lose precision, hence using toBeCloseTo with a tolerance
|
||||||
|
expect(output).toBeCloseTo(input, 0);
|
||||||
|
});
|
||||||
|
});
|
34
app/src/lib/helpers/encode.ts
Normal file
34
app/src/lib/helpers/encode.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
|
||||||
|
export function encodeFloat(f: number): [number, number] {
|
||||||
|
let buffer = new ArrayBuffer(4); // Create a buffer of 4 bytes (32 bits)
|
||||||
|
let floatView = new Float32Array(buffer);
|
||||||
|
let intView = new Uint32Array(buffer);
|
||||||
|
|
||||||
|
floatView[0] = f; // Store the float into the buffer
|
||||||
|
let bits = intView[0]; // Read the bits as integer
|
||||||
|
|
||||||
|
let mantissa = bits & 0x007FFFFF;
|
||||||
|
let exponent = (bits >> 23) & 0xFF;
|
||||||
|
let sign = (f < 0.0) ? 1 : 0;
|
||||||
|
|
||||||
|
// Include the sign bit in the mantissa
|
||||||
|
mantissa = mantissa | (sign << 23);
|
||||||
|
|
||||||
|
return [mantissa, exponent];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decodeFloat(mantissa: number, exponent: number): number {
|
||||||
|
let signBit = (mantissa >> 23) & 1;
|
||||||
|
let mantissaBits = mantissa & 0x007FFFFF;
|
||||||
|
let exponentBits = (exponent & 0xFF) << 23;
|
||||||
|
|
||||||
|
// Reconstruct all bits including sign
|
||||||
|
let bits = (signBit << 31) | exponentBits | mantissaBits;
|
||||||
|
|
||||||
|
let buffer = new ArrayBuffer(4);
|
||||||
|
let floatView = new Float32Array(buffer);
|
||||||
|
let intView = new Uint32Array(buffer);
|
||||||
|
|
||||||
|
intView[0] = bits; // Set the bits as integer
|
||||||
|
return floatView[0]; // Read the float back from the buffer
|
||||||
|
}
|
@ -3,6 +3,8 @@ import type { Graph, NodeRegistry, NodeType, RuntimeExecutor } from "@nodes/type
|
|||||||
|
|
||||||
export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
||||||
|
|
||||||
|
private typeMap: Map<string, NodeType> = new Map();
|
||||||
|
|
||||||
constructor(private registry: NodeRegistry) { }
|
constructor(private registry: NodeRegistry) { }
|
||||||
|
|
||||||
private getNodeTypes(graph: Graph) {
|
private getNodeTypes(graph: Graph) {
|
||||||
@ -26,7 +28,7 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
|||||||
private addMetaData(graph: Graph) {
|
private addMetaData(graph: Graph) {
|
||||||
|
|
||||||
// First, lets check if all nodes have a type
|
// First, lets check if all nodes have a type
|
||||||
const typeMap = this.getNodeTypes(graph);
|
this.typeMap = this.getNodeTypes(graph);
|
||||||
|
|
||||||
const outputNode = graph.nodes.find(node => node.type.endsWith("/output"));
|
const outputNode = graph.nodes.find(node => node.type.endsWith("/output"));
|
||||||
if (!outputNode) {
|
if (!outputNode) {
|
||||||
@ -63,8 +65,6 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
|||||||
if (node) {
|
if (node) {
|
||||||
node.tmp = node.tmp || {};
|
node.tmp = node.tmp || {};
|
||||||
|
|
||||||
node.tmp.type = typeMap.get(node.type);
|
|
||||||
|
|
||||||
if (node?.tmp?.depth === undefined) {
|
if (node?.tmp?.depth === undefined) {
|
||||||
node.tmp.depth = 0;
|
node.tmp.depth = 0;
|
||||||
}
|
}
|
||||||
@ -110,9 +110,12 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
|||||||
const results: Record<string, string | boolean | number> = {};
|
const results: Record<string, string | boolean | number> = {};
|
||||||
|
|
||||||
for (const node of sortedNodes) {
|
for (const node of sortedNodes) {
|
||||||
if (node?.tmp && node?.tmp?.type?.execute) {
|
|
||||||
|
const node_type = this.typeMap.get(node.type)!;
|
||||||
|
|
||||||
|
if (node?.tmp && node_type?.execute) {
|
||||||
const inputs: Record<string, string | number | boolean> = {};
|
const inputs: Record<string, string | number | boolean> = {};
|
||||||
for (const [key, input] of Object.entries(node.tmp.type.inputs || {})) {
|
for (const [key, input] of Object.entries(node_type.inputs || {})) {
|
||||||
|
|
||||||
if (input.type === "seed") {
|
if (input.type === "seed") {
|
||||||
inputs[key] = Math.floor(Math.random() * 100000000);
|
inputs[key] = Math.floor(Math.random() * 100000000);
|
||||||
@ -136,10 +139,18 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
|||||||
|
|
||||||
// execute the node and store the result
|
// execute the node and store the result
|
||||||
try {
|
try {
|
||||||
console.log(`Executing node ${node.tmp.type.id || node.id}`, inputs);
|
const node_inputs = Object.entries(inputs);
|
||||||
results[node.id] = node.tmp.type.execute(...Object.values(inputs)) as number;
|
const transformed_inputs = node_inputs.map(([key, value]) => {
|
||||||
|
const input_type = node_type.inputs[key];
|
||||||
|
if (input.type === "float") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log(key, input_type);
|
||||||
|
});
|
||||||
|
console.log(`Executing node ${node_type.id || node.id}`, node_inputs);
|
||||||
|
results[node.id] = node_type.execute(...Object.values(inputs)) as number;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`Error executing node ${node.tmp.type.id || node.id}`, e);
|
console.error(`Error executing node ${node_type.id || node.id}`, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,23 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { decodeFloat, encodeFloat } from "$lib/helpers/encode";
|
||||||
import { decode, encode } from "$lib/helpers/flat_tree";
|
import { decode, encode } from "$lib/helpers/flat_tree";
|
||||||
|
|
||||||
|
let maxError = 0;
|
||||||
|
new Array(10_000).fill(null).forEach((v, i) => {
|
||||||
|
const input = i < 5_000 ? i : Math.random() * 100;
|
||||||
|
const encoded = encodeFloat(input);
|
||||||
|
const output = decodeFloat(encoded[0], encoded[1]);
|
||||||
|
|
||||||
|
const error = Math.abs(input - output);
|
||||||
|
if (error > maxError) {
|
||||||
|
maxError = error;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("DECODE FLOAT");
|
||||||
|
console.log(maxError);
|
||||||
|
console.log(encodeFloat(2.0));
|
||||||
|
|
||||||
// const input = [5, [6, 1], [7, 2, [5, 1]]];
|
// const input = [5, [6, 1], [7, 2, [5, 1]]];
|
||||||
// const input = [5, [], [6, []], []];
|
// const input = [5, [], [6, []], []];
|
||||||
// const input = [52];
|
// const input = [52];
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
mod utils;
|
mod utils;
|
||||||
use plantarium::unwrap_float;
|
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
@ -17,8 +16,8 @@ pub fn get_input_types() -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn execute(var_value: JsValue) -> f64 {
|
pub fn execute(args: &[i32]) -> Vec<i32> {
|
||||||
utils::set_panic_hook();
|
utils::set_panic_hook();
|
||||||
|
|
||||||
return unwrap_float(var_value);
|
args.into()
|
||||||
}
|
}
|
||||||
|
@ -2,5 +2,6 @@ mod encoding;
|
|||||||
mod helpers;
|
mod helpers;
|
||||||
mod nodes;
|
mod nodes;
|
||||||
mod tree;
|
mod tree;
|
||||||
|
pub use encoding::*;
|
||||||
pub use helpers::*;
|
pub use helpers::*;
|
||||||
pub use tree::*;
|
pub use tree::*;
|
||||||
|
Loading…
Reference in New Issue
Block a user