如何通过测量 float 类型的时间间隔(以秒为单位)来防止浮点除法变成整数除法

How to prevent that a floating point divistion turns into integer devision by measuruing time intervals in seconds of type float

我尝试在 Freebsd 12 下使用 perl 5.20 版计算批量复制过程的传输速率。我得到了奇怪的结果,因为亚秒间隔内的值始终为零,尽管使用浮点除法来获得分形秒持续时间条目。

详情:

对于测量,我使用 DateTime::Hires 并在名为 markTimediffTime 的两个子例程中提取时间标记和差异。要查看每个步骤的进展情况。

#!/usr/bin/env perl
use v5.20.1;
use warnings;
use strict;
use bigint;
use Time::HiRes qw(usleep nanosleep);
use DateTime::HiRes;
use Scalar::Util::Numeric;

# Set the total size of an object 
my $ttlSize = 1E3;

# Set the start time
my $startTime = &markTime('START');

# Sleep 12500 µs
usleep(12500);

# Calculate the elapsed time
my $runTime =  &diffTime($startTime);

# Calculate the rate in Bytes per second
my $rate =  $ttlSize/$runTime;

# Print it
print "RATE: $rate Bytes/s\n";

# Routine to set a timestamp 
sub markTime () {
    my $prefix =  shift;
    $prefix ='NOW' if not $prefix;
    my $now =  DateTime::HiRes->now;
    print"MARK.$prefix: ",$now->strftime( '%Y-%m-%d-%H-%M-%S.%N' ),"\n";
    return $now;
}

# Routine calc the difference
sub diffTime () {
    my $start = shift;
    # Calculate the duration
    my $now  =  &markTime('DIFF');
    my $dur  =  $now - $start;
    my $nano =  $dur->in_units('nanoseconds');

    # Original dateTime duration is integer
    print "DT.DUARTION: ",$nano," ns  IS.INT: ",
        Scalar::Util::Numeric::isint($nano),"\n";

    # Calc values for mano, micro, milli and seconds
    my $math_ns  = sprintf("%e",$nano);
    my $math_mus = sprintf("%e",$math_ns/1.0E3);
    my $math_ms  = sprintf("%e",$math_ns/1.0E6);
    my $math_s   = sprintf("%e",$math_ns/1.0E9);
    # Show the calculations
    print"MATH: ",$math_ns," ns\n";
    print"MATH: ",$math_mus," µs\n";
    print"MATH: ",$math_ms," ms\n";
    print"MATH: ",$math_s," s\n";
    # Check if the stuff ist float
    print "TIME.SECS: ", $math_ns*1.0E-9,
        " TIME.NANO: "   ,$math_ns,
        " IS.FLOAT: "  ,Scalar::Util::Numeric::isfloat($math_ns),"\n";
    return $math_s;
}

结果显示,runtime 的亚秒浮点值除法为 "cutted",传输速率变为 INF,尽管使用了浮点除法运算。

MARK.START: 2019-12-12-10-12-33.954870000
MARK.DIFF: 2019-12-12-10-12-33.968422000
DT.DUARTION: 13552000 ns  IS.INT: 1
MATH: 1.355200e+07 ns
MATH: 1.355200e+04 µs
MATH: 1.300000e+01 ms
MATH: 0.000000e+00 s
TIME.SECS: 0 TIME.NANO: 1.355200e+07 IS.FLOAT: 1
RATE: inf Bytes/s

我该怎么做才能为 $runtime 变量获得正确的值 0.013552?

转储值:

---NANO---
SV = PVNV(0x8029cf9f0) at 0x8031c6408
  REFCNT = 1
  FLAGS = (IOK,NOK,POK,IsCOW,pIOK,pNOK,pPOK)
  IV = 13574000
  NV = 13574000
  PV = 0x8031ab5d0 "13574000"[=12=]
  CUR = 8
  LEN = 10
  COW_REFCNT = 1

---MATH_NS---
SV = PV(0x802cb8900) at 0x8031c64e0
  REFCNT = 1
  FLAGS = (POK,IsCOW,pPOK)
  PV = 0x8031ab0f0 "1.357400e+07"[=12=]
  CUR = 12
  LEN = 16
  COW_REFCNT = 2

