Skip to content

Commit 66e5405

Browse files
committed
BitGroom/BitRound: block ±Inf and add special-value round-trip test
The loop bodies have well-defined behavior only for finite inputs: BitRound's per-value bit-shift becomes an out-of-range shift on ±Inf (undefined behavior), and BitGroom's loop turns ±Inf bit patterns into NaN by ORing low mantissa bits. Both guards now use isfinite(val) instead of !isnan(val) to block ±Inf in addition to NaN. New h5filter_specials_{bitround,bitgroom} round-trip tests write NaN, ±Inf, ±0.0, and normal values via H5Pset_filter, read back, and assert in C that the special values pass through unchanged while at least one normal value was quantized.
1 parent 58308e7 commit 66e5405

6 files changed

Lines changed: 568 additions & 19 deletions

File tree

BITGROOM/example/CMakeLists.txt

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,15 @@ if (H5PL_BUILD_TESTING)
4343
target_link_libraries (h5repack_floats_bitgroom PRIVATE dl)
4444
endif ()
4545

46+
# Round-trip test for special floating-point values (NaN, +/- Inf, +/- zero).
47+
# See the test definition lower in this file for the full description.
48+
add_executable (h5filter_specials_bitgroom ${PROJECT_SOURCE_DIR}/h5filter_specials_bitgroom.c)
49+
TARGET_C_PROPERTIES (h5filter_specials_bitgroom ${LIB_TYPE})
50+
target_link_libraries (h5filter_specials_bitgroom PRIVATE ${H5PL_HDF5_LINK_LIBS})
51+
if (NOT WIN32)
52+
target_link_libraries (h5filter_specials_bitgroom PRIVATE dl)
53+
endif ()
54+
4655
macro (ADD_H5_TEST testname)
4756
add_test (
4857
NAME ${testname}-clearall
@@ -261,6 +270,44 @@ if (H5PL_BUILD_TESTING)
261270
if (NOT DISABLE_H5BITGROOM_ENCODER)
262271
#UD BITGROOM
263272
ADD_H5_UD_TEST (ud_convert 0 h5repack_floats.h5 --enable-error-stack -v -f UD=32022,0,5,3,4,0,0,0 -l CHUNK=4x8)
273+
274+
# Special-value round-trip test: writes a small chunked dataset containing
275+
# NaN, +/- Inf, +/- zero, plus normal values; applies the BitGroom filter
276+
# via H5Pset_filter; reads back; and asserts in C that the special values
277+
# are preserved bit-exact while at least one normal value was quantized.
278+
# BitGroom alternates shave (AND) on even indices and set (OR) on odd
279+
# indices; the special values straddle both. The verdict comes from the
280+
# program's exit code; no h5dump comparison is involved, so cross-platform
281+
# NaN/Inf printf formatting cannot affect the result. TESTLIBDIR is
282+
# already set by ADD_H5_UD_TEST above.
283+
add_test (
284+
NAME H5BITGROOM_UD-specials-roundtrip-clearall
285+
COMMAND ${CMAKE_COMMAND} -E remove
286+
h5filter_specials_bitgroom.h5
287+
h5filter_specials_bitgroom.out
288+
h5filter_specials_bitgroom.out.err
289+
)
290+
if (NOT "${last_test}" STREQUAL "")
291+
set_tests_properties (H5BITGROOM_UD-specials-roundtrip-clearall PROPERTIES DEPENDS ${last_test})
292+
endif ()
293+
set (last_test "H5BITGROOM_UD-specials-roundtrip-clearall")
294+
add_test (
295+
NAME H5BITGROOM_UD-specials-roundtrip
296+
COMMAND "${CMAKE_COMMAND}"
297+
-D "TEST_PROGRAM=$<TARGET_FILE:h5filter_specials_bitgroom>"
298+
-D "TEST_FOLDER=${PROJECT_BINARY_DIR}"
299+
-D "TEST_EXPECT=0"
300+
-D "TEST_OUTPUT=h5filter_specials_bitgroom.out"
301+
-D "TEST_SKIP_COMPARE=1"
302+
-D "TEST_LIBRARY_DIRECTORY=${TESTLIBDIR}"
303+
-D "TEST_ENV_VAR=HDF5_PLUGIN_PATH"
304+
-D "TEST_ENV_VALUE=${CMAKE_BINARY_DIR}/plugins"
305+
-P "${H5BITGROOM_RESOURCES_DIR}/runTest.cmake"
306+
)
307+
set_tests_properties (H5BITGROOM_UD-specials-roundtrip PROPERTIES
308+
WORKING_DIRECTORY "${PROJECT_BINARY_DIR}"
309+
DEPENDS H5BITGROOM_UD-specials-roundtrip-clearall)
310+
set (last_test "H5BITGROOM_UD-specials-roundtrip")
264311
endif ()
265312

266313
endif ()
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2+
* Copyright by The HDF Group. *
3+
* All rights reserved. *
4+
* *
5+
* This file is part of the HDF5 BitGroom filter plugin source. *
6+
* The full copyright notice, including terms governing use, modification, *
7+
* and redistribution, is contained in the file COPYING, which can be found *
8+
* at the root of the source code distribution tree. If you do not have *
9+
* access to this file, you may request a copy from help@hdfgroup.org. *
10+
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
11+
12+
/*
13+
* Round-trip test for special floating-point values through the BitGroom
14+
* filter. Verifies that NaN, +Inf, -Inf, -0.0, and +0.0 are passed through
15+
* the filter unchanged (bit-identical) on both the float and double code
16+
* paths, and that at least one normal value is quantized (proving the
17+
* filter actually ran). The filter is loaded dynamically via HDF5's plugin
18+
* mechanism (HDF5_PLUGIN_PATH); no h5dump comparison is involved so cross-
19+
* platform NaN/Inf printf formatting cannot affect the result.
20+
*
21+
* BitGroom alternates shave (AND-mask) on even indices and set (OR-mask)
22+
* on odd indices; the special values are placed at both even and odd
23+
* positions to exercise both loops.
24+
*/
25+
26+
#include "hdf5.h"
27+
#include <stdint.h>
28+
#include <stdio.h>
29+
#include <string.h>
30+
31+
#define FILENAME "h5filter_specials_bitgroom.h5"
32+
#define DSET_F32 "f32"
33+
#define DSET_F64 "f64"
34+
#define N 16
35+
#define N_SPECIALS 5
36+
#define H5Z_FILTER_BITGROOM 32022
37+
38+
/* Build exact bit patterns via memcpy so the compiler cannot constant-fold
39+
* away -0.0f or normalize a quiet NaN to a different payload. */
40+
static void
41+
init_input_f32(float *buf, uint32_t *bits_out)
42+
{
43+
const uint32_t specials[N_SPECIALS] = {
44+
0x7FC00000U, /* quiet NaN */
45+
0x7F800000U, /* +Inf */
46+
0xFF800000U, /* -Inf */
47+
0x80000000U, /* -0.0 */
48+
0x00000000U, /* +0.0 */
49+
};
50+
for (int i = 0; i < N_SPECIALS; i++)
51+
memcpy(&buf[i], &specials[i], sizeof(float));
52+
for (int i = N_SPECIALS; i < N; i++)
53+
buf[i] = ((float)i - 3.7f) * 0.137f;
54+
memcpy(bits_out, buf, N * sizeof(uint32_t));
55+
}
56+
57+
static void
58+
init_input_f64(double *buf, uint64_t *bits_out)
59+
{
60+
const uint64_t specials[N_SPECIALS] = {
61+
0x7FF8000000000000ULL, /* quiet NaN */
62+
0x7FF0000000000000ULL, /* +Inf */
63+
0xFFF0000000000000ULL, /* -Inf */
64+
0x8000000000000000ULL, /* -0.0 */
65+
0x0000000000000000ULL, /* +0.0 */
66+
};
67+
for (int i = 0; i < N_SPECIALS; i++)
68+
memcpy(&buf[i], &specials[i], sizeof(double));
69+
for (int i = N_SPECIALS; i < N; i++)
70+
buf[i] = ((double)i - 3.7) * 0.137;
71+
memcpy(bits_out, buf, N * sizeof(uint64_t));
72+
}
73+
74+
static int
75+
roundtrip_f32(hid_t file_id)
76+
{
77+
hid_t space_id = H5I_INVALID_HID, dcpl_id = H5I_INVALID_HID, dset_id = H5I_INVALID_HID;
78+
int ret = 1;
79+
hsize_t dims[1] = {N}, chunk[1] = {N};
80+
/* NSD=3, sizeof(data)=4, has_mss_val=0, mss_val_byt_1to4=0, mss_val_byt_5to8=0 */
81+
const unsigned int cd_values[5] = {3, 4, 0, 0, 0};
82+
float input[N], output[N];
83+
uint32_t input_bits[N], output_bits[N];
84+
int any_quantized = 0;
85+
86+
init_input_f32(input, input_bits);
87+
88+
if ((space_id = H5Screate_simple(1, dims, NULL)) < 0)
89+
goto done;
90+
if ((dcpl_id = H5Pcreate(H5P_DATASET_CREATE)) < 0)
91+
goto done;
92+
if (H5Pset_chunk(dcpl_id, 1, chunk) < 0)
93+
goto done;
94+
if (H5Pset_filter(dcpl_id, H5Z_FILTER_BITGROOM, H5Z_FLAG_MANDATORY, 5, cd_values) < 0)
95+
goto done;
96+
if ((dset_id = H5Dcreate(file_id, DSET_F32, H5T_IEEE_F32LE, space_id, H5P_DEFAULT, dcpl_id,
97+
H5P_DEFAULT)) < 0)
98+
goto done;
99+
if (H5Dwrite(dset_id, H5T_NATIVE_FLOAT, H5S_ALL, H5S_ALL, H5P_DEFAULT, input) < 0)
100+
goto done;
101+
H5Dclose(dset_id);
102+
dset_id = H5I_INVALID_HID;
103+
104+
if ((dset_id = H5Dopen(file_id, DSET_F32, H5P_DEFAULT)) < 0)
105+
goto done;
106+
if (H5Dread(dset_id, H5T_NATIVE_FLOAT, H5S_ALL, H5S_ALL, H5P_DEFAULT, output) < 0)
107+
goto done;
108+
memcpy(output_bits, output, sizeof(output_bits));
109+
110+
for (int i = 0; i < N_SPECIALS; i++) {
111+
if (input_bits[i] != output_bits[i]) {
112+
fprintf(stderr, "FAIL [f32, idx %d]: special value mutated: in=0x%08x out=0x%08x\n", i,
113+
input_bits[i], output_bits[i]);
114+
goto done;
115+
}
116+
}
117+
for (int i = N_SPECIALS; i < N; i++) {
118+
if (input_bits[i] != output_bits[i]) {
119+
any_quantized = 1;
120+
break;
121+
}
122+
}
123+
if (!any_quantized) {
124+
fprintf(stderr, "FAIL [f32]: no normal value was quantized; filter did not run\n");
125+
goto done;
126+
}
127+
128+
ret = 0;
129+
130+
done:
131+
if (dset_id >= 0)
132+
H5Dclose(dset_id);
133+
if (dcpl_id >= 0)
134+
H5Pclose(dcpl_id);
135+
if (space_id >= 0)
136+
H5Sclose(space_id);
137+
return ret;
138+
}
139+
140+
static int
141+
roundtrip_f64(hid_t file_id)
142+
{
143+
hid_t space_id = H5I_INVALID_HID, dcpl_id = H5I_INVALID_HID, dset_id = H5I_INVALID_HID;
144+
int ret = 1;
145+
hsize_t dims[1] = {N}, chunk[1] = {N};
146+
/* NSD=3, sizeof(data)=8, has_mss_val=0, mss_val_byt_1to4=0, mss_val_byt_5to8=0 */
147+
const unsigned int cd_values[5] = {3, 8, 0, 0, 0};
148+
double input[N], output[N];
149+
uint64_t input_bits[N], output_bits[N];
150+
int any_quantized = 0;
151+
152+
init_input_f64(input, input_bits);
153+
154+
if ((space_id = H5Screate_simple(1, dims, NULL)) < 0)
155+
goto done;
156+
if ((dcpl_id = H5Pcreate(H5P_DATASET_CREATE)) < 0)
157+
goto done;
158+
if (H5Pset_chunk(dcpl_id, 1, chunk) < 0)
159+
goto done;
160+
if (H5Pset_filter(dcpl_id, H5Z_FILTER_BITGROOM, H5Z_FLAG_MANDATORY, 5, cd_values) < 0)
161+
goto done;
162+
if ((dset_id = H5Dcreate(file_id, DSET_F64, H5T_IEEE_F64LE, space_id, H5P_DEFAULT, dcpl_id,
163+
H5P_DEFAULT)) < 0)
164+
goto done;
165+
if (H5Dwrite(dset_id, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, input) < 0)
166+
goto done;
167+
H5Dclose(dset_id);
168+
dset_id = H5I_INVALID_HID;
169+
170+
if ((dset_id = H5Dopen(file_id, DSET_F64, H5P_DEFAULT)) < 0)
171+
goto done;
172+
if (H5Dread(dset_id, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, output) < 0)
173+
goto done;
174+
memcpy(output_bits, output, sizeof(output_bits));
175+
176+
for (int i = 0; i < N_SPECIALS; i++) {
177+
if (input_bits[i] != output_bits[i]) {
178+
fprintf(stderr, "FAIL [f64, idx %d]: special value mutated: in=0x%016llx out=0x%016llx\n", i,
179+
(unsigned long long)input_bits[i], (unsigned long long)output_bits[i]);
180+
goto done;
181+
}
182+
}
183+
for (int i = N_SPECIALS; i < N; i++) {
184+
if (input_bits[i] != output_bits[i]) {
185+
any_quantized = 1;
186+
break;
187+
}
188+
}
189+
if (!any_quantized) {
190+
fprintf(stderr, "FAIL [f64]: no normal value was quantized; filter did not run\n");
191+
goto done;
192+
}
193+
194+
ret = 0;
195+
196+
done:
197+
if (dset_id >= 0)
198+
H5Dclose(dset_id);
199+
if (dcpl_id >= 0)
200+
H5Pclose(dcpl_id);
201+
if (space_id >= 0)
202+
H5Sclose(space_id);
203+
return ret;
204+
}
205+
206+
int
207+
main(void)
208+
{
209+
hid_t file_id;
210+
int r_f32 = 1, r_f64 = 1;
211+
212+
file_id = H5Fcreate(FILENAME, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT);
213+
if (file_id < 0) {
214+
fprintf(stderr, "FAIL: H5Fcreate(%s) failed\n", FILENAME);
215+
return 1;
216+
}
217+
218+
r_f32 = roundtrip_f32(file_id);
219+
r_f64 = roundtrip_f64(file_id);
220+
221+
H5Fclose(file_id);
222+
223+
if (r_f32 == 0 && r_f64 == 0) {
224+
printf("PASS: BitGroom special-value round-trip (f32 + f64)\n");
225+
return 0;
226+
}
227+
return 1;
228+
}

BITGROOM/src/H5Zbitgroom.c

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -548,12 +548,16 @@ ccr_bgr(const int nsd, const int type, const size_t sz, const int has_mss_val, p
548548
// msk_f32_u32_hshv=msk_f32_u32_one & (msk_f32_u32_zro >> 1); /* Set one bit: the MSB of LSBs */
549549

550550
/* Bit-Groom: alternately shave and set LSBs.
551-
* Do not quantize the dataset fill value, +/- zero, or NaN. */
551+
* Do not quantize the dataset fill value, +/- zero, NaN, or +/- Inf.
552+
* isfinite(val_flt) covers !isnan(val_flt) and additionally blocks
553+
* +/- Inf: the shave (AND) loop preserves Inf bit patterns, but the
554+
* set (OR) loop turns +/- Inf (exponent all 1, mantissa 0) into a
555+
* NaN (exponent all 1, mantissa non-zero) by ORing low mantissa bits. */
552556
for (idx = 0L; idx < sz; idx += 2L)
553-
if ((val_flt = op1.fp[idx]) != mss_val_cmp_flt && val_flt != 0.0f && !isnan(val_flt))
557+
if ((val_flt = op1.fp[idx]) != mss_val_cmp_flt && val_flt != 0.0f && isfinite(val_flt))
554558
u32_ptr[idx] &= msk_f32_u32_zro;
555559
for (idx = 1L; idx < sz; idx += 2L)
556-
if ((val_flt = op1.fp[idx]) != mss_val_cmp_flt && val_flt != 0.0f && !isnan(val_flt))
560+
if ((val_flt = op1.fp[idx]) != mss_val_cmp_flt && val_flt != 0.0f && isfinite(val_flt))
557561
u32_ptr[idx] |= msk_f32_u32_one;
558562
break;
559563
case NC_DOUBLE:
@@ -580,13 +584,12 @@ ccr_bgr(const int nsd, const int type, const size_t sz, const int has_mss_val, p
580584
/* Bit Set mask for OR: Put ones into bits to be set, zeros in untouched bits */
581585
msk_f64_u64_one = ~msk_f64_u64_zro;
582586
// msk_f64_u64_hshv=msk_f64_u64_one & (msk_f64_u64_zro >> 1); /* Set one bit: the MSB of LSBs */
583-
/* Bit-Groom: alternately shave and set LSBs.
584-
* Do not quantize the dataset fill value, +/- zero, or NaN. */
587+
/* See float branch above for why isfinite(val_dbl) is used instead of !isnan(val_dbl). */
585588
for (idx = 0L; idx < sz; idx += 2L)
586-
if ((val_dbl = op1.dp[idx]) != mss_val_cmp_dbl && val_dbl != 0.0 && !isnan(val_dbl))
589+
if ((val_dbl = op1.dp[idx]) != mss_val_cmp_dbl && val_dbl != 0.0 && isfinite(val_dbl))
587590
u64_ptr[idx] &= msk_f64_u64_zro;
588591
for (idx = 1L; idx < sz; idx += 2L)
589-
if ((val_dbl = op1.dp[idx]) != mss_val_cmp_dbl && val_dbl != 0.0 && !isnan(val_dbl))
592+
if ((val_dbl = op1.dp[idx]) != mss_val_cmp_dbl && val_dbl != 0.0 && isfinite(val_dbl))
590593
u64_ptr[idx] |= msk_f64_u64_one;
591594
break;
592595
default:

BITROUND/example/CMakeLists.txt

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,15 @@ if (H5PL_BUILD_TESTING)
4343
target_link_libraries (h5repack_floats_bitround PRIVATE dl)
4444
endif ()
4545

46+
# Round-trip test for special floating-point values (NaN, +/- Inf, +/- zero).
47+
# See the test definition lower in this file for the full description.
48+
add_executable (h5filter_specials_bitround ${PROJECT_SOURCE_DIR}/h5filter_specials_bitround.c)
49+
TARGET_C_PROPERTIES (h5filter_specials_bitround ${LIB_TYPE})
50+
target_link_libraries (h5filter_specials_bitround PRIVATE ${H5PL_HDF5_LINK_LIBS})
51+
if (NOT WIN32)
52+
target_link_libraries (h5filter_specials_bitround PRIVATE dl)
53+
endif ()
54+
4655
macro (ADD_H5_TEST testname)
4756
add_test (
4857
NAME ${testname}-clearall
@@ -266,6 +275,43 @@ if (H5PL_BUILD_TESTING)
266275
if (NOT DISABLE_H5BITROUND_ENCODER)
267276
#UD BITROUND
268277
ADD_H5_UD_TEST (ud_convert 0 h5repack_floats.h5 --enable-error-stack -v -f UD=32023,0,5,3,4,0,0,0 -l CHUNK=4x8)
278+
279+
# Special-value round-trip test: writes a small chunked dataset containing
280+
# NaN, +/- Inf, +/- zero, plus normal values; applies the Granular BitRound
281+
# filter via H5Pset_filter; reads back; and asserts in C that the special
282+
# values are preserved bit-exact while at least one normal value was
283+
# quantized. The verdict comes from the program's exit code; no h5dump
284+
# comparison is involved, so cross-platform NaN/Inf printf formatting
285+
# cannot affect the result. TESTLIBDIR is already set by ADD_H5_UD_TEST
286+
# above.
287+
add_test (
288+
NAME H5BITROUND_UD-specials-roundtrip-clearall
289+
COMMAND ${CMAKE_COMMAND} -E remove
290+
h5filter_specials_bitround.h5
291+
h5filter_specials_bitround.out
292+
h5filter_specials_bitround.out.err
293+
)
294+
if (NOT "${last_test}" STREQUAL "")
295+
set_tests_properties (H5BITROUND_UD-specials-roundtrip-clearall PROPERTIES DEPENDS ${last_test})
296+
endif ()
297+
set (last_test "H5BITROUND_UD-specials-roundtrip-clearall")
298+
add_test (
299+
NAME H5BITROUND_UD-specials-roundtrip
300+
COMMAND "${CMAKE_COMMAND}"
301+
-D "TEST_PROGRAM=$<TARGET_FILE:h5filter_specials_bitround>"
302+
-D "TEST_FOLDER=${PROJECT_BINARY_DIR}"
303+
-D "TEST_EXPECT=0"
304+
-D "TEST_OUTPUT=h5filter_specials_bitround.out"
305+
-D "TEST_SKIP_COMPARE=1"
306+
-D "TEST_LIBRARY_DIRECTORY=${TESTLIBDIR}"
307+
-D "TEST_ENV_VAR=HDF5_PLUGIN_PATH"
308+
-D "TEST_ENV_VALUE=${CMAKE_BINARY_DIR}/plugins"
309+
-P "${H5BITROUND_RESOURCES_DIR}/runTest.cmake"
310+
)
311+
set_tests_properties (H5BITROUND_UD-specials-roundtrip PROPERTIES
312+
WORKING_DIRECTORY "${PROJECT_BINARY_DIR}"
313+
DEPENDS H5BITROUND_UD-specials-roundtrip-clearall)
314+
set (last_test "H5BITROUND_UD-specials-roundtrip")
269315
endif ()
270316

271317
endif ()

0 commit comments

Comments
 (0)