Як визначити, чи має змінна числове значення в Perl?


83

Чи існує простий спосіб у Perl, який дозволить мені визначити, чи є дана змінна числовою? Щось на зразок:

if (is_number($x))
{ ... }

було б ідеально. -wБезумовно, кращою є техніка, яка не видаватиме попереджень під час використання комутатора.

Відповіді:


128

Використання, Scalar::Util::looks_like_number()яке використовує внутрішню функцію Perl C API Look_like_number (), що є, мабуть, найефективнішим способом зробити це. Зауважте, що рядки "inf" та "нескінченність" розглядаються як числа.

Приклад:

#!/usr/bin/perl

use warnings;
use strict;

use Scalar::Util qw(looks_like_number);

my @exprs = qw(1 5.25 0.001 1.3e8 foo bar 1dd inf infinity);

foreach my $expr (@exprs) {
    print "$expr is", looks_like_number($expr) ? '' : ' not', " a number\n";
}

Дає цей результат:

1 is a number
5.25 is a number
0.001 is a number
1.3e8 is a number
foo is not a number
bar is not a number
1dd is not a number
inf is a number
infinity is a number

Дивіться також:


1
І як зазвичай з документами perl, знайти фактичне визначення того, що робить функція, досить складно. Наступні сліди perldoc perlapiговорять нам: Перевірте, чи зміст SV виглядає як число (чи це число). "Inf" та "Infinity" розглядаються як цифри (тому не видаватимуть нечислове попередження), навіть якщо ваш atof () їх не перебирає. Навряд чи можна перевірити специфікацію ...
День

3
Опис у Scalar :: Util - це добре, вигляд_подібного_числа повідомляє, чи ваш вхід - це те, що Perl розглядає як число, що не обов'язково є найкращою відповіддю на це питання. Згадка про atof неактуальна, atof не є частиною CORE :: або POSIX (ви повинні дивитись на strtod, який підвів atof та / is / частина POSIX) і припускаючи, що те, що Perl вважає числом, є дійсним числовим введенням до функцій C, очевидно, дуже неправильно.
MkV

дуже приємна функція :) для undef та нечислових рядків повертає 0, для числових рядків повертає 1, для цілих чисел повертає 4352, а для плаваючих повертає 8704 :) загалом> 0 виявляється число. Я протестував його під Linux.
Znik

1
Мені подобається ця функція загалом, але розглядайте великі ints. 1000000 - це багато нулів для відстеження, благаючи про помилку, але 1 000 000 розглядається як триелементний масив, тому Perl приймає 1_000_000, але Look_like_number () говорить ні. Це мене засмучує.
Dave Jacoby

Примітка: шістнадцяткові рядки, як-от 0x12, не вважаються даними тесту.
Адам Кац,

23

Перевірте модуль CPAN Regexp :: Common . Я думаю, що він робить саме те, що вам потрібно, і обробляє всі крайові випадки (наприклад, реальні числа, наукові позначення тощо). напр

use Regexp::Common;
if ($var =~ /$RE{num}{real}/) { print q{a number}; }

23

Початкове питання полягало в тому, як визначити, чи змінна є числовою, а не якщо вона "має числове значення".

Є кілька операторів, які мають окремі режими роботи для числових та рядкових операндів, де "числовий" означає все, що спочатку було числом або коли-небудь використовувалося в числовому контексті (наприклад $x = "123"; 0+$x, перед додаванням $xє рядком, а потім - вважається числовим).

Один із способів сказати це:

if ( length( do { no warnings "numeric"; $x & "" } ) ) {
    print "$x is numeric\n";
}

Якщо побітова функція ввімкнена, що робить &лише числовий оператор і додає окремий рядковий &.оператор, ви повинні його відключити:

if ( length( do { no if $] >= 5.022, "feature", "bitwise"; no warnings "numeric"; $x & "" } ) ) {
    print "$x is numeric\n";
}

(побітове доступне в perl 5.022 і вище та ввімкнено за замовчуванням, якщо ви use 5.028;або вище.)


Чудово, дякую! Це саме те, що я шукав.
Хуан А. Наварро,

Якщо я упакую вашу підпрограму в підпункт, я отримую дивну поведінку, оскільки вона правильно виявляє нечислові значення, доки я не спробую перше числове значення, яке також правильно визнається як істинне, але потім, все інше звідти це також правда. Однак, коли я розміщую евал навколо довжини (...) частини, він працює нормально весь час. Будь-яка ідея, чого мені не вистачало? sub numeric { $obj = shift; no warnings "numeric"; return eval('length($obj & "")'); }
йогібімбі

@yogibimbi: ви повторно використовуєте ту саму змінну $ obj кожного разу; спробуй my $obj = shift;. Чому Евал?
ysth

ой, погано, я використовував my $obj = shift, звичайно, просто неправильно переніс його зі свого коду до коментаря, трохи відредагував. Однак sub numeric { my $obj = shift; no warnings "numeric"; return length($obj & ""); }видає ту саму помилку. Звичайно, наявність підпільної глобальної змінної може пояснити поведінку, це саме те, чого я очікував би у такому випадку, але, на жаль, це не так просто. Крім того, це було б спіймано strict& warnings. Я спробував eval у досить відчайдушній спробі позбутися помилки, і це спрацювало. Немає глибших міркувань, лише спроби та помилки.
йогібімбі

Перевірте це: sub numeric { my $obj = shift; no warnings "numeric"; return length($obj & ""); } print numeric("w") . "\n"; #=>0, print numeric("x") . "\n"; #=>0, print numeric("1") . "\n"; #=>0, print numeric(3) . "\n"; #=>1, print numeric("w") . "\n"; #=>1. Якщо розмістити eval ('') навколо довжини, останній відбиток дасть 0, як слід. Піди розберися.
йогібімбі

