Linux Perl - Convertion of Hex Value to Decimal - linux

I am developing a script that convert collected HEX ( I don't know the Bit format of them) values to Decimal Values.
One of the example is the hex value: fef306da
If I convert it, I receive 4277339866.
Website where I found the expected value (Decimal from signed 2's complement:):
https://www.rapidtables.com/convert/number/hex-to-decimal.html
Do you guys have a solution how can I convert hex fef306da to decimal -17627430.
Note: I get wrong value conversion when I convert hex that have (-)negative sign when decimal.
Thanks all!

Look at pack and use modifiers for unsigned and signed values.
my $hex_value = "fef306da";
my $output_num = unpack('l', pack('L', hex($hex_value)));
print $output_num; ## -17627430
Perform a test on each hex value to determine if it is a 16bit or 32bit value.
Then use the correct modifier with pack for long or short values.

it seems that you expect your decimal to be 32bit signed integer, but HEX($n) returns a 64bit one
so you may try repack it
perl -e 'print unpack "l", pack "L", hex( "fef306da" )'

If you interested in binary conversion then check following code (fef306da is 32bit number)
use strict;
use warnings;
use feature 'say';
my $input = 'fef306da';
my $hex = hex($input);
my $dec;
if( $hex & 0x80000000 ) {
$dec = -1 * ((~$hex & 0x7fffffff)+1);
} else {
$dec = $data;
}
say $dec;
Output
-17627430
Tip: Two's complement

You could use pack
my $hex = "fef306da";
my $num = hex($hex);
$num = unpack("l", pack("L", $num));
say $num; # -17627430
or
my $hex = "fef306da";
$hex = substr("00000000$hex", -8); # Pad to 8 chars
my $num = unpack("l>", pack("H8", $hex));
say $num; # -17627430
But simple arithmetic will do.
my $hex = "fef306da";
my $num = hex($hex);
$num -= 0x1_0000_0000 if $num >= 0x8000_0000;
say $num; # -17627430

Related

How to convert a string to an integer in PowerShell?

I am trying to create a string from a variable in PowerShell. I want to display numbers as strings and not as integers.
I have tried putting [str] before my variable, like for string to integer ([int]), but that gave me an error. I also searched my issue but no one has asked a question like this.
The code in question is the "$jstr" line.
for ($j = 0; $j -lt 1000; $j++)
{
$jstr = [str]$j
$dot = "."
$num = $jstr + $dot
Write-Host $num, Get-Random -SetSeed $j
}
I want the output to be something like "1. Random number", with 1 being the seed number and Random number being the number mapped to that seed,
A couple ways...
If you have a number expressed as a string, you can call string's toInt32() method:
You can assign it to a variable with the type identifier:
However, this ONLY works if the string is ALL numbers (no non-numeric/non-hex characters).
If you want to go the other way (express an integer as a string), use the toString() method:
$integer.toString()
Like this?
for ($j = 0; $j -lt 6; $j++)
{
$rand = Get-Random -SetSeed $j
"$j.$rand"
}
0.1866861594
1.42389573
2.365335408
3.688215708
4.1011161543
5.1334173171
here's one way to get a list with the seed on the left, a dot, and then the random number. it uses the -f string format operator to place the numbers and to align the numbers in the allotted space. [grin]
$Start = 0
$End = 10
$SLength = ([string]$Count).Length
$RMin = 0
$RMax = 1e3
$RLength = ([string]$RMax).Length
foreach ($Number in $Start..$End)
{
"{0,$SLength}. {1,$RLength}" -f $Number, (Get-Random -SetSeed $Number -InputObject ($RMin..$RMax))
}
output ...
0. 607
1. 602
2. 997
3. 636
4. 143
5. 839
6. 6
7. 404
8. 536
9. 394
10. 124

convert 0 into string

i'm working on a script in perl.
This script read a DB and generate config file for other devices.
I have a problem with "0".
From my database, i get a 0 (int) and i want this 0 become a "0" in the config file. When i get any other value (1,2,3, etc), the script generate ("1","2","3", etc). But the 0 become an empty string "".
I know, for perl:
- undef
- 0
- ""
- "0"
are false.
How can i convert a 0 to "0" ? I try qw,qq,sprintf, $x = $x || 0, and many many more solutions.
I juste want to make a explicit conversion instead of an implicite conversion.
Thank you for your help.
If you think you have zero, but the program thinks you have an empty string, you are probably dealing with a dualvar. A dualvar is a scalar that contains both a string and a number. Perl usually returns a dualvar when it needs to return false.
For example,
$ perl -we'my $x = 0; my $y = $x + 1; CORE::say "x=$x"'
x=0
$ perl -we'my $x = ""; my $y = $x + 1; CORE::say "x=$x"'
Argument "" isn't numeric in addition (+) at -e line 1.
x=
$ perl -we'my $x = !1; my $y = $x + 1; CORE::say "x=$x"'
x=
As you can see, the value returned by !1 acts as zero when used as a number, and acts as an empty string when used as a string.
To convert this dualvar into a number (leaving other numbers unchanged), you can use the following:
$x ||= 0;

How to move the decimal point N places to the left efficiently?

I have a bunch of decimal numbers (as strings) which I receive from an API. I need to 'unscale' them, i.e. divide them by some power of 10. This seems a simple task for integers, but I have decimals with no guaranteed range. So, basically I need a function that works like this:
move_point "12.34" 1; # "1.234"
move_point "12.34" 5; # "0.0001234"
I'd rather not use floats to avoid any rounding errors.
This is a bit verbose, but should do the trick:
sub move_point {
my ($n, $places) = #_;
die 'negative number of places' if $places < 0;
return $n if $places == 0;
my ($i, $f) = split /\./, $n; # split to integer/fractional parts
$places += length($f);
$n = sprintf "%0*s", $places+1, $i.$f; # left pad with enough zeroes
substr($n, -$places, 0, '.'); # insert the decimal point
return $n;
}
Demo:
my $n = "12.34";
for my $p (0..5) {
printf "%d %s\n", $p, move_point($n, $p);
}
0 12.34
1 1.234
2 0.1234
3 0.01234
4 0.001234
5 0.0001234
Unless your data has contains values with significantly more digits than you have shown then a floating-point value has more than enough accuracy for your purpose. Perl can reliably reproduce up to 16-digit values
use strict;
use warnings 'all';
use feature 'say';
say move_point("12.34", 1); # "1.234"
say move_point("12.34", 5); # "0.0001234"
say move_point("1234", 12);
say move_point("123400", -9);
sub move_point {
my ($v, $n) = #_;
my $dp = $v =~ /\.([^.]*)\z/ ? length $1 : 0;
$dp += $n;
$v /= 10**$n;
sprintf '%.*f', $dp < 0 ? 0 : $dp, $v;
}
output
1.234
0.0001234
0.000000001234
123400000000000
Update
If the limits of standard floating-point numbers are actually insuffcient for you then the core Math::BigFloat will do what you need
This program shows a number with sixteen digits of accuracy, multiplied by everything from 10E-20 to 10E20
use strict;
use warnings 'all';
use feature 'say';
use Math::BigFloat;
for ( -20 .. 20 ) {
say move_point('1234567890.1234567890', $_);
}
sub move_point {
my ($v, $n) = #_;
$v = Math::BigFloat->new($v);
# Build 10**$n
my $mul = Math::BigFloat->new(10)->bpow($n);
# Count new decimal places
my $dp = $v =~ /\.([^.]*)\z/ ? length $1 : 0;
$dp += $n;
$v->bdiv($mul);
$v->bfround(-$dp) if $dp >= 0;
$v->bstr;
}
output
123456789012345678900000000000
12345678901234567890000000000
1234567890123456789000000000
123456789012345678900000000
12345678901234567890000000
1234567890123456789000000
123456789012345678900000
12345678901234567890000
1234567890123456789000
123456789012345678900
12345678901234567890
1234567890123456789
123456789012345678.9
12345678901234567.89
1234567890123456.789
123456789012345.6789
12345678901234.56789
1234567890123.456789
123456789012.3456789
12345678901.23456789
1234567890.123456789
123456789.0123456789
12345678.90123456789
1234567.890123456789
123456.7890123456789
12345.67890123456789
1234.567890123456789
123.4567890123456789
12.34567890123456789
1.234567890123456789
0.1234567890123456789
0.01234567890123456789
0.001234567890123456789
0.0001234567890123456789
0.00001234567890123456789
0.000001234567890123456789
0.0000001234567890123456789
0.00000001234567890123456789
0.000000001234567890123456789
0.0000000001234567890123456789
0.00000000001234567890123456789

How to tell apart numeric scalars and string scalars in Perl?

Perl usually converts numeric to string values and vice versa transparently. Yet there must be something which allows e.g. Data::Dumper to discriminate between both, as in this example:
use Data::Dumper;
print Dumper('1', 1);
# output:
$VAR1 = '1';
$VAR2 = 1;
Is there a Perl function which allows me to discriminate in a similar way whether a scalar's value is stored as number or as string?
A scalar has a number of different fields. When using Perl 5.8 or higher, Data::Dumper inspects if there's anything in the IV (integer value) field. Specifically, it uses something similar to the following:
use B qw( svref_2object SVf_IOK );
sub create_data_dumper_literal {
my ($x) = #_; # This copying is important as it "resolves" magic.
return "undef" if !defined($x);
my $sv = svref_2object(\$x);
my $iok = $sv->FLAGS & SVf_IOK;
return "$x" if $iok;
$x =~ s/(['\\])/\\$1/g;
return "'$x'";
}
Checks:
Signed integer (IV): ($sv->FLAGS & SVf_IOK) && !($sv->FLAGS & SVf_IVisUV)
Unsigned integer (IV): ($sv->FLAGS & SVf_IOK) && ($sv->FLAGS & SVf_IVisUV)
Floating-point number (NV): $sv->FLAGS & SVf_NOK
Downgraded string (PV): ($sv->FLAGS & SVf_POK) && !($sv->FLAGS & SVf_UTF8)
Upgraded string (PV): ($sv->FLAGS & SVf_POK) && ($sv->FLAGS & SVf_UTF8)
You could use similar tricks. But keep in mind,
It'll be very hard to stringify floating point numbers without loss.
You need to properly escape certain bytes (e.g. NUL) in string literals.
A scalar can have more than one value stored in it. For example, !!0 contains a string (the empty string), a floating point number (0) and a signed integer (0). As you can see, the different values aren't even always equivalent. For a more dramatic example, check out the following:
$ perl -E'open($fh, "non-existent"); say for 0+$!, "".$!;'
2
No such file or directory
It is more complicated. Perl changes the internal representation of a variable depending on the context the variable is used in:
perl -MDevel::Peek -e '
$x = 1; print Dump $x;
$x eq "a"; print Dump $x;
$x .= q(); print Dump $x;
'
SV = IV(0x794c68) at 0x794c78
REFCNT = 1
FLAGS = (IOK,pIOK)
IV = 1
SV = PVIV(0x7800b8) at 0x794c78
REFCNT = 1
FLAGS = (IOK,POK,pIOK,pPOK)
IV = 1
PV = 0x785320 "1"\0
CUR = 1
LEN = 16
SV = PVIV(0x7800b8) at 0x794c78
REFCNT = 1
FLAGS = (POK,pPOK)
IV = 1
PV = 0x785320 "1"\0
CUR = 1
LEN = 16
There's no way to find this out using pure perl. Data::Dumper uses a C library to achieve it. If forced to use Perl it doesn't discriminate strings from numbers if they look like decimal numbers.
use Data::Dumper;
$Data::Dumper::Useperl = 1;
print Dumper(['1',1])."\n";
#output
$VAR1 = [
1,
1
];
Based on your comment that this is to determine whether quoting is needed for an SQL statement, I would say that the correct solution is to use placeholders, which are described in the DBI documentation.
As a rule, you should not interpolate variables directly in your query string.
One simple solution that wasn't mentioned was Scalar::Util's looks_like_number. Scalar::Util is a core module since 5.7.3 and looks_like_number uses the perlapi to determine if the scalar is numeric.
The autobox::universal module, which comes with autobox, provides a type function which can be used for this purpose:
use autobox::universal qw(type);
say type("42"); # STRING
say type(42); # INTEGER
say type(42.0); # FLOAT
say type(undef); # UNDEF
When a variable is used as a number, that causes the variable to be presumed numeric in subsequent contexts. However, the reverse isn't exactly true, as this example shows:
use Data::Dumper;
my $foo = '1';
print Dumper $foo; #character
my $bar = $foo + 0;
print Dumper $foo; #numeric
$bar = $foo . ' ';
print Dumper $foo; #still numeric!
$foo = $foo . '';
print Dumper $foo; #character
One might expect the third operation to put $foo back in a string context (reversing $foo + 0), but it does not.
If you want to check whether something is a number, the standard way is to use a regex. What you check for varies based on what kind of number you want:
if ($foo =~ /^\d+$/) { print "positive integer" }
if ($foo =~ /^-?\d+$/) { print "integer" }
if ($foo =~ /^\d+\.\d+$/) { print "Decimal" }
And so on.
It is not generally useful to check how something is stored internally--you typically don't need to worry about this. However, if you want to duplicate what Dumper is doing here, that's no problem:
if ((Dumper $foo) =~ /'/) {print "character";}
If the output of Dumper contains a single quote, that means it is showing a variable that is represented in string form.
You might want to try Params::Util::_NUMBER:
use Params::Util qw<_NUMBER>;
unless ( _NUMBER( $scalar ) or $scalar =~ /^'.*'$/ ) {
$scalar =~ s/'/''/g;
$scalar = "'$scalar'";
}
The following function returns true (1) if the input is numeric and false ("") if it is a string. The function also returns true (-1) if the input is a numeric Inf or NaN. Similar code can be found in the JSON::PP module.
sub is_numeric {
my $value = shift;
no warnings 'numeric';
# string & "" -> ""
# number & "" -> 0 (with warning)
# nan and inf can detect as numbers, so check with * 0
return unless length((my $dummy = "") & $value);
return unless 0 + $value eq $value;
return 1 if $value * 0 == 0; # finite number
return -1; # inf or nan
}
I don't think there is perl function to find type of value. One can find type of DS(scalar,array,hash). Can use regex to find type of value.

Convert Memory Size (Human readable) into Actual Number (bytes) in Perl

Is there an actual package in CPAN to convert such string:
my $string = "54.4M"
my $string2 = "3.2G"
into the actual number in bytes:
54,400,000
3,200,000,000
And vice versa.
In principle what I want to do at the end is to sum out all the memory size.
To get the exact output you asked for, use Number::FormatEng and Number::Format:
use strict;
use warnings;
use Number::FormatEng qw(:all);
use Number::Format qw(:subs);
my $string = "54.4M" ;
my $string2 = "3.2G" ;
print format_number(unformat_pref($string)) , "\n";
print format_number(unformat_pref($string2)) , "\n";
__END__
54,400,000
3,200,000,000
By the way, only unformat_pref is needed if you are going to perform calculations with the result.
Since Number::FormatEng was intended for engineering notation conversion (not for bytes), its prefix is case-sensitive. If you want to use it for kilobytes, you must use lower case k.
Number::Format will convert these strings into actual bytes (kinda, almost).
use Number::Format qw(:subs);
my $string = "54.4M" ;
my $string2 = "3.2G" ;
print round(unformat_number($string) , 0), "\n";
print round(unformat_number($string2), 0), "\n";
__END__
57042534
3435973837
The reason I said "kinda, almost" is that Number::Format treats 1K as being equal to 1024 bytes, not 1000 bytes. That's probably why it gives a weird-looking result (with fractional bytes), unless it is rounded.
For your first problem, I did not find a CPAN package, but this code snippet might do:
sub convert_human_size {
my $size = shift;
my #suffixes = ('', qw(k m g));
for my $index (0..$#suffixes) {
my $suffix = $suffixes[$index];
if ( $size =~ /^([\d.]+)$suffix\z/i ) {
return int($1 * (1024 ** $index));
}
}
# No match
die "Didn't understand human-readable file size '$size'"; # or croak
}
Wrap the number through Number::Format's format_number function if you'd like pretty semi-colons (e.g. "5,124" instead of "5124")
CPAN solves the second part of your problem:
Number::Bytes::Human
For example:
use Number::Bytes::Human qw(format_bytes);
$size = format_bytes(54_400_000);
You may provide an optional bs => 1000 parameter to change the base of the conversion to 1000 instead of 1024.
This should get you started. You could add other factors, like kilobytes ("K") on your own, as well as formatting of output (comma separators, for example):
#!/usr/bin/perl -w
use strict;
use POSIX qw(floor);
my $string = "54.4M";
if ( $string =~ m/(\d+)?.(\d+)([M|G])/ ) {
my $mantissa = "$1.$2";
if ( $3 eq "M" ) {
$mantissa *= (2 ** 20);
}
elsif ( $3 eq "G" ) {
$mantissa *= (2 ** 30);
}
print "$string = ".floor($mantissa)." bytes\n";
}
Output:
54.4M = 57042534 bytes
Basically, to go from strings to numbers, all you need is a hash mapping units to multipliers:
#!/usr/bin/perl
use strict; use warnings;
my $base = 1000;
my %units = (
K => $base,
M => $base ** 2,
G => $base ** 3,
# etc
);
my #strings = qw( 54.4M 3.2G 1K 0.1M .);
my $pattern = join('|', sort keys %units);
my $total;
for my $string ( #strings ) {
while ( $string =~ /(([0-9]*(?:\.[0-9]+)?)($pattern))/g ) {
my $number = $2 * $units{$3};
$total += $number;
printf "%12s = %12.0f\n", $1, $number;;
}
}
printf "Total %.0f bytes\n", $total;
Output:
54.4M = 54400000
3.2G = 3200000000
1K = 1000
0.1M = 100000
Total 3254501000 bytes

Resources