kprint: add floating-point formatting based in Grisu2b 59,56

This commit is contained in:
Lephe 2021-02-02 22:18:15 +01:00
parent 023675d449
commit bbf6401213
No known key found for this signature in database
GPG key ID: 1BBA026E13FC0495
15 changed files with 773 additions and 30 deletions

View file

@ -31,6 +31,7 @@ set(SOURCES_COMMON
src/keysc/keycodes.c
src/keysc/keysc.c
src/kprint/kprint.c
src/kprint/kformat_fp.c
src/mmu/mmu.c
src/render/dhline.c
src/render/dimage.c
@ -56,6 +57,7 @@ set(SOURCES_COMMON
src/tmu/tmu.c
src/3rdparty/tinymt32/rand.c
src/3rdparty/tinymt32/tinymt32.c
src/3rdparty/grisu2b_59_56/grisu2b_59_56.c
)
set(SOURCES_FX
src/gray/engine.c
@ -104,9 +106,16 @@ set(ASSETS_FX src/font5x7.png)
set(ASSETS_CG src/font8x9.png)
fxconv_declare_assets(${ASSETS_FX} ${ASSETS_CG})
include_directories("${PROJECT_SOURCE_DIR}/include" "${PROJECT_BINARY_DIR}/include")
include_directories(
"${PROJECT_SOURCE_DIR}/include"
"${PROJECT_BINARY_DIR}/include"
"${FXSDK_COMPILER_INSTALL}/include/openlibm")
add_compile_options(-Wall -Wextra -std=c11 -Os -fstrict-volatile-bitfields)
# Silence extended warnings on Grisu2b code
set_source_files_properties(src/3rdparty/grisu2b_59_56/grisu2b_59_56.c PROPERTIES
COMPILE_OPTIONS "-Wno-all;-Wno-extra")
if("${FXSDK_PLATFORM_LONG}" STREQUAL fx9860G)
add_compile_definitions(FX9860G)
add_library(gint-fx STATIC ${SOURCES_COMMON} ${SOURCES_FX} ${ASSETS_FX})

View file

@ -13,11 +13,15 @@ This is free software: you may use it for any purpose, share it, modify it, and
share your changes. Credit is not required, but please let me know!
gint also includes third-party code that is distributed under its own license.
Currently, this only includes:
Currently, this includes:
* A stripped-down version of the [TinyMT random number generator](http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/TINYMT/index.html)
([GitHub repository](https://github.com/MersenneTwister-Lab/TinyMT)) by
Mutsuo Saito and Makoto Matsumoto. See `src/3rdparty/tinymt32/LICENSE.txt`.
* A stripped-down version of the [Grisu2b floating-point representation
algorithm](https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf)
with α=-59 and γ=-56, by Florian Loitsch. See `src/3rdparty/grisu2b_59_56/README`
for details [the original code here](https://drive.google.com/open?id=0BwvYOx00EwKmejFIMjRORTFLcTA).
## Programming interface

View file

@ -19,6 +19,13 @@
see <stdio.h>. */
size_t kvsprint(char *output, size_t len, char const *format, va_list *args);
/* kprint_enable_fp(): Enable and load floating-point formats
Loads floating-point formats %e, %E, %f, %F, %g and %G. By default these
formats are not enabled because the formatting code takes a large amount of
space. Calling this function pulls the floating-point formatter from the
gint library at link time. */
void kprint_enable_fp(void);
//---
// Printer extensions
//---
@ -34,7 +41,7 @@ typedef struct {
/* Minimal length of formatted string (padding can be added) */
uint16_t length;
/* How much significant characters of data, meaning varies */
uint16_t precision;
int16_t precision;
/* Size specifier for integers (%o, %x, %i, %d, %u), may be one of:
(0) char (8-bit)
@ -78,6 +85,15 @@ typedef void (*kprint_formatter_t)(KFORMAT_ARGS);
@kformat Format function */
void kprint_register(int spec, kprint_formatter_t kformat);
/* kprint_out(): Output a single character to the kprint buffer */
void kprint_out(int byte);
/* kprint_outn(): Output the same character <n> times */
void kprint_outn(int byte, int n);
/* kprint_outstr(): Output a string to the kprint buffer */
void kprint_outstr(char const *str, int n);
//---
// Helper functions for formatters
//---
@ -103,8 +119,8 @@ typedef struct
/* Style of display:
KPRINT_GENERIC Sign ignored, 0-padding ignored
KPRINT_INTEGER .precision causes 0-padding
KPRINT_NUMERICAL No effect */
enum { KPRINT_GENERIC = 0, KPRINT_INTEGER, KPRINT_NUMERICAL } style;
KPRINT_NUMERIC No effect */
enum { KPRINT_GENERIC = 0, KPRINT_INTEGER, KPRINT_NUMERIC } style;
} GPACKED(2) kprint_geometry_t;
@ -115,10 +131,11 @@ typedef struct
* g->prefix, the length of the desired prefix (if unused, 0)
* g->content, the natural content length for the provided data
* g->sign, the sign of the input ("+" or "-"); for KPRINT_GENERIC, 0
* g->style, which affects the meaning of options
This function outputs:
* g->sign, which can be changed to " " or NUL depending on opt
* All other fields of g that are not part of the input
* g->sign, which will be changed to " " or NUL (0) depending on options
* All fields of g that are not part of the input
The algorithm for laying out the format is as follows.
1. For numerical and integer formats, turn a "+" sign into " " if

View file

@ -10,20 +10,28 @@
/* Formatted printing functions
These functions implement most of printf()'s features, including:
These functions implement most of printf(3)'s features, including:
* Signed and unsigned integer formats (%d, %i, %o, %u, %x, %X)
* Character, string and pointer formats (%c, %s, %p)
* Format options (0, #, -, (space), length, precision)
* Parameter size (hh, h, l, ll)
* Parameter length (hh, h, l, ll, z)
* Limiting the size of the output and still returning the whole length
* If kprint_enable_fp() from <gint/kprint.h> is called: floating-point
formats (%e, %E, %f, %F, %g, %G) (disabled by default for space)
They do not support:
* Floating-point (%e, %E, %f, %F, %g, %G, %a, %A)
* Exotic integer types (intmax_t) or features (thousands separators)
* Hexadecimal floating-point (%a, %A)
* The strerror() shorthand, since there is no errno (%m)
* Some size modifiers: long double (L), intmax_t (j), ptrdiff_t (t), and the
nonstandard synonyms q (ll) and Z (z)
* Dynamic length field (*)
* Thousands separators (') and locale-aware digits (I)
* Nonstandard synonyms %C (%lc) and %S (%ls)
A new fixed-point format %j has been added; it behaves like %d but includes
a decimal point. The number of decimal places is specified by the precision
field. */
field. For instance %.3j with 12345 prints "123.45". This can be used for
decimal fixed-point values. */
/* Print to string from var args */
int sprintf(char *str, char const *format, ...);

22
src/3rdparty/grisu2b_59_56/LICENSE vendored Normal file
View file

@ -0,0 +1,22 @@
Copyright (c) 2009 Florian Loitsch
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

14
src/3rdparty/grisu2b_59_56/README vendored Normal file
View file

@ -0,0 +1,14 @@
Grisu is an algorithm by Florian Loitsch to print the decimal representation of
floating-point numbers.
The original code from which this folder is extracted can be found at
<https://drive.google.com/open?id=0BwvYOx00EwKmejFIMjRORTFLcTA>.
Only a minimal variation, the Grisu2 rounding algorithm with α=-59 and γ=-56,
is present here. The code has been adapted to fit the limited runtime as well
as the use of OpenLibm.
See the original paper at <https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf>
and Florian Loitsch's home page at <https://florian.loitsch.com/publications>.
The files are licensed under the permissive conditions of the attached LICENSE.

58
src/3rdparty/grisu2b_59_56/diy_fp.h vendored Normal file
View file

@ -0,0 +1,58 @@
/*
Copyright (c) 2009 Florian Loitsch
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <stdint.h>
typedef struct diy_fp_t {
uint64_t f;
int e;
} diy_fp_t;
static diy_fp_t minus(diy_fp_t x, diy_fp_t y) {
diy_fp_t r = {.f = x.f - y.f, .e = x.e};
return r;
}
/*
static diy_fp_t minus(diy_fp_t x, diy_fp_t y) {
assert(x.e == y.e);
assert(x.f >= y.f);
diy_fp_t r = {.f = x.f - y.f, .e = x.e};
return r;
}
*/
static diy_fp_t multiply(diy_fp_t x, diy_fp_t y) {
uint64_t a,b,c,d,ac,bc,ad,bd,tmp,h;
diy_fp_t r; uint64_t M32 = 0xFFFFFFFF;
a = x.f >> 32; b = x.f & M32;
c = y.f >> 32; d = y.f & M32;
ac = a*c; bc = b*c; ad = a*d; bd = b*d;
tmp = (bd>>32) + (ad&M32) + (bc&M32);
tmp += 1U << 31; /// mult_round
r.f = ac+(ad>>32)+(bc>>32)+(tmp >>32);
r.e = x.e + y.e + 64;
return r;
}

110
src/3rdparty/grisu2b_59_56/double.h vendored Normal file
View file

@ -0,0 +1,110 @@
/*
Copyright (c) 2009 Florian Loitsch
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "diy_fp.h"
#include "powers.h"
#include <stdbool.h>
typedef union {
double d;
uint64_t n;
} converter_t;
static uint64_t double_to_uint64(double d) { converter_t tmp; tmp.d = d; return tmp.n; }
static double uint64_to_double(uint64_t d64) { converter_t tmp; tmp.n = d64; return tmp.d; }
#define DP_SIGNIFICAND_SIZE 52
#define DP_EXPONENT_BIAS (0x3FF + DP_SIGNIFICAND_SIZE)
#define DP_MIN_EXPONENT (-DP_EXPONENT_BIAS)
#define DP_EXPONENT_MASK 0x7FF0000000000000
#define DP_SIGNIFICAND_MASK 0x000FFFFFFFFFFFFF
#define DP_HIDDEN_BIT 0x0010000000000000
static diy_fp_t normalize_diy_fp(diy_fp_t in) {
diy_fp_t res = in;
/* Normalize now */
/* the original number could have been a denormal. */
while (! (res.f & DP_HIDDEN_BIT))
{
res.f <<= 1;
res.e--;
}
/* do the final shifts in one go. Don't forget the hidden bit (the '-1') */
res.f <<= (DIY_SIGNIFICAND_SIZE - DP_SIGNIFICAND_SIZE - 1);
res.e = res.e - (DIY_SIGNIFICAND_SIZE - DP_SIGNIFICAND_SIZE - 1);
return res;
}
static diy_fp_t double2diy_fp(double d) {
uint64_t d64 = double_to_uint64(d);
int biased_e = (d64 & DP_EXPONENT_MASK) >> DP_SIGNIFICAND_SIZE;
uint64_t significand = (d64 & DP_SIGNIFICAND_MASK);
diy_fp_t res;
if (biased_e != 0)
{
res.f = significand + DP_HIDDEN_BIT;
res.e = biased_e - DP_EXPONENT_BIAS;
} else {
res.f = significand;
res.e = DP_MIN_EXPONENT + 1;
}
return res;
}
static diy_fp_t normalize_boundary(diy_fp_t in) {
diy_fp_t res = in;
/* Normalize now */
/* the original number could have been a denormal. */
while (! (res.f & (DP_HIDDEN_BIT << 1)))
{
res.f <<= 1;
res.e--;
}
/* do the final shifts in one go. Don't forget the hidden bit (the '-1') */
res.f <<= (DIY_SIGNIFICAND_SIZE - DP_SIGNIFICAND_SIZE - 2);
res.e = res.e - (DIY_SIGNIFICAND_SIZE - DP_SIGNIFICAND_SIZE - 2);
return res;
}
static void normalized_boundaries(double d, diy_fp_t* out_m_minus, diy_fp_t* out_m_plus) {
diy_fp_t v = double2diy_fp(d);
diy_fp_t pl, mi;
bool significand_is_zero = v.f == DP_HIDDEN_BIT;
pl.f = (v.f << 1) + 1; pl.e = v.e - 1;
pl = normalize_boundary(pl);
if (significand_is_zero)
{
mi.f = (v.f << 2) - 1;
mi.e = v.e - 2;
} else {
mi.f = (v.f << 1) - 1;
mi.e = v.e - 1;
}
mi.f <<= mi.e - pl.e;
mi.e = pl.e;
*out_m_plus = pl;
*out_m_minus = mi;
}

27
src/3rdparty/grisu2b_59_56/grisu2.h vendored Normal file
View file

@ -0,0 +1,27 @@
/*
Copyright (c) 2009 Florian Loitsch
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
void grisu2(double v, char* buffer, int* length, int* K);

View file

@ -0,0 +1,95 @@
/*
Copyright (c) 2009 Florian Loitsch
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/
#include "diy_fp.h"
#include "k_comp.h"
#include "double.h"
#include "powers.h"
#include <stdbool.h>
#include "grisu2.h"
#include <stdint.h>
#define TEN9 1000000000
void grisu_round(char* buffer, int len,
uint64_t delta, uint64_t rest,
uint64_t ten_kappa, uint64_t wp_w) {
while (rest < wp_w &&
delta - rest >= ten_kappa &&
(rest + ten_kappa < wp_w || /// closer
wp_w - rest > rest+ten_kappa - wp_w))
{
buffer[len-1]--; rest += ten_kappa;
}
}
void digit_gen(diy_fp_t W, diy_fp_t Mp, diy_fp_t delta,
char* buffer, int* len, int* K) {
uint32_t div; int d,kappa; diy_fp_t one, wp_w;
wp_w = minus(Mp, W);
one.f = ((uint64_t) 1) << -Mp.e; one.e = Mp.e;
uint32_t p1 = Mp.f >> -one.e; /// Mp_cut
uint64_t p2 = Mp.f & (one.f - 1);
*len = 0; kappa = 10; div = TEN9;
while (kappa > 0) {
d = p1 / div;
if (d || *len) buffer[(*len)++] = '0' + d; /// Mp_inv1
p1 %= div; kappa--;
uint64_t tmp = (((uint64_t)p1)<<-one.e)+p2;
if (tmp <= delta.f) { /// Mp_delta
*K += kappa;
grisu_round(buffer, *len, delta.f, tmp, ((uint64_t)div) << -one.e, wp_w.f);
return;
}
div /= 10;
}
uint64_t unit = 1;
while (1) {
p2 *= 10; delta.f *= 10; unit *= 10;
d = p2 >> -one.e;
if (d || *len) buffer[(*len)++] = '0' + d;
p2 &= one.f - 1; kappa--;
if (p2 < delta.f) {
*K += kappa;
grisu_round(buffer, *len, delta.f, p2, one.f, wp_w.f*unit);
return;
}
}
}
void grisu2(double v, char* buffer, int* length, int* K) {
diy_fp_t w_m, w_p;
int q = 64, alpha = -59, gamma = -56; int pos;
normalized_boundaries(v, &w_m, &w_p);
diy_fp_t w = normalize_diy_fp(double2diy_fp(v));
int mk = k_comp(w_p.e + q, alpha, gamma);
diy_fp_t c_mk = cached_power(mk);
diy_fp_t W = multiply(w, c_mk);
diy_fp_t Wp = multiply(w_p, c_mk);
diy_fp_t Wm = multiply(w_m, c_mk);
Wm.f++; Wp.f--;
diy_fp_t delta = minus(Wp, Wm);
*K = -mk;
digit_gen(W, Wp, delta, buffer, length, K);
}

32
src/3rdparty/grisu2b_59_56/k_comp.h vendored Normal file
View file

@ -0,0 +1,32 @@
/*
Copyright (c) 2009 Florian Loitsch
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <openlibm.h>
#define D_1_LOG2_10 0.30102999566398114 // 1 / lg(10)
static int k_comp(int e, int alpha, int gamma) {
return ceil((alpha-e+63) * D_1_LOG2_10);
}

27
src/3rdparty/grisu2b_59_56/powers.h vendored Normal file
View file

@ -0,0 +1,27 @@
/*
Copyright (c) 2009 Florian Loitsch
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "diy_fp.h"
#include "powers_ten_round64.h"

File diff suppressed because one or more lines are too long

281
src/kprint/kformat_fp.c Normal file
View file

@ -0,0 +1,281 @@
//---
// gint:kprint:kformat-fp: Floating-point formatted printing
//
// This object file provides floating-point formatters. It can be linked in by
// calling kprint_enable_fp() and will automatically register floating-point
// formatters that use the Grisu2b algorithm.
//---
#include <gint/defs/types.h>
#include <gint/defs/util.h>
#include <gint/kprint.h>
#include "../3rdparty/grisu2b_59_56/grisu2.h"
//---
// String generation for doubles
//---
/* The grisu2() function does not have a size limit so we generate into a
buffer large enough to hold the result. */
static char digit_buffer[50];
/* Round a number at the specified place (which can be out of bounds). An extra
byte before the buffer should be secured to leave room for a new digit in
case a carry reaches there. Returns the new start-of-buffer.
@buffer Buffer with generated digits, updated if needed
@length Number of digits generated in the buffer, updated if needed
@e Location of decimal dot relative to integer in buffer
@place Decimal to place to round to */
static void round_str(char **buffer_ptr, int *length, int e, int place)
{
char *buffer = *buffer_ptr;
/* Interpret place as relative to buffer indices */
place += *length + e - 1;
/* Specified place is out-of-bounds */
if(place < 0 || place >= *length - 1) return;
/* Next digit is 0..4 so rounding has no effect */
if(buffer[place + 1] < '5') return;
/* Propagate carries if needed */
while(place >= -1)
{
buffer[place]++;
if(buffer[place] <= '9') break;
buffer[place] = '0';
place--;
}
/* Add one digit if needed */
if(place >= 0) return;
(*buffer_ptr)--;
(*length)++;
}
/* Remove zeros at the end of the digits, reducing [length] accordingly. */
static int remove_zeros(char *buffer, int length, int unremovable)
{
int removed = 0;
while(length > unremovable && buffer[length - 1] == '0')
{
buffer[length - 1] = 0;
length--;
removed++;
}
return removed;
}
/* Handles infinities and NaNs. */
static int special_notation(double v, int upper)
{
if(__builtin_isinf(v) && v < 0)
{
kprint_outstr(upper ? "-INF" : "-inf", 4);
return 1;
}
if(__builtin_isinf(v))
{
kprint_outstr(upper ? "INF" : "inf", 3);
return 1;
}
if(__builtin_isnan(v))
{
kprint_outstr(upper ? "NAN" : "nan", 3);
return 1;
}
return 0;
}
/* Prints decimal explicitly for %f and %g. */
static void direct_notation(kprint_options_t *opt, kprint_geometry_t g,
char *digits, int length, int e)
{
/* Number of characters for decimal part, including dot */
int dec_chars = opt->precision + (opt->precision > 0);
/* See case discussion below */
g.content = (length + e >= 0) ? length + e + dec_chars : 1 + dec_chars;
kformat_geometry(opt, &g);
kprint_outn(' ', g.left_spaces);
if(g.sign) kprint_out(g.sign);
kprint_outn('0', g.zeros);
int pre = opt->precision;
if(e >= 0) /* xxxxxx00[.00] */
{
/* Decimal dot is after digits; rounding never occurs */
kprint_outstr(digits, length);
kprint_outn('0', e);
if(pre > 0)
{
kprint_out('.');
kprint_outn('0', pre);
}
}
else if(length + e > 0) /* xxxy(.xx), xx.xy(xx), xx.xxxx[00] */
{
/* Decimal dot is within the digits; we might have rounded */
kprint_outstr(digits, length + e);
if(pre > 0)
{
kprint_out('.');
kprint_outstr(digits + length + e, min(-e, pre));
kprint_outn('0', pre + e);
}
}
else if(length + e <= 0) /* 0.00(00xxxx), 0.00xy(xx), 0.00xxxx00 */
{
/* Decimal dot is before the digits; we might have rounded */
kprint_out('0');
if(pre > 0)
{
kprint_out('.');
kprint_outn('0', min(-e - length, pre));
kprint_outstr(digits, min(length, pre + length + e));
kprint_outn('0', pre + e);
}
}
kprint_outn(' ', g.right_spaces);
}
/* Prints exponent notation for %e and %g. */
static void exponent_notation(kprint_options_t *opt, kprint_geometry_t g,
char *digits, int length, int e, int uppercase)
{
int true_e = e + length - 1;
/* Number of characters for decimal part and for exponent */
int dec_chars = opt->precision + (opt->precision > 0);
int exp_chars = 4 + (true_e >= 100 || true_e <= -100);
g.content = 1 + dec_chars + exp_chars;
kformat_geometry(opt, &g);
kprint_outn(' ', g.left_spaces);
if(g.sign) kprint_out(g.sign);
kprint_outn('0', g.zeros);
/* Digits */
kprint_out(digits[0]);
if(opt->precision > 0)
{
kprint_out('.');
kprint_outstr(digits + 1, min(length - 1, opt->precision));
kprint_outn('0', opt->precision - (length - 1));
}
/* Exponent */
kprint_out(uppercase ? 'E' : 'e');
kprint_out(true_e >= 0 ? '+' : '-');
if(true_e < 0) true_e = -true_e;
if(true_e >= 100)
{
kprint_out(true_e / 100 + '0');
true_e %= 100;
}
kprint_out(true_e / 10 + '0');
kprint_out(true_e % 10 + '0');
kprint_outn(' ', g.right_spaces);
}
//---
// Formatters for kprint
//---
static void kformat_fp(KFORMAT_ARGS)
{
double v = va_arg(*args, double);
digit_buffer[0] = '0';
char *digits = digit_buffer + 1;
int length, e;
int is_e = (spec | 0x20) == 'e';
int is_f = (spec | 0x20) == 'f';
int is_g = (spec | 0x20) == 'g';
/* In %e and %f, default to 6 decimals. In %g, default to 6 significant
digits, and force to at least 1 */
if(opt->precision < 0) opt->precision = 6;
if(opt->precision == 0 && is_g) opt->precision = 1;
if(special_notation(v, (spec & 0x20) == 0)) return;
/* fabs(v) = int(digits) * 10^e */
grisu2(v, digits, &length, &e);
digits[length] = 0;
/* In %f and %e, .precision is the number of decimal places; in %g, it
is the number of significant digits. Determine the number of decimal
places for the rounding (which is one more than the final number if
a carry creates a new significant digit on the left) */
int round_place = opt->precision;
if(is_e) round_place -= (length + e - 1);
if(is_g) round_place -= (length + e);
/* Round off to the specified number of decimal places. digits and
length may extend one place left because of carries */
round_str(&digits, &length, e, round_place);
kprint_geometry_t g = {
.sign = (v < 0) ? '-' : '+',
.prefix = 0,
.style = KPRINT_NUMERIC,
};
if(is_f) return direct_notation(opt, g, digits, length, e);
if(is_e) return exponent_notation(opt, g, digits, length, e,spec=='E');
int true_e = length + e - 1;
int extreme = (true_e < -4) || (true_e >= opt->precision);
/* %g is left. Remove decimal zeros at the end of the digit string,
starting from the last-shown digit. Keep all figits before the
point, the amount of which depends on the mode */
int removed = remove_zeros(digits, min(length, opt->precision),
extreme ? 1 : true_e + 1);
opt->precision -= removed;
if(extreme)
{
/* Don't print more significant digits than we have digits
(elimination of trailing zeros) */
opt->precision = min(opt->precision, length);
/* There is one leading digit and this many decimal places */
opt->precision--;
return exponent_notation(opt, g, digits, length, e, spec=='G');
}
else
{
/* Number of digits before the point */
int leading_digits = true_e + 1;
/* Eliminate trailing zeros after the point */
opt->precision = min(opt->precision, length);
/* Remove leading digits from decimal place count */
opt->precision -= leading_digits;
return direct_notation(opt, g, digits, length, e);
}
}
/* kprint_enable_fp(): Enable and load floating-point formats */
void kprint_enable_fp(void)
{
kprint_register('e', kformat_fp);
kprint_register('f', kformat_fp);
kprint_register('g', kformat_fp);
}

View file

@ -66,15 +66,12 @@ kprint_formatter_t kprint_formatters[26] = {
/* kprint_register(): Register a formatter */
void kprint_register(int spec, kprint_formatter_t kformat)
{
spec |= 0x20;
int i = (spec | 0x20) - 'a';
/* Non-letters */
if(i < 0 || i >= 26) return;
if(spec < 'a' || spec > 'z') return;
/* Size-specifying letters */
if(spec == 'h' || spec == 'l' || spec == 'z') return;
kprint_formatters[i] = kformat;
kprint_formatters[spec - 'a'] = kformat;
}
//---
@ -104,17 +101,23 @@ static void kprint_flush(void)
kprint_ptr = kprint_buffer;
}
/* kprint_out(): Output a single character to the buffer */
GINLINE static void kprint_out(int byte)
/* kprint_out(): Output a single character to the kprint buffer */
GINLINE void kprint_out(int byte)
{
if(kprint_ptr >= kprint_limit) kprint_flush();
*kprint_ptr++ = byte;
}
/* kprint_outn(): Output the same character <n> times */
GINLINE static void kprint_outn(int byte, size_t n)
GINLINE void kprint_outn(int byte, int n)
{
while(n--) kprint_out(byte);
while(n-- > 0) kprint_out(byte);
}
/* kprint_outstr(): Output a string to the kprint buffer */
GINLINE void kprint_outstr(char const *str, int n)
{
for(int i = 0; i < n; i++) kprint_out(str[i]);
}
//---
@ -125,7 +128,7 @@ GINLINE static void kprint_outn(int byte, size_t n)
kprint_options_t kprint_opt(char const **options_ptr)
{
/* No options enabled by default, set the size to int */
kprint_options_t opt = { .size = 2 };
kprint_options_t opt = { .size = 2, .precision = -1 };
/* This function acts as a deterministic finite automaton */
enum {
@ -144,6 +147,7 @@ kprint_options_t kprint_opt(char const **options_ptr)
if(c == '.')
{
state = precision;
opt.precision = 0;
continue;
}
else if(state == length && c >= '0' && c <= '9')
@ -213,11 +217,9 @@ void kprint(char const *format, va_list *args)
opt = kprint_opt(&format);
spec = *format++;
if(spec)
{
int id = (spec | 0x20) - 'a';
int id = (spec | 0x20) - 'a';
if(id >= 0 && id < 26 && kprint_formatters[id])
kprint_formatters[id](spec, &opt, args);
}
}
kprint_flush();
@ -245,7 +247,7 @@ size_t kvsprint(char *output, size_t length, char const *format, va_list *args)
void kformat_geometry(kprint_options_t *opt, kprint_geometry_t *g)
{
int integral = (g->style == KPRINT_INTEGER);
int numerical = (g->style == KPRINT_NUMERICAL) || integral;
int numerical = (g->style == KPRINT_NUMERIC) || integral;
ssize_t padding;
/* Sign character (no discussion required for negative values) */
@ -263,7 +265,7 @@ void kformat_geometry(kprint_options_t *opt, kprint_geometry_t *g)
if(padding < 0) padding = 0;
/* In integral modes, precision forces zeros */
if(integral && opt->precision)
if(integral && opt->precision >= 0)
{
if(opt->alignment == '0') opt->alignment = 0;
@ -294,7 +296,6 @@ void kformat_geometry(kprint_options_t *opt, kprint_geometry_t *g)
static int digits_10(char *str, uint64_t n)
{
int digits = 0;
while(n || !digits)
{
/* TODO: Use qdiv() in kprint's digits_10() */
@ -421,6 +422,7 @@ static void kformat_int(KFORMAT_ARGS)
int pure, total;
pure = digits_10(digits, n);
if(opt->precision == 0 && !n) pure = 0;
total = max(pure, opt->precision);
g.content = total;
@ -455,6 +457,7 @@ static void kformat_uint(KFORMAT_ARGS)
if(specl == 'o') pure = digits_8(digits, n);
if(specl == 'x') pure = digits_16(digits, spec == 'X', n);
if(opt->precision == 0 && !n) pure = 0;
total = max(pure, opt->precision);
/* Prefix length */
@ -509,7 +512,7 @@ static void kformat_fixed(KFORMAT_ARGS)
kprint_geometry_t g = {
.sign = (n < 0) ? '-' : '+',
.prefix = 0,
.style = KPRINT_NUMERICAL,
.style = KPRINT_NUMERIC,
};
if(n < 0) n = -n;