X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=pod%2Fperlfaq4.pod;h=1c06b0b2c84b8ec5fbe4f367eceac4f013dcee35;hb=e851c10581f8b45e99ba164a13a07381402b7e64;hp=38a0e91328e5137ab1afcb8a7639addb024df960;hpb=e573f90328e9db84c5405db01c52908bfac9286d;p=p5sagit%2Fp5-mst-13.2.git diff --git a/pod/perlfaq4.pod b/pod/perlfaq4.pod index 38a0e91..1c06b0b 100644 --- a/pod/perlfaq4.pod +++ b/pod/perlfaq4.pod @@ -1,6 +1,6 @@ =head1 NAME -perlfaq4 - Data Manipulation ($Revision: 7954 $) +perlfaq4 - Data Manipulation =head1 DESCRIPTION @@ -17,7 +17,7 @@ exactly. Some real numbers lose precision in the process. This is a problem with how computers store numbers and affects all computer languages, not just Perl. -L show the gory details of number representations and +L shows the gory details of number representations and conversions. To limit the number of decimal places in your numbers, you can use the @@ -48,35 +48,45 @@ numbers. What you think in the above as 'three' is really more like =head2 Why isn't my octal data interpreted correctly? -Perl only understands octal and hex numbers as such when they occur as -literals in your program. Octal literals in perl must start with a -leading C<0> and hexadecimal literals must start with a leading C<0x>. -If they are read in from somewhere and assigned, no automatic -conversion takes place. You must explicitly use C or C if you -want the values converted to decimal. C interprets hexadecimal (C<0x350>), -octal (C<0350> or even without the leading C<0>, like C<377>) and binary -(C<0b1010>) numbers, while C only converts hexadecimal ones, with -or without a leading C<0x>, such as C<0x255>, C<3A>, C, or C. -The inverse mapping from decimal to octal can be done with either the -<%o> or C<%O> C formats. +(contributed by brian d foy) + +You're probably trying to convert a string to a number, which Perl only +converts as a decimal number. When Perl converts a string to a number, it +ignores leading spaces and zeroes, then assumes the rest of the digits +are in base 10: + + my $string = '0644'; + + print $string + 0; # prints 644 + + print $string + 44; # prints 688, certainly not octal! -This problem shows up most often when people try using C, -C, C, or C, which by widespread tradition -typically take permissions in octal. +This problem usually involves one of the Perl built-ins that has the +same name a unix command that uses octal numbers as arguments on the +command line. In this example, C on the command line knows that +its first argument is octal because that's what it does: - chmod(644, $file); # WRONG - chmod(0644, $file); # right + %prompt> chmod 644 file -Note the mistake in the first line was specifying the decimal literal -C<644>, rather than the intended octal literal C<0644>. The problem can -be seen with: +If you want to use the same literal digits (644) in Perl, you have to tell +Perl to treat them as octal numbers either by prefixing the digits with +a C<0> or using C: - printf("%#o",644); # prints 01204 + chmod( 0644, $file); # right, has leading zero + chmod( oct(644), $file ); # also correct -Surely you had not intended C - did you? If you -want to use numeric literals as arguments to chmod() et al. then please -try to express them as octal constants, that is with a leading zero and -with the following digits restricted to the set C<0..7>. +The problem comes in when you take your numbers from something that Perl +thinks is a string, such as a command line argument in C<@ARGV>: + + chmod( $ARGV[0], $file); # wrong, even if "0644" + + chmod( oct($ARGV[0]), $file ); # correct, treat string as octal + +You can always check the value you're using by printing it in octal +notation to ensure it matches what you think it should be. Print it +in octal and decimal format: + + printf "0%o %d", $number, $number; =head2 Does Perl have a round() function? What about ceil() and floor()? Trig functions? @@ -362,19 +372,18 @@ pseudorandom generator than comes with your operating system, look at =head2 How do I get a random number between X and Y? -To get a random number between two values, you can use the -C builtin to get a random number between 0 and +To get a random number between two values, you can use the C +built-in to get a random number between 0 and 1. From there, you shift +that into the range that you want. -C returns a number such that -C<< 0 <= rand($x) < $x >>. Thus what you want to have perl -figure out is a random number in the range from 0 to the -difference between your I and I. +C returns a number such that C<< 0 <= rand($x) < $x >>. Thus +what you want to have perl figure out is a random number in the range +from 0 to the difference between your I and I. -That is, to get a number between 10 and 15, inclusive, you -want a random number between 0 and 5 that you can then add -to 10. +That is, to get a number between 10 and 15, inclusive, you want a +random number between 0 and 5 that you can then add to 10. - my $number = 10 + int rand( 15-10+1 ); + my $number = 10 + int rand( 15-10+1 ); # ( 10,11,12,13,14, or 15 ) Hence you derive the following simple function to abstract that. It selects a random integer between the two given @@ -479,6 +488,9 @@ Julian day) 31 =head2 How do I find yesterday's date? +X X X X X +X X X X +X (contributed by brian d foy) @@ -491,20 +503,36 @@ give you the same time of day, only the day before. print "Yesterday was $yesterday\n"; -You can also use the C module using its Today_and_Now +You can also use the C module using its C function. use Date::Calc qw( Today_and_Now Add_Delta_DHMS ); my @date_time = Add_Delta_DHMS( Today_and_Now(), -1, 0, 0, 0 ); - print "@date\n"; + print "@date_time\n"; Most people try to use the time rather than the calendar to figure out dates, but that assumes that days are twenty-four hours each. For most people, there are two days a year when they aren't: the switch to and from summer time throws this off. Let the modules do the work. +If you absolutely must do it yourself (or can't use one of the +modules), here's a solution using C, which comes with +Perl: + + # contributed by Gunnar Hjalmarsson + use Time::Local; + my $today = timelocal 0, 0, 12, ( localtime )[3..5]; + my ($d, $m, $y) = ( localtime $today-86400 )[3..5]; + printf "Yesterday: %d-%02d-%02d\n", $y+1900, $m+1, $d; + +In this case, you measure the day starting at noon, and subtract 24 +hours. Even if the length of the calendar day is 23 or 25 hours, +you'll still end up on the previous calendar day, although not at +noon. Since you don't care about the time, the one hour difference +doesn't matter and you end up with the previous date. + =head2 Does Perl have a Year 2000 problem? Is Perl Y2K compliant? Short answer: No, Perl does not have a Year 2000 problem. Yes, Perl is @@ -517,12 +545,11 @@ Can you use your pencil to write a non-Y2K-compliant memo? Of course you can. Is that the pencil's fault? Of course it isn't. The date and time functions supplied with Perl (gmtime and localtime) -supply adequate information to determine the year well beyond 2000 -(2038 is when trouble strikes for 32-bit machines). The year returned -by these functions when used in a list context is the year minus 1900. -For years between 1910 and 1999 this I to be a 2-digit decimal -number. To avoid the year 2000 problem simply do not treat the year as -a 2-digit number. It isn't. +supply adequate information to determine the year well beyond 2000 and +2038. The year returned by these functions when used in a list +context is the year minus 1900. For years between 1910 and 1999 this +I to be a 2-digit decimal number. To avoid the year 2000 +problem simply do not treat the year as a 2-digit number. It isn't. When gmtime() and localtime() are used in scalar context they return a timestamp string that contains a fully-expanded year. For example, @@ -781,44 +808,22 @@ result to a scalar, producing a count of the number of matches. $count = () = $string =~ /-\d+/g; -=head2 How do I capitalize all the words on one line? - -To make the first letter of each word upper case: - - $line =~ s/\b(\w)/\U$1/g; - -This has the strange effect of turning "C" into "C". Sometimes you might want this. Other times you might need a -more thorough solution (Suggested by brian d foy): - - $string =~ s/ ( - (^\w) #at the beginning of the line - | # or - (\s\w) #preceded by whitespace - ) - /\U$1/xg; - - $string =~ s/([\w']+)/\u\L$1/g; - -To make the whole line upper case: - - $line = uc($line); +=head2 Does Perl have a Year 2038 problem? -To force each word to be lower case, with the first letter upper case: +No, all of Perl's built in date and time functions and modules will +work to about 2 billion years before and after 1970. - $line =~ s/(\w+)/\u\L$1/g; +Many systems cannot count time past the year 2038. Older versions of +Perl were dependent on the system to do date calculation and thus +shared their 2038 bug. -You can (and probably should) enable locale awareness of those -characters by placing a C pragma in your program. -See L for endless details on locales. +=head2 How do I capitalize all the words on one line? +X X X X -This is sometimes referred to as putting something into "title -case", but that's not quite accurate. Consider the proper -capitalization of the movie I, for example. +(contributed by brian d foy) -Damian Conway's L module provides some smart -case transformations: +Damian Conway's L handles all of the thinking +for you. use Text::Autoformat; my $x = "Dr. Strangelove or: How I Learned to Stop ". @@ -829,6 +834,30 @@ case transformations: print autoformat($x, { case => $style }), "\n"; } +How do you want to capitalize those words? + + FRED AND BARNEY'S LODGE # all uppercase + Fred And Barney's Lodge # title case + Fred and Barney's Lodge # highlight case + +It's not as easy a problem as it looks. How many words do you think +are in there? Wait for it... wait for it.... If you answered 5 +you're right. Perl words are groups of C<\w+>, but that's not what +you want to capitalize. How is Perl supposed to know not to capitalize +that C after the apostrophe? You could try a regular expression: + + $string =~ s/ ( + (^\w) #at the beginning of the line + | # or + (\s\w) #preceded by whitespace + ) + /\U$1/xg; + + $string =~ s/([\w']+)/\u\L$1/g; + +Now, what if you don't want to capitalize that "and"? Just use +L and get on with the next problem. :) + =head2 How can I split a [character] delimited string except when inside [character]? Several modules can handle this sort of parsing--C, @@ -981,7 +1010,7 @@ appear as part of the data. If you want to work with comma-separated values, don't do this since that format is a bit more complicated. Use one of the modules that -handle that fornat, such as C, C, or +handle that format, such as C, C, or C. If you want to break apart an entire line of fixed columns, you can use @@ -1006,9 +1035,16 @@ C, and C modules. (contributed by brian d foy) -For example, I'll use a string that has two Perl scalar variables -in it. In this example, I want to expand C<$foo> and C<$bar> to -their variable's values. +If you can avoid it, don't, or if you can use a templating system, +such as C or C