This is an automated email from the ASF dual-hosted git repository.
xiaoxiang781216 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx-apps.git
The following commit(s) were added to refs/heads/master by this push:
new e99ed0b48 testing: Add host fmaf regression program
e99ed0b48 is described below
commit e99ed0b48b42553f4f840390acc481daf3af1475
Author: Anatol Pomozov <[email protected]>
AuthorDate: Sat Oct 10 17:01:11 2026 -0700
testing: Add host fmaf regression program
Add a host-side regression program under testing/libc/fmaf that compiles the
NuttX fmaf implementation and compares results and required exception flags
with host libm across all four rounding modes. Include Make and CMake
metadata
for building it as a NuttX test app.
Keep the comparison harness with the apps testing sources rather than the
NuttX tools directory, and document how to invoke it from nuttx-apps.
Tests: host C compiler build and run against system libm; 4,072,672 cases
passed across four rounding modes.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Anatol Pomozov <[email protected]>
---
testing/libc/fmaf/CMakeLists.txt | 38 ++++++
testing/libc/fmaf/Kconfig | 12 ++
testing/libc/fmaf/Make.defs | 25 ++++
testing/libc/fmaf/Makefile | 34 +++++
testing/libc/fmaf/test_fmaf.c | 282 +++++++++++++++++++++++++++++++++++++++
5 files changed, 391 insertions(+)
diff --git a/testing/libc/fmaf/CMakeLists.txt b/testing/libc/fmaf/CMakeLists.txt
new file mode 100644
index 000000000..87a37af90
--- /dev/null
+++ b/testing/libc/fmaf/CMakeLists.txt
@@ -0,0 +1,38 @@
+#
##############################################################################
+# apps/testing/libc/fmaf/CMakeLists.txt
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
contributor
+# license agreements. See the NOTICE file distributed with this work for
+# additional information regarding copyright ownership. The ASF licenses this
+# file to you under the Apache License, Version 2.0 (the "License"); you may
not
+# use this file except in compliance with the License. You may obtain a copy
of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+#
+#
##############################################################################
+
+if(CONFIG_TESTING_FMAF)
+ nuttx_add_application(
+ NAME
+ fmaf_test
+ STACKSIZE
+ ${CONFIG_DEFAULT_TASK_STACKSIZE}
+ MODULE
+ ${CONFIG_TESTING_FMAF}
+ COMPILE_FLAGS
+ -frounding-math
+ -ffp-contract=off
+ INCLUDE_DIRECTORIES
+ ${NUTTX_DIR}/libs/libm/libm
+ SRCS
+ test_fmaf.c)
+endif()
diff --git a/testing/libc/fmaf/Kconfig b/testing/libc/fmaf/Kconfig
new file mode 100644
index 000000000..0aca89fee
--- /dev/null
+++ b/testing/libc/fmaf/Kconfig
@@ -0,0 +1,12 @@
+#
+# For a description of the syntax of this configuration file,
+# see the file kconfig-language.txt in the NuttX tools repository.
+#
+
+config TESTING_FMAF
+ tristate "fmaf() test"
+ default n
+ ---help---
+ Compare the NuttX fmaf() implementation with the configured
libm over
+ boundary, double-rounding, and randomized inputs in all four
rounding
+ modes. Requires binary64 double precision.
diff --git a/testing/libc/fmaf/Make.defs b/testing/libc/fmaf/Make.defs
new file mode 100644
index 000000000..8f7846203
--- /dev/null
+++ b/testing/libc/fmaf/Make.defs
@@ -0,0 +1,25 @@
+############################################################################
+# apps/testing/libc/fmaf/Make.defs
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership. The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+############################################################################
+
+ifneq ($(CONFIG_TESTING_FMAF),)
+CONFIGURED_APPS += $(APPDIR)/testing/libc/fmaf
+endif
diff --git a/testing/libc/fmaf/Makefile b/testing/libc/fmaf/Makefile
new file mode 100644
index 000000000..612b328cb
--- /dev/null
+++ b/testing/libc/fmaf/Makefile
@@ -0,0 +1,34 @@
+############################################################################
+# apps/testing/libc/fmaf/Makefile
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership. The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+############################################################################
+
+include $(APPDIR)/Make.defs
+
+PROGNAME = fmaf_test
+PRIORITY = SCHED_PRIORITY_DEFAULT
+STACKSIZE = $(CONFIG_DEFAULT_TASK_STACKSIZE)
+MODULE = $(CONFIG_TESTING_FMAF)
+
+CFLAGS += -frounding-math -ffp-contract=off -I$(TOPDIR)/libs/libm/libm
+
+MAINSRC = test_fmaf.c
+
+include $(APPDIR)/Application.mk
diff --git a/testing/libc/fmaf/test_fmaf.c b/testing/libc/fmaf/test_fmaf.c
new file mode 100644
index 000000000..8e8f2bc59
--- /dev/null
+++ b/testing/libc/fmaf/test_fmaf.c
@@ -0,0 +1,282 @@
+/****************************************************************************
+ * apps/testing/libc/fmaf/test_fmaf.c
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership. The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Compile and link this file directly with -frounding-math -ffp-contract=off
+ * and the host libm:
+ *
+ * cc -O2 -frounding-math -ffp-contract=off \
+ * -I../nuttx/libs/libm/libm testing/libc/fmaf/test_fmaf.c \
+ * -o test_fmaf -lm
+ *
+ * Compare with host libm in all four rounding modes. NaN payloads may vary;
+ * finite values, signed zeros and required exception flags must match.
+ * The exit status is zero if all cases match.
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <fenv.h>
+#include <float.h>
+#include <math.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+/* Keep the implementation under a private name so the test can compare it
+ * with the fmaf() supplied by the configured C library.
+ */
+
+#define fmaf nuttx_fmaf
+#include "lib_fmaf.c"
+#undef fmaf
+
+float nuttx_fmaf(float x, float y, float z);
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+static uint32_t next_bits(uint32_t *state)
+{
+ *state = *state * UINT32_C(1664525) + UINT32_C(1013904223);
+ return *state;
+}
+
+static float from_bits(uint32_t bits)
+{
+ float value;
+
+ memcpy(&value, &bits, sizeof(value));
+ return value;
+}
+
+static uint32_t to_bits(float value)
+{
+ uint32_t bits;
+
+ memcpy(&bits, &value, sizeof(bits));
+ return bits;
+}
+
+static int check(uint32_t x, uint32_t y, uint32_t z, int rounding)
+{
+ float a = from_bits(x);
+ float b = from_bits(y);
+ float c = from_bits(z);
+ int mask = FE_INVALID | FE_OVERFLOW | FE_UNDERFLOW | FE_INEXACT;
+ int expected_flags;
+ int actual_flags;
+ float expected;
+ float actual;
+
+ feclearexcept(FE_ALL_EXCEPT);
+ expected = fmaf(a, b, c);
+ expected_flags = fetestexcept(mask);
+ feclearexcept(FE_ALL_EXCEPT);
+ actual = nuttx_fmaf(a, b, c);
+ actual_flags = fetestexcept(mask);
+
+ /* C permits either invalid-flag choice for infinity times zero plus a
+ * quiet NaN. A scalar multiply/add raises it while a hardware FMA can
+ * give the NaN precedence; both return the required NaN result.
+ */
+
+ if ((z & UINT32_C(0x7fc00000)) == UINT32_C(0x7fc00000) &&
+ (((x & UINT32_C(0x7fffffff)) == 0 &&
+ (y & UINT32_C(0x7fffffff)) == UINT32_C(0x7f800000)) ||
+ ((y & UINT32_C(0x7fffffff)) == 0 &&
+ (x & UINT32_C(0x7fffffff)) == UINT32_C(0x7f800000))))
+ {
+ actual_flags &= ~FE_INVALID;
+ expected_flags &= ~FE_INVALID;
+ }
+
+ if (!(isnan(actual) && isnan(expected)) &&
+ to_bits(actual) != to_bits(expected))
+ {
+ fprintf(stderr, "fmaf result: round=%d x=%08x y=%08x z=%08x "
+ "got=%08x expected=%08x\n", rounding, x, y, z,
+ to_bits(actual), to_bits(expected));
+ return 1;
+ }
+
+ if (actual_flags != expected_flags)
+ {
+ fprintf(stderr, "fmaf exceptions: round=%d x=%08x y=%08x z=%08x "
+ "got=%x expected=%x\n", rounding, x, y, z,
+ actual_flags, expected_flags);
+ return 1;
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+int main(void)
+{
+ static const int rounding[] =
+ {
+ FE_TONEAREST, FE_DOWNWARD, FE_UPWARD, FE_TOWARDZERO
+ };
+
+ static const uint32_t boundary[] =
+ {
+ 0, 0x80000000, 1, 0x80000001, 0x007fffff, 0x807fffff,
+ 0x00800000, 0x80800000, 0x3f000000, 0xbf000000,
+ 0x3f800000, 0xbf800000, 0x3f800001, 0xbf800001,
+ 0x7f7fffff, 0xff7fffff, 0x7f800000, 0xff800000,
+ 0x7fc00001, 0x7f800001
+ };
+
+ /* Products on either side of a binary32 halfway value must not acquire
+ * a false tie while rounding via binary64, including subnormal outputs.
+ */
+
+ static const uint32_t ties[][3] =
+ {
+ { 0x3f800001, 0x3f800001, 0xbf800002 },
+ { 0x007fffff, 0x3f800001, 0x00800000 },
+ { 0x00000001, 0x3f000000, 0x00000001 },
+ { 0x00000001, 0x00000001, 0x00800000 },
+ { 0x80000001, 0x00000001, 0x00800000 },
+ { 0x3f800000, 0x3f800000, 0x33800000 },
+ { 0x3f800000, 0x3f800000, 0x33800001 },
+ { 0x3f800000, 0x3f800000, 0x337fffff },
+ };
+
+ /* Mantissas (in units of the result's last place) for the double
+ * rounding traps below: even and odd, all ones, and subnormal.
+ */
+
+ static const uint32_t trapmant[] =
+ {
+ 1, 3, 0x7fffff, 0x800000, 0x800001, 0x800002, 0x555555, 0xaaaaaa,
+ 0xfffffe, 0xffffff
+ };
+
+ const unsigned int nrounding = sizeof(rounding) / sizeof(rounding[0]);
+ const unsigned int nties = sizeof(ties) / sizeof(ties[0]);
+ const unsigned int nboundary = sizeof(boundary) / sizeof(boundary[0]);
+ const unsigned int ntrapmant = sizeof(trapmant) / sizeof(trapmant[0]);
+ int original = fegetround();
+ uint64_t count = 0;
+ int ret = 1;
+
+ for (unsigned int r = 0; r < nrounding; r++)
+ {
+ uint32_t state = 0x230;
+
+ if (fesetround(rounding[r]))
+ {
+ goto done;
+ }
+
+ for (unsigned int i = 0; i < nties; i++)
+ {
+ if (check(ties[i][0], ties[i][1], ties[i][2], rounding[r]))
+ {
+ goto done;
+ }
+
+ count++;
+ }
+
+ for (unsigned int i = 0; i < nboundary; i++)
+ {
+ for (unsigned int j = 0; j < nboundary; j++)
+ {
+ for (unsigned int k = 0; k < nboundary; k++)
+ {
+ if (check(boundary[i], boundary[j], boundary[k],
+ rounding[r]))
+ {
+ goto done;
+ }
+
+ count++;
+ }
+ }
+ }
+
+ /* Double rounding traps. With x = (1 + 2^-23) 2^a and
+ * y = (1 - 2^-23) 2^b, the product is 2^(e-1) (1 - 2^-46) for
+ * e = a + b + 1: exact in binary64, and just below half a unit in
+ * the last place of z = m 2^e. The exact sum is therefore just
+ * below (or, if z has the opposite sign, just above) a binary32
+ * halfway value, but its binary64 rounding is that halfway value.
+ * Converting that to binary32 picks the even neighbour, which is
+ * the wrong one half of the time. This is what the halfway
+ * correction in fmaf() is for; random inputs reach it with a
+ * probability near 2^-29 and the cases above only by accident.
+ */
+
+ for (int e = -149; e <= 104; e++)
+ {
+ const int a = (e - 1) / 2;
+ const int b = e - 1 - a;
+ const float x = ldexpf(1.0f + FLT_EPSILON, a);
+ const float y = ldexpf(1.0f - FLT_EPSILON, b);
+
+ for (unsigned int m = 0; m < ntrapmant; m++)
+ {
+ const float z = ldexpf((float)trapmant[m], e);
+
+ for (unsigned int s = 0; s < 4; s++)
+ {
+ uint32_t xbits = to_bits(x) ^ ((s & 1) ? 0x80000000 : 0);
+ uint32_t zbits = to_bits(z) ^ ((s & 2) ? 0x80000000 : 0);
+
+ if (check(xbits, to_bits(y), zbits, rounding[r]))
+ {
+ goto done;
+ }
+
+ count++;
+ }
+ }
+ }
+
+ for (unsigned int i = 0; i < 1000000; i++)
+ {
+ uint32_t x = next_bits(&state);
+ uint32_t y = next_bits(&state);
+ uint32_t z = next_bits(&state);
+
+ if (check(x, y, z, rounding[r]))
+ {
+ goto done;
+ }
+
+ count++;
+ }
+ }
+
+ printf("fmaf: %llu cases match host libm bits and exception flags "
+ "in four rounding modes\n", (unsigned long long)count);
+ ret = 0;
+done:
+ fesetround(original);
+ return ret;
+}