Я использую подпрограмму (stats
) для вычисления статистики для списка чисел. Эти числа могут быть достаточно большими, чтобы потерять точность, если они хранятся как обычные числа perl. Я получаю такие числа как строки в формате JSON. Чтобы декодировать эти строки без потери точности, я использую объект JSON::PP
с allow_nonref
и allow_bignum
. активирован. Я отправляю список таких декодированных чисел в подпрограмму stats
(см. код, показанный ниже). Эта процедура вычисляет некоторую статистику. Затем эта статистика кодируется в JSON и сохраняется в файл.
В большинстве случаев кажется, что процесс работает правильно, но для некоторых входных данных (примеры см. в коде) вычисленное значение статистики среднего и дисперсии либо явно неверно, либо кодировщик кодирует их как строки JSON, либо и то, и другое. Я подозреваю, что это связано с взаимодействием объектов Math::BigInt
и Math::BigFloat
, созданных декодированием JSON, и List::Util::sum0
а>.
Я пытаюсь выяснить, что вызывает это, и способ избежать/исправить это, желательно, не прибегая к большим неосновным модулям. Я готов принять неточный расчет среднего значения и дисперсии, но не совсем неточные результаты или числовые результаты, закодированные в виде строки в JSON.
Скрипт (stats.pl
) для демонстрации проблемы:
use strict;
use warnings;
use Data::Dumper;
$Data::Dumper::Varname = "DUMPED_RAWDATA";
use JSON::PP;
use List::Util;
my $JSON = JSON::PP->new->allow_bignum->utf8->pretty->canonical;
sub stats {
#TODO fix bug about negative variance. AVOID OVERFLOW
#TODO use GMP, XS?
# @_ has decoded numbers (called RAWDATA here)
my $n = scalar @_;
my $sum = List::Util::sum0(@_);
my $mean = $sum / $n;
my $var = List::Util::sum0( map { $_**2 } @_ ) / $n - $mean**2;
my $s = {
n => $n,
sum => $sum,
max => List::Util::max(@_),
min => List::Util::min(@_),
mean => $mean,
variance => $var
};
# DUMP STATE IF SOME ERROR OCCURS
print Dumper( \@_ ),
$JSON->encode( { json_encoded_stats => $s, json_encoded_rawdata => \@_ } )
if ( '"' eq substr( $JSON->encode($var), 0, 1 ) #MEAN ENCODED AS STRING
or '"' eq substr( $JSON->encode($mean), 0, 1 ) #VARIANCE ENCODED AS STRING
or $var < 0 ); #VARIANCE IS NEGATIVE!
$s;
}
my @test = (
[
qw( 919300112739897344 919305709216464896 919305709216464896 985592115567603712 959299136196456448)
],
[qw(479655558 429035600 3281034608 3281034608 2606592908 3490045576)],
[ qw(914426431563644928) x 3142 ]
);
for (@test) {
print "---\n";
stats( map { $JSON->decode($_) } @$_ );
}
Ниже приведен сокращенный вывод perl stats.pl
с проблемами, обозначенными как <---
.
---
$DUMPED_RAWDATA1 = [
'919300112739897344',
'919305709216464896',
'919305709216464896',
'985592115567603712',
'959299136196456448'
];
{
"json_encoded_rawdata" : [
919300112739897344,
919305709216464896,
919305709216464896,
985592115567603712,
959299136196456448
],
"json_encoded_stats" : {
"max" : 985592115567603712,
"mean" : "9.40560556587377e+17", <--- ENCODED AS STRING
"min" : 919300112739897344,
"n" : 5,
"sum" : 4702802782936887296,
"variance" : 7.46903843214008e+32
}
}
---
$DUMPED_RAWDATA1 = [
479655558,
429035600,
3281034608,
3281034608,
2606592908,
3490045576
];
{
"json_encoded_rawdata" : [
479655558,
429035600,
3281034608,
3281034608,
2606592908,
3490045576
],
"json_encoded_stats" : {
"max" : 3490045576,
"mean" : 2261233143,
"min" : 429035600,
"n" : 6,
"sum" : 13567398858,
"variance" : "-1.36775568782523e+18" <--- NEGATIVE VARIANCE, STRING ENCODED
}
}
---
$DUMPED_RAWDATA1 = [
'914426431563644928',
.
.
.
<snip 3140 identical lines>
'914426431563644928'
];
{
"json_encoded_rawdata" : [
914426431563644928,
.
.
.
<snip 3140 identical lines>
914426431563644928
],
"json_encoded_stats" : {
"max" : 914426431563644928,
"mean" : "9.14426431563676e+17", <--- STRING ENCODED
"min" : 914426431563644928,
"n" : 3142,
"sum" : 2.87312784797307e+21,
"variance" : -9.75463826617761e+22 <--- NEGATIVE VARIANCE
}
}
bignum
,Math::Big*
. - person pii_ke   schedule 06.07.2020