CROSSTABS: Drop buggy ASE for asymmetric lambda; fix T for symmetric lambda.
[pspp] / src / math / histogram.c
index ca2d9d7d20f1d93517bbaa71e2f36f7f9aa54791..b00067210f08676736d589660c9eb85f75216831 100644 (file)
@@ -21,6 +21,7 @@
 #include <gsl/gsl_histogram.h>
 #include <math.h>
 
+#include "data/settings.h"
 #include "libpspp/message.h"
 #include "libpspp/assertion.h"
 #include "libpspp/cast.h"
@@ -84,6 +85,10 @@ double get_slack (double limit, double half_bin_width, int *n_half_bins)
    ADJ_MIN and ADJ_MAX are locations of the adjusted values of MIN and MAX (the range will
    always be  equal or slightly larger).
    Returns the number of bins.
+
+   The "testing_assert" expressions in this function should be algebraically correct.
+   However, due to floating point rounding they could fail, especially when small numbers
+   are involved.  In normal use, therefore, testing_assert does nothing.
  */
 static int
 adjust_bin_ranges (double bin_width, double min, double max, double *adj_min, double *adj_max)
@@ -97,7 +102,7 @@ adjust_bin_ranges (double bin_width, double min, double max, double *adj_min, do
   double lower_slack =  get_slack (min, half_bin_width, &lower_limit);
   double upper_slack = -get_slack (max, half_bin_width, &upper_limit);
 
-  assert (max > min);
+  testing_assert (max > min);
 
   /* If min is negative, then lower_slack may be less than zero.
      In this case, the lower bound must be extended in the negative direction
@@ -108,7 +113,7 @@ adjust_bin_ranges (double bin_width, double min, double max, double *adj_min, do
       lower_limit--;
       lower_slack += half_bin_width;
     }
-  assert (lower_limit * half_bin_width <= min);
+  testing_assert (lower_limit * half_bin_width <= min);
 
   /* However, the upper bound must be extended regardless, because histogram bins
      span the range [lower, upper). In other words, the upper bound must be
@@ -116,7 +121,7 @@ adjust_bin_ranges (double bin_width, double min, double max, double *adj_min, do
   */
   upper_limit++;;
   upper_slack += half_bin_width;
-  assert (upper_limit * half_bin_width > max);
+  testing_assert (upper_limit * half_bin_width > max);
 
   /* The range must be an EVEN number of half bin_widths */
   if ( (upper_limit - lower_limit) % 2)
@@ -140,29 +145,43 @@ adjust_bin_ranges (double bin_width, double min, double max, double *adj_min, do
   */
   if ( lower_limit % 2 == 0)
     {
-      if (upper_slack > lower_slack && upper_slack > half_bin_width)
+      /* If there is not enough slack at either end to perform a shift,
+         then we must extend the range so that there is.  We must extend
+         by two half bin widths in order to preserve the EVEN condition
+         established above.  Also, we extend on the end with the least
+         slack, in order to keep things as balanced as possible. */
+      if ( upper_slack > lower_slack && upper_slack <= half_bin_width)
+        {
+          lower_limit -= 2;
+          lower_slack += 2 * half_bin_width;
+        }
+           
+      if (lower_slack > upper_slack && lower_slack < half_bin_width)
+        {
+          upper_limit += 2;
+          upper_slack += 2 * half_bin_width;
+        }
+
+      if (upper_slack > lower_slack)
         {
+          testing_assert (upper_slack > half_bin_width);
+
           /* Adjust the range to the left */
           lower_limit --;
           upper_limit --;
           upper_slack -= half_bin_width;
           lower_slack += half_bin_width;
         }
-      else if (lower_slack > upper_slack && lower_slack >= half_bin_width)
+      else
         {
+          testing_assert (lower_slack >= half_bin_width);
+
           /* Adjust the range to the right */
           lower_limit ++;
           upper_limit ++;
           lower_slack -= half_bin_width;
           upper_slack += half_bin_width;
         }
-      else
-        {
-          /* In this case, we cannot adjust in either direction.
-             To get the most pleasing alignment, we would have to change
-             the bin width (which would have other visual disadvantages).
-          */
-        }
     }
 
   /* If there are any completely empty bins, then remove them,
@@ -183,8 +202,8 @@ adjust_bin_ranges (double bin_width, double min, double max, double *adj_min, do
   *adj_min = lower_limit * half_bin_width;
   *adj_max = upper_limit * half_bin_width;
 
-  assert (*adj_max > max);
-  assert (*adj_min <= min);
+  testing_assert (*adj_max > max);
+  testing_assert (*adj_min <= min);
 
   return (upper_limit - lower_limit) / 2.0;
 }
@@ -208,12 +227,14 @@ histogram_create (double bin_width, double min, double max)
 
   assert (bin_width > 0);
 
+  bin_width = chart_rounded_tick (bin_width);
   bins = adjust_bin_ranges (bin_width, min, max, &adjusted_min, &adjusted_max);
 
   /* Force the number of bins to lie in a sensible range. */
   if (bins > MAX_BINS) 
     {
-      bins = adjust_bin_ranges ((max - min) / (double) (MAX_BINS - 1),
+      bin_width = chart_rounded_tick ((max - min) / (double) (MAX_BINS - 1));
+      bins = adjust_bin_ranges (bin_width,
                                 min, max, &adjusted_min, &adjusted_max);
     }