use warnings; use strict; use Data::Dumper; my @percentages = generate(); print "@percentages\n"; my @quantized = quantize(1000,@percentages); print "Original percentages: @percentages\n"; print "Quantized percentages: @quantized\n"; my $sum; $sum += $_ foreach(@quantized);; print "Sum of quantized percentages: $sum\n"; =head2 my @quantized = quantize($factor, @percentages); The quantize() function takes a quantizaton factor and an array of percentages which should add to 100%. It returns an array of quantized percentages which does add to 100%. The percentages are quantized to multiples of (100/$factor). The function minimizes the worst case error. Two error functions are provided: one is the absolute error (the difference between the original value and the quantized value) and the other is the absolute relative error (the absolute error divided by the value being quantized). There are many other possibilities, depending on your needs. =cut sub quantize { my $quantum = 100 / shift; my $error = 0; my $sum = 0; my @x = map { my $q = sprintf("%0.0f", $_/$quantum) * $quantum; my $d = $q - $_; $error += $d; $sum += $q; [ $_, $q, $d ] } @_; print Dumper(\@x); print "initial total error: $error\n"; print "initial sum: $sum\n"; while(abs($sum - 100) > $quantum/2) { my $direction = ($sum > 100) ? 1 : -1 ; my $min_error = 10000; my $min_index = 0; print "errors of adjusted values: "; foreach my $i (0..(@x-1)) { my $e = abs($x[$i]->[2] - $quantum * $direction) / $x[$i]->[0]; # relative error #my $e = abs($x[$i]->[2] - $quantum * $direction); # absolute error print " $e"; if($e < $min_error) { $min_error = $e; $min_index = $i; print "(i = $i)"; } } print "\n"; print "adjust $min_index: $x[$min_index]->[0], $x[$min_index]->[1] $x[$min_index]->[2]\n"; $x[$min_index]->[1] -= $quantum * $direction; $x[$min_index]->[2] -= $quantum * $direction; print "\t$x[$min_index]->[1], $x[$min_index]->[2]\n"; $sum -= $quantum * $direction; } return(map { $_->[1] } @x); } =head2 generate() The generate() function generates a somewhat random array of percentages that adds to 100%. =cut sub generate { my $sum = 0; my @percentages; foreach (1..20) { my $x = rand(50); if($sum + $x < 100) { push(@percentages, $x); $sum += $x; } } push(@percentages, 100 - $sum); return(@percentages); }