Fortran中selected_real_kind()常量定义的正确方法
首先明确你观察到的现象本质:
你测试里的两个版本根本不是同一种精度,常量的表示差异和selected_real_kind这个写法本身没有任何关系。你写selected_real_kind(16,40)是要求编译器提供至少16位十进制有效数字、指数范围不小于10^±40的实数类型,在x86平台上这通常对应80位扩展双精度(10字节长度);而real(kind=8)是硬编码kind值的64位IEEE双精度,仅能保证15位左右有效数字,两者精度差了一个等级,常量存储值不一样是必然结果。
关于性能问题你完全搞反了原因:
程序如果变慢,和常量的尾差没有任何关系,开销完全来自你选的80位扩展双精度类型本身。现代CPU的SSE/AVX向量指令集对64位双精度有完整的向量化支持,运算吞吐量很高;但80位扩展精度通常只能走老旧的x87标量指令栈,没法并行向量化,运算速度比64位双精度慢2-4倍都是正常范围。只要你选的精度等级一致,用selected_real_kind和硬编码kind值生成的机器码完全相同,不会有任何额外性能损失。
selected_real_kind的规范实现方式
硬写real(kind=8)本身就是不符合Fortran标准的不可移植写法——Fortran从来没有规定kind值和类型字节长度的对应关系,部分编译器下双精度的kind值可能不是8,硬编码数值换编译环境就会出bug。用selected_real_kind才是标准推荐的写法,注意遵循以下规则即可:
- 按你实际需要的精度选参数,不要盲目选高精度:如果只需要普通64位双精度,就写
selected_real_kind(15, 307),这个参数刚好匹配IEEE 64位双精度的指标(15位有效数字,指数范围覆盖10-308到10308),在所有符合标准的编译器上都能选到正确的双精度类型,可移植性远好于硬写kind=8。如果确实需要更高精度,再单独定义扩展精度的kind参数,不要把高精度类型用在不需要的场景里平白损失性能。 - 所有常量的后缀必须和你定义的kind参数绑定,不要硬写
d0或者固定后缀:比如你定义了双精度kind参数叫dp,所有实数常量都要加_dp后缀,不要写1.d0——d0是固定对应编译器默认双精度的后缀,万一你调整了kind参数,没改后缀的常量会先被转成低精度再赋值,带来不必要的精度损失。 - 常用数学常量不要依赖运行时函数计算:比如PI不要写
4*atan(1.0_dp),虽然大部分编译器能在编译期算出正确值,但不同数学库的实现可能有细微差异,直接按所选精度写足有效数字的字面值最稳妥,还能完全避免编译期/运行时的计算开销。
规范的参数模块参考实现
module param implicit none save ! 精度定义:按需选择,不要盲目上高精度 integer, parameter :: dp = selected_real_kind(15, 307) ! 普通64位IEEE双精度 ! integer, parameter :: xtd = selected_real_kind(18, 4931) ! 80位扩展双精度,需要时再启用 ! 常量统一绑定kind后缀,写足有效数字 real(dp), parameter :: PI = 3.14159265358979323846_dp integer, parameter :: L = 512 integer, parameter :: steps = 160000 real(dp), parameter :: dt = 0.001_dp real(dp), parameter :: lambda = 1.0_dp real(dp), parameter :: mu = 0.0_dp real(dp), parameter :: a = dt*(lambda+mu), b = dt*(lambda-mu) end module param
最后补充一点:编译期就确定值的parameter常量,不管精度是多少,都不会在程序运行时带来额外计算开销,所有常量值都是编译阶段直接算好写进二进制的,不用怕常量定义本身拖慢速度。
内容的提问来源于stack exchange,提问作者ferro11001

