检查字符串化数字是否高于MAX_UINT64的最有效方法?

时间:2017-08-29 16:54:56

标签: string perl numbers

假设我有一个由64位Perl执行的脚本,它带有一个实际上是数字的参数,但当然首先是一个字符串(因为所有命令行参数都是字符串)。

现在,如果该参数的值适合64位无符号整数,则脚本应该对参数执行某些操作;否则,它应该以适当的错误消息中止。

检查该参数(作为字符串,即在数学运算中使用它之前)是否适合64位无符号整数的最有效方法是什么?

我已经想到的:

  1. 我可以进行字符串比较

    我不想那样做,因为在那种情况下我不得不应对整理,Unicode::Collate的文档对于我的小问题看起来有点过分。

    但这只是一种感觉,所以我很感激你的意见或其他意见。

    旁注:我试过这个,它的工作方式与预期一致。但这只是一个快速的测试;我没有玩locales,所以在其他系统上它可能不起作用(虽然我怀疑有一个整理在“1”之前加“2”,但你永远不知道)。

  2. 比较前转换为数字不起作用:

    root@spock:/root/test# perl -e '$i="18446744073709551615"+0; $j="18446744073709551616"+0; print "$i  $j\n"; print(($i < $j) ? "less\n" : "greater or equal\n")'
    18446744073709551615  1.84467440737096e+19
    greater or equal
    

    注意Perl如何打印第二个数字。这是最小的无符号整数,不适合64位,因此Perl将其转换为double。然后,当它以数字方式比较$i$j时,它必须将$i转换为双倍;由于此处涉及的精度损失,$i转换为与$j相同的值,因此比较出错。

  3. 我可以做use bigint;。我试过这个,它表现得像预期的那样。

    但这可能会导致性能急剧下降。据我所知,use bigint;意味着使用各种重型库。

    但这只是一种感觉,所以如果这是要走的路,请告诉我。

  4. 另一个想法(尚未尝试):我可以使用pack()以某种方式从字符串化数字生成字节序列吗?然后我可以检查该字节序列的长度。如果它小于或等于8个字节,则字符串化的数字适合64位无符号整数。

  5. 你会如何解决这个问题?

3 个答案:

答案 0 :(得分:4)

use constant MAX_UINT64 = '18446744073709551615';

my $larger_than_max =
      length($s) > length(MAX_UINT64)
   || length($s) == length(MAX_UINT64) && $s gt MAX_UINT64;

假设输入匹配/^(?:0|[1-9][0-9]*)\z/。调整为喜欢(例如处理前导零或符号)。

答案 1 :(得分:2)

您可以使用一个简单的快捷方式来消除大多数数字。十进制表示中包含19位或更少位数的任何数字都可以容纳64位整数,因此如果包含整数的字符串的长度小于20,则表示良好。

长度大于或等于21的任何字符串都不好。

UINT64_MAX18446744073709551615。因此,有一些带有20位十进制数的数字可以适合64位无符号整数。有些人不能。

此时,使用ge进行简单的字符串比较就足够了,因为无论语言环境如何,阿拉伯数字的排序都是相同的。

$ perl -E "say 'yes' if $ARGV[1] ge $ARGV[0]" 18446744073709551615 18446744073709551616
yes

答案 2 :(得分:0)

为了清楚起见,我假设输入是一串数字。

您要求最有效的方法。如果不了解输入的分布,就无法确定这一点。例如,如果输入是统一的128位整数,则最有效的方法是从以下内容开始:

if (length(@ARGV[0]) > 20) {die "Number too large.\n"}

这处理了99.9999999999%以上的案件。实际上,如果输入在256位整数中是统一的,那么您可能会因为编写简单代码而被原谅:

warn "Number too large.\n";

关于在合理的时间内反复进行一致的测试,您可以考虑使用Damian Conway的Regexp :: Number这样的正则表达式(对于带符号的64位数字,但原理是有效的)。注意,它是真实代码,它处理前导零。

 '0*(?:(?:9(?:[0-1][0-9]{17}'  .
        '|2(?:[0-1][0-9]{16}'  .
        '|2(?:[0-2][0-9]{15}'  .
        '|3(?:[0-2][0-9]{14}'  .
        '|3(?:[0-6][0-9]{13}'  .
        '|7(?:[0-1][0-9]{12}'  .
        '|20(?:[0-2][0-9]{10}' .
        '|3(?:[0-5][0-9]{9}'   .
        '|6(?:[0-7][0-9]{8}'   .
        '|8(?:[0-4][0-9]{7}'   .
        '|5(?:[0-3][0-9]{6}'   .
        '|4(?:[0-6][0-9]{5}'   .
        '|7(?:[0-6][0-9]{4}'   .
        '|7(?:[0-4][0-9]{3}'   .
        '|5(?:[0-7][0-9]{2}'   .
        '|80(?:[0-6])))))))))))))))))' .
  '|[1-8]?[0-9]{0,18})'

与perl的启动时间(甚至是击键)相比,这应该是令人眼花fast乱的。

关于bigint,它执行速度非常快,并且包含一些很酷的优化功能,但是除非您在代码中测试了很多数字,否则上面的内容就足够了。

但是,如果您真的想燃烧橡胶,请查看perl胆量,并使用可以暴露宏SvIOK(SV *)的东西。 (有关更多详细信息,请参见https://metacpan.org/pod/release/KRISHPL/pod2texi-0.1/perlguts.pod#What-is-an-%22IV%22?)