Fortran - 想四舍五入到小数点后一位

Fortran - want to round to one decimal point

在 fortran 中,我必须将纬度和经度四舍五入到小数点后一位。

我正在使用 gfortran 编译器和 nint 函数,但以下方法不起作用:

print *, nint( 1.40 * 10. ) / 10.    ! prints 1.39999998
print *, nint( 1.49 * 10. ) / 10.    ! prints 1.50000000

在此处寻找通用和特定的解决方案。例如:

  1. 如何显示四舍五入到小数点后一位的数字?

  2. 我们如何在 fortran 中存储这样的四舍五入的数字。在 float 变量中是不可能的,但是还有其他方法吗?

  3. 我们如何将这样的数字写入 NetCDF?

  4. 我们如何将这些数字写入 CSV 或文本文件?

无法按照您的要求进行操作。潜在的问题是您想要的舍入值不一定能够使用浮点数表示。

例如,如果您有一个值 10.58,则它在 IEEE754 float32 中表示为 1.3225000 x 2^3 = 10.580000。

当您将此值四舍五入到小数点后一位(无论您选择这样做)时,结果将是 10.6,但 10.6 没有精确表示。最接近的表示形式是 float32 中的 1.3249999 x 2^3 = 10.599999。所以无论你如何处理舍入,都无法将 10.6 精确存储在 float32 值中,也无法将其作为浮点值写入 netCDF 文件。

是的,可以做到!上面的 "accepted" 答案在其有限的范围内是正确的,但是对于您在 Fortran(或其他各种 HGL)中实际可以完成的事情是错误的。

唯一的问题是,如果使用 F(6.1) 的写入失败,您愿意付出什么代价?

从一个角度来看,您的问题是 "Arbitrary Precision" 计算主题的一个特别微不足道的变体。当您需要精确地存储、操作和执行 "math" 时,您如何想象密码学是如何处理的?例如,1024 位数字?

在这种情况下,一个简单的策略是将每个数字分成其组成部分 "LHSofD"(小数的左侧)和 "RHSofD" 值。例如,您可能有一个 RLon(i,j) = 105.591,并希望将 105.6(或任何舍入方式)打印到您的 netCDF(或任何正常)文件中。将其拆分为 RLonLHS(i,j) = 105 和 RLonRHS(i,j) = 591.

...此时您可以选择增加通用性,但要付出一定的代价。为了保存 "money",RHS 可能会保留为 0.591(但如果您需要做更奇特的事情,则不具有一般性)。

为简单起见,假设 "cheap and cheerful" 第二种策略。

LHS 很简单 (Int())。

现在,对于 RHS,乘以 10(如果您希望四舍五入为 1 DEC),例如到达 RLonRHS(i,j) = 5.91,然后应用 Fortran "round to nearest Int" NInt() intrinsic ... 留下 RLonRHS(i,j) = 6.0.

...鲍勃是你的叔叔:

现在,您使用连接 "duals" 的合适 Write 语句将 LHS 和 RHS 打印到您的 netCDF,并将根据 OP 中所需的目标创建精确表示。

... 当然,稍后将这些值 returns 读入与上述相同的问题,除非读入也是 ArbPrec 感知的。

...我们编写了自己的 ArbPrec 库,但也有一些关于,也在 VBA 和其他 HGL 中...但请注意,完整的 ArbPrec 机器位是一件非常重要的事情。 ..幸运的是你的问题很简单。

正如其他人所说,问题在于 NetCDF 文件中浮点表示的使用。使用 nco 实用程序,您可以使用 scale_factor 和 add_offset 将 latitude/longitude 更改为短整数。像这样:

ncap2 -s 'latitude=pack(latitude, 0.1, 0); longitude=pack(longitude, 0.1, 0);' old.nc new.nc

