+sub from_hex {
+ my $self = shift;
+ my $selfref = ref $self;
+ my $class = $selfref || $self;
+
+ my $str = shift;
+
+ $self = $class -> bzero() unless $selfref;
+
+ if ($str =~ s/
+ ^
+
+ # sign
+ ( [+-]? )
+
+ # optional "hex marker"
+ (?: 0? x )?
+
+ # significand using the hex digits 0..9 and a..f
+ (
+ [0-9a-fA-F]+ (?: _ [0-9a-fA-F]+ )*
+ (?:
+ \.
+ (?: [0-9a-fA-F]+ (?: _ [0-9a-fA-F]+ )* )?
+ )?
+ |
+ \.
+ [0-9a-fA-F]+ (?: _ [0-9a-fA-F]+ )*
+ )
+
+ # exponent (power of 2) using decimal digits
+ (?:
+ [Pp]
+ ( [+-]? )
+ ( \d+ (?: _ \d+ )* )
+ )?
+
+ $
+ //x)
+ {
+ my $s_sign = $1 || '+';
+ my $s_value = $2;
+ my $e_sign = $3 || '+';
+ my $e_value = $4 || '0';
+ $s_value =~ tr/_//d;
+ $e_value =~ tr/_//d;
+
+ # The significand must be multiplied by 2 raised to this exponent.
+
+ my $two_expon = $class -> new($e_value);
+ $two_expon -> bneg() if $e_sign eq '-';
+
+ # If there is a dot in the significand, remove it and adjust the
+ # exponent according to the number of digits in the fraction part of
+ # the significand. Multiply the exponent adjustment value by 4 since
+ # the digits in the significand are in base 16, but the exponent is
+ # only in base 2.
+
+ my $idx = index($s_value, '.');
+ if ($idx >= 0) {
+ substr($s_value, $idx, 1) = '';
+ $two_expon -= $class -> new(CORE::length($s_value))
+ -> bsub($idx)
+ -> bmul("4");
+ }
+
+ $self -> {sign} = $s_sign;
+ $self -> {_m} = $MBI -> _from_hex('0x' . $s_value);
+
+ if ($two_expon > 0) {
+ my $factor = $class -> new("2") -> bpow($two_expon);
+ $self -> bmul($factor);
+ } elsif ($two_expon < 0) {
+ my $factor = $class -> new("0.5") -> bpow(-$two_expon);
+ $self -> bmul($factor);
+ }
+
+ return $self;
+ }
+
+ return $self->bnan();
+}
+