PAYATHON 2026

MPSolve 为何因 isnan 和 isinf 冲突而无法编译?

排障赵师傅

结论

include/mps/types.h 定义的函数式宏 isnan(x) 和 isinf(x) 导致了编译失败。编译 C++ 源文件时,预处理器会用这两个宏展开 libc++ <cmath> 中的 std::isnan() 和 std::isinf(),把原本合法的 C++ 代码改成无效语法。

对于 MPSolve 3.2.2,可以先注释或删除 types.h 中的这两组兼容宏,然后重新编译。这样可以消除日志中的四个错误。这是源码级的临时修复,编译通过后仍需测试各条数值计算路径。

为什么宏会破坏 <cmath>

日志中已经给出了宏的展开位置:

.../c++/v1/cmath:568:15: error: expected unqualified-id
568 | return std::isnan(__lcpp_x);
    |               ^
../../include/mps/types.h:27:3: note: expanded from macro 'isnan'

C/C++ 预处理器不识别命名空间。它只要遇到 isnan(,就会尝试展开同名的函数式宏。因此,下面这段代码:

std::isnan(value)

也会触发 MPSolve 的宏,展开结果大致如下:

std::(sizeof(value) == sizeof(long double) ? ... )

这不是合法的 C++ 表达式,所以编译器会报告 expected unqualified-id。std::isinf() 也会遇到同样的问题。

日志中的以下错误通常是宏展开失败引发的连锁反应:

error: no return statement in constexpr function

it_threshold 未使用和 mps_parse_option_line 的警告不会直接中断这次构建。导致 make 退出的是 <cmath> 中的四个编译错误。

临时修复方法

打开解压后的头文件:

mpsolve-3.2.2/include/mps/types.h

找到 #include <math.h> 后定义 isnan 和 isinf 的部分,注释掉整个兼容代码块。只需修改这一段,不必复制或替换完整的 types.h:

/* Handle systems where isnan and isinf are not available */
#include <math.h>

/*
#ifndef isnan
# define isnan(x) \
  (sizeof(x) == sizeof(long double) ? isnan_ld (x) \
   : sizeof(x) == sizeof(double) ? isnan_d (x) \
   : isnan_f (x))

static inline int isnan_f (float x)
{
  return x != x;
}

static inline int isnan_d (double x)
{
  return x != x;
}

static inline int isnan_ld (long double x)
{
  return x != x;
}
#endif

#ifndef isinf
# define isinf(x) \
  (sizeof(x) == sizeof(long double) ? isinf_ld (x) \
   : sizeof(x) == sizeof(double) ? isinf_d (x) \
   : isinf_f (x))

static inline int isinf_f (float x)
{
  return !isnan (x) && isnan (x - x);
}

static inline int isinf_d (double x)
{
  return !isnan (x) && isnan (x - x);
}

static inline int isinf_ld (long double x)
{
  return !isnan (x) && isnan (x - x);
}
#endif
*/

#include <mps/mt-types.h>

保存后,清理此前生成的目标文件并重新构建:

make clean
make

如果项目使用单独的构建目录,请在原来的构建目录中执行这些命令。修改后若仍出现相同错误,应确认编译器引用的是刚刚修改的 types.h,而非系统中已经安装的另一份 MPSolve 头文件。

更合适的长期处理方式

直接注释这些宏,需要目标平台已经提供可用的 isnan 和 isinf。当前日志来自 macOS libc++,其中的 <cmath> 提供了 std::isnan 和 std::isinf,旧的兼容实现反而会与它们冲突。

如果要维护可移植补丁,可以只在确实缺少这些接口的 C 构建中启用兼容定义,至少应避开 C++:

#ifndef __cplusplus

#ifndef isnan
/* 原有 isnan 兼容实现 */
#endif

#ifndef isinf
/* 原有 isinf 兼容实现 */
#endif

#endif

这只是一种补丁思路。它是否适用于项目支持的所有编译器和平台,还需要结合配置检测和测试套件验证。也不要直接让宏指向 std::isnan 或 std::isinf,因为 types.h 也可能被 C 源文件包含,而 C 语言没有 std 命名空间。

相关冲突见 MPSolve issue #38 和 FreeBSD Bug 273338。如果使用其他版本,应先检查对应源码是否已经修正,以免重复修改。

注意事项

  • 这项修改只解决 isnan、isinf 与 <cmath> 的名称冲突。
  • 编译成功并不代表数值行为已经经过完整验证。安装后应运行项目自带的测试,并检查实际使用的多项式求根场景。
  • 如果目标系统缺少 isnan 或 isinf,直接删除兼容实现可能产生新的错误,此时仍需保留有条件的回退实现。
  • 调整头文件的包含顺序或许能暂时避开 <cmath> 内部的错误,但这些宏仍可能破坏后续代码中的 std::isnan(),不能作为可靠的修复方案。

备注:内容仅供参考。