@@ -99,10 +99,11 @@ public boolean accepts(DType dtype) {
9999 @ Override
100100 public EncodeResult encode (DType dtype , Object data ) {
101101 PType ptype = ((DType .Primitive ) dtype ).ptype ();
102- if (ptype == PType .F64 ) {
103- return encodeF64 ((double []) data , dtype );
104- }
105- throw new UnsupportedOperationException ("ALP encode not yet supported for " + ptype );
102+ return switch (ptype ) {
103+ case F64 -> encodeF64 ((double []) data , dtype );
104+ case F32 -> encodeF32 ((float []) data , dtype );
105+ default -> throw new UnsupportedOperationException ("ALP encode not supported for " + ptype );
106+ };
106107 }
107108
108109 // ── F64 encode ────────────────────────────────────────────────────────────
@@ -214,6 +215,115 @@ private static byte[] scalarF64(double v) {
214215 return ScalarProtos .ScalarValue .newBuilder ().setF64Value (v ).build ().toByteArray ();
215216 }
216217
218+ // ── F32 encode ────────────────────────────────────────────────────────────
219+
220+ private static int [] findExponentsF32 (float [] values ) {
221+ int sampleLen = Math .min (SAMPLE_SIZE , values .length );
222+ int bestExpE = 0 , bestExpF = 0 , bestExceptions = sampleLen + 1 ;
223+
224+ outer :
225+ for (int expE = 0 ; expE < F10_F32 .length ; expE ++) {
226+ for (int expF = 0 ; expF < F10_F32 .length ; expF ++) {
227+ float encFactor = F10_F32 [expE ] * IF10_F32 [expF ];
228+ float decFactor = F10_F32 [expF ] * IF10_F32 [expE ];
229+ int exceptions = 0 ;
230+ for (int i = 0 ; i < sampleLen ; i ++) {
231+ float enc = values [i ] * encFactor ;
232+ if (!Float .isFinite (enc ) || (float ) Math .round (enc ) * decFactor != values [i ]) {
233+ exceptions ++;
234+ }
235+ }
236+ if (exceptions < bestExceptions ) {
237+ bestExceptions = exceptions ;
238+ bestExpE = expE ;
239+ bestExpF = expF ;
240+ if (bestExceptions == 0 ) {
241+ break outer ;
242+ }
243+ }
244+ }
245+ }
246+ return new int []{bestExpE , bestExpF };
247+ }
248+
249+ private static EncodeResult encodeF32 (float [] values , DType dtype ) {
250+ int n = values .length ;
251+ int [] exps = findExponentsF32 (values );
252+ int expE = exps [0 ], expF = exps [1 ];
253+ float encFactor = F10_F32 [expE ] * IF10_F32 [expF ];
254+ float decFactor = F10_F32 [expF ] * IF10_F32 [expE ];
255+
256+ int [] encodedArr = new int [n ];
257+ var patchIndices = new ArrayList <Integer >();
258+ var patchValues = new ArrayList <Float >();
259+
260+ float min = Float .MAX_VALUE , max = -Float .MAX_VALUE ;
261+ for (int i = 0 ; i < n ; i ++) {
262+ float v = values [i ];
263+ float enc = v * encFactor ;
264+ int encoded ;
265+ if (Float .isFinite (enc ) && (float ) (encoded = Math .round (enc )) * decFactor == v ) {
266+ encodedArr [i ] = encoded ;
267+ } else {
268+ encodedArr [i ] = 0 ;
269+ patchIndices .add (i );
270+ patchValues .add (v );
271+ }
272+ if (v < min ) {
273+ min = v ;
274+ }
275+ if (v > max ) {
276+ max = v ;
277+ }
278+ }
279+
280+ byte [] statsMin = n > 0 ? scalarF32 (min ) : null ;
281+ byte [] statsMax = n > 0 ? scalarF32 (max ) : null ;
282+
283+ MemorySegment encodedBuf = Arena .ofAuto ().allocate ((long ) n * 4 , 4 );
284+ for (int i = 0 ; i < n ; i ++) {
285+ encodedBuf .setAtIndex (PTypeIO .LE_INT , i , encodedArr [i ]);
286+ }
287+
288+ EncodeNode encodedNode = EncodeNode .leaf (EncodingId .VORTEX_PRIMITIVE , 0 );
289+
290+ if (patchIndices .isEmpty ()) {
291+ byte [] metaBytes = EncodingProtos .ALPMetadata .newBuilder ()
292+ .setExpE (expE ).setExpF (expF ).build ().toByteArray ();
293+ EncodeNode root = new EncodeNode (EncodingId .VORTEX_ALP ,
294+ ByteBuffer .wrap (metaBytes ), new EncodeNode []{encodedNode }, new int [0 ]);
295+ return new EncodeResult (root , List .of (encodedBuf ), statsMin , statsMax );
296+ }
297+
298+ int numPatches = patchIndices .size ();
299+ MemorySegment idxBuf = Arena .ofAuto ().allocate ((long ) numPatches * 4 , 4 );
300+ MemorySegment valBuf = Arena .ofAuto ().allocate ((long ) numPatches * 4 , 4 );
301+ for (int i = 0 ; i < numPatches ; i ++) {
302+ idxBuf .setAtIndex (PTypeIO .LE_INT , i , patchIndices .get (i ));
303+ valBuf .setAtIndex (PTypeIO .LE_FLOAT , i , patchValues .get (i ));
304+ }
305+
306+ EncodingProtos .PatchesMetadata patches = EncodingProtos .PatchesMetadata .newBuilder ()
307+ .setLen (numPatches )
308+ .setOffset (0 )
309+ .setIndicesPtype (DTypeProtos .PType .forNumber (PType .U32 .ordinal ()))
310+ .build ();
311+ byte [] metaBytes = EncodingProtos .ALPMetadata .newBuilder ()
312+ .setExpE (expE ).setExpF (expF ).setPatches (patches ).build ().toByteArray ();
313+
314+ EncodeNode idxNode = EncodeNode .leaf (EncodingId .VORTEX_PRIMITIVE , 1 );
315+ EncodeNode valNode = EncodeNode .leaf (EncodingId .VORTEX_PRIMITIVE , 2 );
316+ EncodeNode root = new EncodeNode (EncodingId .VORTEX_ALP ,
317+ ByteBuffer .wrap (metaBytes ),
318+ new EncodeNode []{encodedNode , idxNode , valNode },
319+ new int [0 ]);
320+ return new EncodeResult (root , List .of (encodedBuf , idxBuf , valBuf ), statsMin , statsMax );
321+ }
322+
323+ private static byte [] scalarF32 (float v ) {
324+ return ScalarProtos .ScalarValue .newBuilder ().setF32Value (v ).build ().toByteArray ();
325+ }
326+
217327 @ Override
218328 public Array decode (DecodeContext ctx ) {
219329 ByteBuffer rawMeta = ctx .metadata ();
0 commit comments