Как заставить long double в Perl

Я теряю точность при выполнении арифметических операций или попытке напечатать (отладить) такие большие числа: 1234567890.123456789

Я думаю, что мои проблемы связаны с $d (результат арифметики) и форматированной печатью $e. Как я могу заставить длинные удвоения? Моя версия Perl (5.8.4 на SUN) говорит, что это возможно. В sprintf есть параметр размера для длинных удвоений (q, L или ll), но я не понял, как его использовать, и не знаю, будет ли он работать с printf.

Редактировать: я добавил BigFloat, который работает! Но я все равно хотел бы форсировать длинные удвоения.

Попробуйте сложить 1234567890 + 0,123456789 и вычесть 1234567890 - 0,123456789.

use Config;
use Math::BigFloat;
$a = 1234567890;
$b = 123456789;
$c = $b/1e9;                   # 0.123456789
$d = $a + $c;                  # not enough precision (32-bit or double?)
$e = sprintf("%d.%.9d",$a,$b); # combine as strings
$f = 1234567890.123456789;     # for reference (not enough precision)

# Use BigFloat to bypass lack of longdbl
$aBig = Math::BigFloat->new("$a");
$dSum = $aBig->fadd("$c");         # $dSum = $a + $c
$aBig = Math::BigFloat->new("$a"); # <-- Need a new one for every operation?
$dDif = $aBig->fsub(abs("$c"));    # $dDif = $a - $c

print "a $a\n";             # 1234567890  
print "c $c\n";             # 0.123456789
print "d=a+c $d\n";         # 1234567890.12346  <-- **Problem**
print "dSum=a+c $dSum\n";   # 1234567890.123456789  <-- Solution
print "dDif=a-c $dDif\n";   # 1234567890.876543211  <-- Solution
print "e $e\n";             # 1234567890.123456789
print "f $f\n";             # 1234567890.12346  <-- double, 52-bit, not longdbl? 
printf ("printf    e 20.9f %20.9f\n",$e);    # 1234567890.123456717 <-- **Problem**
printf ("printf dSum 20.9f %20.9f\n",$dSum); # 1234567890.123456717 <-- **Problem**
printf ("printf dSum 20s %20s\n",$dSum);     # 1234567890.123456789 
printf ("printf dDif 20.9f %20.9f\n",$dDif); # 1234567890.876543283 <-- **Problem**
printf ("printf dDif 20s %20s\n",$dDif);     # 1234567890.876543211 

print "uselongdouble $Config{uselongdouble}\n"; # empty. No long doubles by default
print "d_longdbl $Config{d_longdbl}\n";         # "define". Supports long doubles
print "size double longdbl $Config{doublesize} $Config{longdblsize}\n"; # Ans 8 16

Я также использовал этот код, чтобы попытаться понять типы, но это не очень помогло. Кто-нибудь использовал его для объяснения подобных проблем?

use Devel::Peek 'Dump';

Dump ($dSum); # Wow, it's complicated
Dump ($f);

person dnvrdave    schedule 13.01.2015    source источник
comment
uselongdouble сообщит, был ли ваш Perl скомпилирован с использованием длинных двойников или нет; если это не так, вам нужно перекомпилировать perl. но вместо этого может быть полезен Math::BigFloat.   -  person ysth    schedule 14.01.2015
comment
Связано: stackoverflow.com/q/3722679/827263   -  person Keith Thompson    schedule 14.01.2015
comment
@ysth: под которым, я полагаю, вы подразумеваете $Config{uselongdouble}, для которого требуется use Config   -  person Borodin    schedule 14.01.2015
comment
правильный. Что касается вывода Dump(), то есть только один собственный тип Perl с плавающей запятой, NV. обычно это двойник, но $Config{uselongdouble} указывает, что perl был скомпилирован так, чтобы он был длинным двойником. обратите внимание, что даже long double будет иметь достаточную точность для хранения вашего 1234567890.123456789, поэтому вам все равно придется взглянуть на Math::BigFloat   -  person ysth    schedule 14.01.2015
comment
Я добавил BigFloat, и он работает. Спасибо! Я также добавил $f, что указывает на то, что я использую только double. Как я могу заставить long double?   -  person dnvrdave    schedule 15.01.2015
comment
Вероятно, вы захотите обновить этот Perl. Вы можете получить чуть менее устаревшую версию в виде пакета OpenSolaris или скомпилировать ее. легко с помощью perlbrew. Было множество обновлений Math::BigFloat с версии 1.44, поставляемой с 5.8. .4   -  person Schwern    schedule 15.01.2015
comment
Это объясняет, почему printf не работал. stackoverflow.com/questions/5426791/   -  person dnvrdave    schedule 17.01.2015


Ответы (2)


bignum перегрузит все операторы в текущей области, чтобы использовать целые числа произвольной точности и операции с плавающей запятой.

use bignum;

my $f = 123456789.123456789;
print "$f\n";         # 123456789.123456789
print $f + $f, "\n";  # 246913578.246913578

За кулисами bignum превращает все числовые константы в объекты Math::BigInt и Math::BigNum по мере необходимости.

Важно отметить, что bignum имеет лексическую область видимости и не влияет на всю программу. Например...

{
    use bignum;
    $f = 123456789.123456789;  # $f is a Math::BigNum object
}

$g = 123456789.123456789;      # $g is a regular NV

print "$f\n";     # 123456789.123456789
print "$g\n";     # 123456789.123457

# This will use Math::BigNum's addition method, but $g has already lost precision.
print $f + $g, "\n";  # 246913578.246913789

Вы можете повысить производительность, используя Math::BigInt::GMP для использования библиотеки арифметики GNU Multiple Precision для некоторых операций. Вы должны сначала установить этот модуль, используя обычный процесс установки CPAN (вам не нужен GMP). Затем скажите bignum использовать GMP.

use bignum lib => "GMP";
person Schwern    schedule 15.01.2015
comment
«использовать бигнум»; работал отлично! Я обнаружил, что Math::BigFloat с ним не работает, но сейчас он мне и не нужен. Спасибо! - person dnvrdave; 17.01.2015
comment
Учтите, что bignum в 100 раз медленнее родного perl. - person cnd; 28.07.2018

Perl имеет один размер числа с плавающей запятой, и он называется NV. Размер NV определяется при сборке Perl.

$ perl -V:nvsize
nvsize='8';

Эта информация также доступна в программе Perl через модуль Config.

$ perl -MConfig -E'say $Config{nvsize}'
8

Размер NV нельзя изменить после сборки Perl.

Вы можете заставить Perl быть собранным для использования long double float следующим образом при сборке Perl:

sh Configure -Duselongdouble ...
  -or-
perlbrew install -Duselongdouble ...
  -or-
perlbrew install --ld ...

Если вы не хотите перестраивать свой perl или делать новый, вам нужно будет использовать модуль. Я рекомендую Math::LongDouble, так как он обеспечивает доступ к собственным long double числам с плавающей запятой, и это так максимально прозрачно.

Другой вариант — использовать библиотеку произвольной точности, такую ​​как Math::BigFloat, но это будет медленнее, чем необходимо, если все, что вам нужно, это long double.

person ikegami    schedule 15.01.2015