9

Проста (і, можливо, спрощена) відповідь на запитання полягає в тому, що зміст $xчислової є наступним:

if ($x  eq  $x+0) { .... }

Він робить текстове порівняння оригіналу $xз $xперетвореним у числове значення.


1
Це видасть попередження, якщо ви використовуєте "-w" або "використовуйте попередження;".
Дерек Парк

1
Попередження можна видалити, $x eq (($x+0)."")однак гірша проблема полягає в тому, що за цією функцією "1.0" не є числовим
Eponymous

1
достатньо тестування $ x + 0 ne ''. коли ви напишите 0001, тоді правильний номер буде перевірено як не номер. те саме, коли ви перевірите текстове значення '.05'.
Znik

8

Зазвичай перевірка числа здійснюється за допомогою регулярних виразів. Цей код визначатиме чисельність чисельності, а також перевірятиме невизначені змінні, щоб не видавати попередження:

sub is_integer {
   defined $_[0] && $_[0] =~ /^[+-]?\d+$/;
}

sub is_float {
   defined $_[0] && $_[0] =~ /^[+-]?\d+(\.\d+)?$/;
}

Ось декілька матеріалів для читання, на які слід подивитися.


2
Я думаю, це трохи відхиляється, особливо коли запитувач сказав / просто /. Багато випадків, включаючи наукові позначення, навряд чи є простими. Якщо не використовувати це для модуля, я б не хвилювався про такі деталі. Іноді простота найкраща. Не кладіть шоколадний сироп у корову, щоб зробити шоколадне молоко!
осірісготра

'.7' - це, мабуть, один з найпростіших випадків, який досі пропущений ... краще спробуйте /^[+-]?\d*\.?\d+$/ для float. Мій варіант, враховуючи і наукові позначення, теж: /^[+-]?\d*\.?\d+(?:(?:e|E)\d+)?$/
Аконкагуа,

\d*\.?\d+Частина представляє Redos ризик. Я рекомендую /^[+-]?(?!\.(?!\d)|$)\d*(?:\.\d*)?$/або /^[+-]?(?!\.(?!\d)|$)\d*(?:\.\d*)?(?:(?<=[\d.])e[+-]?\d+)?$/iвключити замість цього наукові позначення ( пояснення та приклади ). Тут використовується подвоєний від’ємний результат пошуку, щоб також запобігти передачі рядків, як .і .e0передачі як числа. Він також використовує позитивний погляд назад, щоб забезпечити eнаступну кількість.
Адам Кац

3

Не ідеально, але ви можете використовувати регулярний вираз:

sub isnumber 
{
    shift =~ /^-?\d+\.?\d*$/;
}

Та сама проблема, що і відповідь andrewrk: пропускає багато навіть простих випадків, наприклад ".7"
Аконкагуа,


2

Трохи більш надійний регулярний вираз можна знайти в Regexp :: Common .

Здається, ви хочете знати, чи вважає Perl змінну числовою. Ось функція, яка затримує це попередження:

sub is_number{
  my $n = shift;
  my $ret = 1;
  $SIG{"__WARN__"} = sub {$ret = 0};
  eval { my $x = $n + 1 };
  return $ret
}

Інший варіант - локально вимкнути попередження:

{
  no warnings "numeric"; # Ignore "isn't numeric" warning
  ...                    # Use a variable that might not be numeric
}

Зверніть увагу, що нечислові змінні будуть мовчки перетворені в 0, що, мабуть, і так і хотілося.


2

rexep не ідеальний ... це:

use Try::Tiny;

sub is_numeric {
  my ($x) = @_;
  my $numeric = 1;
  try {
    use warnings FATAL => qw/numeric/;
    0 + $x;
  }
  catch {
    $numeric = 0;
  };
  return $numeric;
}


1

Мені це здалося цікавим

if ( $value + 0 eq $value) {
    # A number
    push @args, $value;
} else {
    # A string
    push @args, "'$value'";
}

вам потрібно пояснити краще, ви кажете, що вам це цікаво, але чи відповідає це на запитання? Спробуйте пояснити, чому ваша відповідь є рішенням для поставленого питання
Кумар Саураб

Наприклад, моє $ значення 1, $ value + 0 залишається незмінним 1. Порівнюючи з $ value 1 дорівнює 1. Якщо значення $ є рядком, скажіть "swadhi", тоді $ value + 0 стає ascii значенням рядка "swadhi" + 0 = якесь інше число.
Свадхікар

0

Особисто я думаю, що шлях - це покладатися на внутрішній контекст Perl, щоб зробити рішення стійким до куль. Хороший регулярний вираз може відповідати всім дійсним числовим значенням і жодному з нечислових (або навпаки), але оскільки існує спосіб використання тієї самої логіки, яку використовує інтерпретатор, слід безпечніше покладатися на це безпосередньо.

Оскільки я, як правило, запускаю свої сценарії -w, мені довелося поєднати ідею порівняння результату "значення плюс нуль" з початковим значенням із no warningsзаснованим підходом @ysth:

do { 
    no warnings "numeric";
    if ($x + 0 ne $x) { return "not numeric"; } else { return "numeric"; }
}


-1

якщо (визначено $ x && $ x! ~ m / \ D /) {} або $ x = 0 якщо! $ х; якщо ($ x! ~ м / \ D /) {}

Це невелика варіація відповіді Вікея, але дозвольте пояснити мої міркування щодо зміни.

Виконання регулярного виразу з невизначеним значенням спричинить викид помилки та змусить код вийти у багатьох, якщо не в більшості середовищах. Тестування, чи визначено значення, або встановлення типового випадку, як це робив я в альтернативному прикладі перед запуском виразу, як мінімум збереже журнал помилок.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.