---MATH_NS/1E9---
SV = PV(0x802d18b50) at 0x802f19bb8
  REFCNT = 1
  FLAGS = (TEMP,ROK)
  RV = 0x8031d97c8
  SV = PVHV(0x800a8a560) at 0x8031d97c8
    REFCNT = 2
    FLAGS = (OBJECT,SHAREKEYS)
    STASH = 0x8016910d8 "Math::BigInt"
    ARRAY = 0x80323cdc0  (0:7, 2:1)
    hash quality = 62.5%
    KEYS = 2
    FILL = 1
    MAX = 7
    Elt "value" HASH = 0x7025df17
    SV = IV(0x803162b60) at 0x803162b70
      REFCNT = 1
      FLAGS = (ROK)
      RV = 0x801acca08
      SV = PVAV(0x800a894d8) at 0x801acca08
        REFCNT = 1
        FLAGS = (OBJECT)
        STASH = 0x801692e70 "Math::BigInt::Calc"
        ARRAY = 0x802d3bb18
        FILL = 0
        MAX = 0
        FLAGS = (REAL)
        Elt No. 0
        SV = IV(0x800b19ae8) at 0x800b19af8
          REFCNT = 1
          FLAGS = (IOK,pIOK)
          IV = 0
    Elt "sign" HASH = 0xc9f40697
    SV = PV(0x802cb8aa0) at 0x8031c6078
      REFCNT = 1
      FLAGS = (POK,IsCOW,pPOK)
      PV = 0x8017af0f0 "+"[=12=]
      CUR = 1
      LEN = 10
      COW_REFCNT = 1
  PV = 0x8031d97c8 ""
  CUR = 0
  LEN = 0

使用 Math::BigFloat

修正 diffTime

所以我算出来了,在程序中use bigint;(现在在例子中调用)是麻烦制造者,会对所有部门产生影响。解决方法是:

sub diffTimeBigFloat () {
    my $start = shift;
    # Calculate the duration
    my $now  =  &markTime('DIFF');
    my $dur  =  $now->subtract_datetime_absolute($start);
    my $nano =  $dur->in_units('nanoseconds');
    # Original dateTime Duration
    print "DT.DUARTION: ",$nano," ns  IS.INT: ",
        Scalar::Util::Numeric::isint($nano),"\n";

    # Calc values for mano, micro, milli and seconds
    my $math_s  =  Math::BigFloat->new(sprintf("%sE-9",$nano));
    print"MATH: ",$math_s," s\n";
    print "TIME.SECS: ", $math_s, 
          " TIME.NANO: ", $math_s*1E9,
          " IS.FLOAT: " ,Scalar::Util::Numeric::isfloat($math_s),"\n";
    return $math_s;
}

use bigint; 实际上导致 1.0E9 被替换为 Math::BigInt->new(1.0E9)

此外,当至少一个操作数是 Math::BigInt 对象时,Math::BigInt 会覆盖除法。覆盖导致结果成为 Math::BigInt 对象。

因此,

use bigint; 可以在远处产生很大的影响。因此,我个人觉得 use bigint; 太神奇了。我宁愿在适当的地方使用 Math::BigInt->new(...) 而不是让 use bigint; 将我所有的数字常量转换为 Math::BigInt 对象。

但是你走了另一条路。您不仅在使用 use bigint;,而且在您显然不想要它的效果时也在使用它。至少,在 diffTime 内部使用 no bigint;,这样子中的数字常量不会自动包装到 Math::BigInt 对象中。

use bigint; 

# bigint isn't used by DateTime::Duration
my $nano = do { no bigint; 13552000 };

say $nano/1E9;                        # 0
no bigint;
say $nano/1E9;                        # 0.013552

但是就像我上面说的,我建议完全避免 use bigint; 以支持在适当的情况下显式创建 Math::BigInt 对象,这根本不是你的情况。

my $nano = 13552000;
say $nano/1E9;                        # 0.013552

如果你因为什么原因需要处理Math::BigInt对象,需要对其进行浮点运算,解决方法是将Math::BigInt对象转换成普通标量或Math::BigFloat(或Math::BigRat)对象。

可以使用 $x->numify 将 Math::BigInt 对象转换为普通标量。如果值太大,可能会溢出。

可以使用 Math::BigFloat->new($x).

将 Math::BigInt 对象转换为 Math::BigFloat 对象
use bigint; 

use Math::BigFloat;

# bigint isn't used by DateTime::Duration
my $nano = do { no bigint; 13552000 };

say $nano/1E9;                        # 0
say $nano/(1E9->numify);              # 0.013552
say $nano/Math::BigFloat->new(1E9);   # 0.013552

不要像以前那样使用 sprintf。这将导致不必要的精度损失。