Add %g conversion specifer to output-stream for limited precision

The %g conversion specifier is for printing numbers that were at some
time stored in a cairo_fixed_t type and as a result have their
precision limited by the size of CAIRO_FIXED_FRAC_BITS.

Using %g will limit the number of digits after the decimal point to
the minimum required to preserve the available precision.
This commit is contained in:
Adrian Johnson 2008-03-18 22:27:25 +10:30
parent f3734085a1
commit d78013470b

View file

@ -44,6 +44,26 @@
#include <ctype.h>
#include <errno.h>
/* Numbers printed with %f are printed with this number of significant
* digits after the decimal.
*/
#define SIGNIFICANT_DIGITS_AFTER_DECIMAL 6
/* Numbers printed with %g are assumed to only have CAIRO_FIXED_FRAC_BITS
* bits of precision available after the decimal point.
*
* FIXED_POINT_DECIMAL_DIGITS specifies the minimum number of decimal
* digits after the decimal point required to preserve the available
* precision.
*
* The conversion is:
*
* FIXED_POINT_DECIMAL_DIGITS = ceil( CAIRO_FIXED_FRAC_BITS * ln(2)/ln(10) )
*
* We can replace ceil(x) with (int)(x+1) since x will never be an
* integer for any likely value of CAIRO_FIXED_FRAC_BITS.
*/
#define FIXED_POINT_DECIMAL_DIGITS ((int)(CAIRO_FIXED_FRAC_BITS*0.301029996 + 1))
void
_cairo_output_stream_init (cairo_output_stream_t *stream,
@ -236,8 +256,6 @@ _cairo_output_stream_write_hex_string (cairo_output_stream_t *stream,
}
}
#define SIGNIFICANT_DIGITS_AFTER_DECIMAL 6
/* Format a double in a locale independent way and trim trailing
* zeros. Based on code from Alex Larson <alexl@redhat.com>.
* http://mail.gnome.org/archives/gtk-devel-list/2001-October/msg00087.html
@ -247,7 +265,7 @@ _cairo_output_stream_write_hex_string (cairo_output_stream_t *stream,
* into cairo (see COPYING). -- Kristian Høgsberg <krh@redhat.com>
*/
static void
_cairo_dtostr (char *buffer, size_t size, double d)
_cairo_dtostr (char *buffer, size_t size, double d, cairo_bool_t limited_precision)
{
struct lconv *locale_data;
const char *decimal_point;
@ -266,40 +284,44 @@ _cairo_dtostr (char *buffer, size_t size, double d)
assert (decimal_point_len != 0);
/* Using "%f" to print numbers less than 0.1 will result in
* reduced precision due to the default 6 digits after the
* decimal point.
*
* For numbers is < 0.1, we print with maximum precision and count
* the number of zeros between the decimal point and the first
* significant digit. We then print the number again with the
* number of decimal places that gives us the required number of
* significant digits. This ensures the number is correctly
* rounded.
*/
if (fabs (d) >= 0.1) {
snprintf (buffer, size, "%f", d);
if (limited_precision) {
snprintf (buffer, size, "%.*f", FIXED_POINT_DECIMAL_DIGITS, d);
} else {
snprintf (buffer, size, "%.18f", d);
p = buffer;
/* Using "%f" to print numbers less than 0.1 will result in
* reduced precision due to the default 6 digits after the
* decimal point.
*
* For numbers is < 0.1, we print with maximum precision and count
* the number of zeros between the decimal point and the first
* significant digit. We then print the number again with the
* number of decimal places that gives us the required number of
* significant digits. This ensures the number is correctly
* rounded.
*/
if (fabs (d) >= 0.1) {
snprintf (buffer, size, "%f", d);
} else {
snprintf (buffer, size, "%.18f", d);
p = buffer;
if (*p == '+' || *p == '-')
p++;
if (*p == '+' || *p == '-')
p++;
while (isdigit (*p))
p++;
while (isdigit (*p))
p++;
if (strncmp (p, decimal_point, decimal_point_len) == 0)
p += decimal_point_len;
if (strncmp (p, decimal_point, decimal_point_len) == 0)
p += decimal_point_len;
num_zeros = 0;
while (*p++ == '0')
num_zeros++;
num_zeros = 0;
while (*p++ == '0')
num_zeros++;
decimal_digits = num_zeros + SIGNIFICANT_DIGITS_AFTER_DECIMAL;
decimal_digits = num_zeros + SIGNIFICANT_DIGITS_AFTER_DECIMAL;
if (decimal_digits < 18)
snprintf (buffer, size, "%.*f", decimal_digits, d);
if (decimal_digits < 18)
snprintf (buffer, size, "%.*f", decimal_digits, d);
}
}
p = buffer;
@ -441,7 +463,10 @@ _cairo_output_stream_vprintf (cairo_output_stream_t *stream,
single_fmt, va_arg (ap, const char *));
break;
case 'f':
_cairo_dtostr (buffer, sizeof buffer, va_arg (ap, double));
_cairo_dtostr (buffer, sizeof buffer, va_arg (ap, double), FALSE);
break;
case 'g':
_cairo_dtostr (buffer, sizeof buffer, va_arg (ap, double), TRUE);
break;
case 'c':
buffer[0] = va_arg (ap, int);