【问题标题】:Fortran - want to round to one decimal pointFortran - 想要四舍五入到小数点后一位
【发布时间】:2016-12-29 19:36:47
【问题描述】:

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

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

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

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

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

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

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

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

【问题讨论】:

  • 真正简短的回答是你不能这样做。浮点表示是不精确的,并且不能保证浮点除以十(或四舍五入到小数点后一位)在您使用的任何大小的浮点类型下都有精确的表示。这仅用于打印或显示吗?
  • @talonmies - 不,它没有打印或显示。我有一个输入 netcdf 文件,小数点后只有一位。我已经修改了经度,我想把它写回 netcdf 文件。
  • 好吧,你不能用浮点数做到这一点。您将需要使用定点或小数类型
  • @talonmies - 你能举个例子并添加它作为答案吗?我很乐意投票并接受
  • NetCDF 浮点数与 Fortran REAL 相同。

标签: fortran rounding gfortran netcdf


【解决方案1】:

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

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

【讨论】:

  • @JohnE 如果您正在寻找“打印一个四舍五入的数字或将其写入 CSV”write(*,'(f1.1)') 1.40write(*,'(f1.1)') 1.49 有什么问题? OP 问题的答案的重点是 NetCDF 具有非常具体的允许格式,并且不允许截断浮点数。
  • @JohnE 恕我直言,如果需要做任何事情,那么应该编辑问题(尤其是标题)以明确 NetCDF 要求,并希望将搜索流量发送到更相关的信息源。我相信如果您认为自己悬赏了错误的问题,则可能会被 mod 删除。
  • @JohnE 如果答案令人困惑,我很乐意删除它。但是,我只看到了这个问题,因为上面有netcdf标签。
  • @RobertDavy 不,请离开。我可能会根据我正在寻找的内容为不同的答案提供赏金,但是当我输入此内容时,您有 2 个赞成票,所以显然还有其他人认为这很有帮助。更多答案意味着人们有更多选择,这很好。
【解决方案2】:

没有办法按照你的要求去做。潜在的问题是您想要的舍入值不一定能够使用浮点数表示。

例如,如果您有一个值 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 文件。

【讨论】:

  • 这取决于“将其写回文件”的含义。如果它使用 FORMAT(F6.1,....) 那么它会起作用。
  • @Holmz:注意我写了netCDF file
【解决方案3】:

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

唯一的问题是,如果像 Write with F(6.1) 这样的操作失败,你愿意付出什么代价?

从一个角度来看,您的问题是“任意精度”计算主题的一个特别微不足道的变化。当您需要以精确的精度存储、操作和执行“数学”(例如 1024 位数字)时,您如何想象密码学是如何处理的?

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

...在这一点上,您可以选择增加通用性,但要付出一些代价。为了节省“钱”,RHS 可能会保留为 0.591(但如果您需要做更高级的事情,则一般性较宽松)。

为简单起见,假设“便宜又愉快”的第二种策略。

LHS 很简单 (Int())。

现在,对于 RHS,乘以 10(如果您希望四舍五入到 1 DEC),例如到达 RLonRHS(i,j) = 5.91,然后应用 Fortran“四舍五入到最接近的 Int” NInt() 内在函数......留下 RLonRHS(i,j) = 6.0。

...鲍勃是你的叔叔:

现在您使用连接“对偶”的合适的写入语句将 LHS 和 RHS 打印到您的 netCDF,并将根据 OP 中所需的目标创建一个精确的表示。

...当然,稍后读入这些值会返回与上述相同的问题,除非读入也支持 ArbPrec。

...我们编写了我们自己的 ArbPrec 库,但有几个关于,也在 VBA 和其他 HGL 中...但是要注意完整的 ArbPrec 机器位不是一件小事...幸运的是你的问题是这么简单。

【讨论】:

    【解决方案4】:

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

    展示与交换

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

    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 已向该方向提出了指示。

    这些努力在计算/程序员时间上不会便宜。您还需要在输出和交换中管理这些类型。

    根据您的工作要求,您可能会发现只需按 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
    

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

    【讨论】:

      【解决方案5】:

      `round_x = nint(x*10d0)/10d0' 运算符对 x 进行舍入(对于 abs(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

      附言

      对于 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)”返回-5以外的值的实现;

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

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

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

      3.1。 netCDF 支持“打包数据值”,即您可以使用属性“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 属性。但是很难预测哪个读者会使用哪个属性以及他们是否会使用它们;

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

      1. 将 write() 与编辑描述符“Fw.1”一起使用,见上文。

      【讨论】:

      • @JohnE,在我看来,你改变问题仍然没有改善它。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-01-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-26
      相关资源
      最近更新 更多