`round_x = nint(x*10d0)/10d0' 运算符将 x 舍入(对于 abs(x) < 2**31/10,对于大数使用 dnint())并分配round_x 变量的四舍五入值用于进一步计算。

如上面的答案所述,并非所有小数点后一位有效数字都有精确表示,例如0.3就没有。

print *, 0.3d0

输出:

  0.29999999999999999

要将舍入值输出到文件、屏幕或将其转换为小数点后只有一个有效数字的字符串,请使用编辑描述符 'Fw.1'(w - 宽度 w 个字符, 0 - 可变宽度)。例如:

print '(5(1x, f0.1))', 1.30, 1.31, 1.35, 1.39, 345.46

输出:

 1.3 1.3 1.4 1.4 345.5

@JohnE,使用'G10.2'是不正确的,它将结果四舍五入到两位有效数字,而不是小数点后一位。例如:

print '(g10.2)',  345.46

输出:

0.35E+03

P.S.

对于 NetCDF,舍入应由 NetCDF 查看器处理,但是,您可以将变量输出为 NC_STRING 类型:

write(NetCDF_out_string, '(F0.1)') 1.49

或者,或者,获取“漂亮的”NC_FLOAT/NC_DOUBLE 个数字:

beautiful_float_x = nint(x*10.)/10. + epsilon(1.)*nint(x*10.)/10./2.
beautiful_double_x = dnint(x*10d0)/10d0 + epsilon(1d0)*dnint(x*10d0)/10d0/2d0

P.P.S。 @JohnE

  1. 首选的解决方案是不对内存或文件中的中间结果进行舍入。仅在发出人类可读数据的最终输出时才执行舍入;

  2. 使用带有编辑描述符“Fw.1”的打印,见上文;

  3. 没有简单可靠的方法来准确存储四舍五入的数字(带小数点的数字):

2.1。理论上,某些 Fortran 实现可以支持十进制算术,但我不知道其中 'selected_real_kind(4, 4, 10)' returns 不是 -5 的值的实现;

2.2。可以将四舍五入的数字存储为字符串;

2.3。您可以使用 GIMP 库的 Fortran 绑定。带有 mpq_ 前缀的函数设计用于处理有理数;

  1. 没有简单可靠的方法可以在 netCDF 文件中写入四舍五入的数字,同时为该文件的 reader 保留它们的属性:

3.1。 netCDF 支持 'Packed Data Values‘, i.e. you can set an integer type with the attributes’ scale_factor‘,’ add_offset' 并保存整数数组。但是,在文件中,‘scale_factor’将存储为单精度或双精度的浮点数,即该值将不同于 0.1。相应地,读取时,通过netCDF库unpacked_data_value = packed_data_value*scale_factor + add_offset计算时,会出现舍入误差。 (您可以设置scale_factor=0.1*(1.+epsilon(1.))scale_factor=0.1d0*(1d0+epsilon(1d0))以排除大量数字'9'。);

3.2。有 C_format 和 FORTRAN_format 属性。但是很难预测哪个 reader 会使用哪个属性以及他们是否会使用它们;

3.3。您可以将四舍五入的数字存储为字符串或用户定义的类型;

  1. 使用带有编辑描述符“Fw.1”的 write(),见上文。

关于“四舍五入到一位小数”,可以考虑几个方面。这些涉及:内部存储和操作;展示与交流。

展示与交流

最简单的方面涵盖了我们如何报告储值,而不考虑使用的内部表示。正如其他答案和其他地方所深入介绍的那样,我们可以使用带有单个小数位的数字编辑描述符:

print '(F0.1,2X,F0.1)', 10.3, 10.17
end

输出的四舍五入方式是可变模式:

print '(RU,F0.1,2X,RD,F0.1)', 10.17, 10.17
end

在此示例中,我们选择向上舍入然后向下舍入,但我们也可以舍入为零或舍入到最接近的值(或让编译器为我们选择)。

对于任何格式化输出,无论是屏幕还是文件,都可以使用此类编辑描述符。 G 编辑描述符,例如可用于编写 CSV 文件的描述符,也将执行此舍入。

对于未格式化的输出,此舍入概念不适用,因为引用了内部表示。同样,对于 NetCDF 和 HDF5 等交换格式,我们没有这种舍入。

对于 NetCDF,您的属性约定可以指定类似 FORTRAN_format 的内容,它为(默认)实数、非舍入变量的最终显示提供了适当的格式。

内部存储空间

其他答案和问题本身都提到了不可能准确表示(和使用)十进制数字。但是,Fortran 语言中没有任何内容要求这是不可能的:

integer, parameter :: rk = SELECTED_REAL_KIND(radix=10)
real(rk) x
x = 0.1_rk

print *, x

end

是一个 Fortran 程序,它有一个基数为 10 的变量和文字常量。另见 IEEE_SELECTED_REAL_KIND(radix=10).

现在,您极有可能看到 selected_real_kind(radix=10) 为您提供值 -5,但如果您想要一些可以用作类型参数的正数,您只需要找人提供你这样的系统。

如果您找不到这样的东西,那么您将需要努力解决错误。这里有两部分需要考虑。

Fortran 中的固有实数类型是浮点数。要使用定点数字类型或二进制编码的十进制系统,您需要求助于非固有类型。这样的话题超出了这个答案的范围,但是 DrOli 已经在那个方向上做了指示。

这些努力不会 computationally/programmer-time 便宜。您还需要在输出和交换中注意管理这些类型。

根据您的工作要求,您可能会发现简单地按 10 的(幂)缩放并处理整数套装。在这种情况下,您还需要在约定中找到相应的 NetCDF 属性,例如 scale_factor.

关于我们的内部表示问题,我们对输出有类似的舍入问题。例如,如果我的输入数据的经度为 10.17... 但我想在我的内部表示中将其四舍五入为(最接近的可表示值)一个十进制数字(比如 10.2/10.1999998) 然后解决它,我该如何管理它?

我们已经看到 nint(10.17*10)/10. 如何给我们这个,但我们也了解了一些关于数字编辑描述符如何很好地为输出做这件事,包括控制舍入模式:

character(10) :: intermediate
real :: rounded
write(intermediate, '(RN,F0.1)') 10.17
read(intermediate, *) rounded

print *, rounded  ! This may look not "exact"

end

如果需要,我们可以在此处跟踪错误的累积。