summaryrefslogtreecommitdiffstats
path: root/src/storage_number.c
blob: b5c5f40675804d8e05f46e0e424958c1ba3ab960 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#ifdef STORAGE_WITH_MATH
#include <math.h>
#endif

#include "common.h"
#include "log.h"
#include "storage_number.h"

#if __GNUC__
#if __x86_64__ || __ppc64__
#define ENVIRONMENT64
#else
#define ENVIRONMENT32
#endif
#endif

storage_number pack_storage_number(calculated_number value, uint32_t flags)
{
	// bit 32 = sign 0:positive, 1:negative
	// bit 31 = 0:divide, 1:multiply
	// bit 30, 29, 28 = (multiplier or divider) 0-6 (7 total)
	// bit 27, 26, 25 flags
	// bit 24 to bit 1 = the value

	storage_number r = get_storage_number_flags(flags);
	if(!value) return r;

	int m = 0;
	calculated_number n = value;

	// if the value is negative
	// add the sign bit and make it positive
	if(n < 0) {
		r += (1 << 31); // the sign bit 32
		n = -n;
	}

	// make its integer part fit in 0x00ffffff
	// by dividing it by 10 up to 7 times
	// and increasing the multiplier
	while(m < 7 && n > (calculated_number)0x00ffffff) {
		n /= 10;
		m++;
	}

	if(m) {
		// the value was too big and we divided it
		// so we add a multiplier to unpack it
		r += (1 << 30) + (m << 27); // the multiplier m

		if(n > (calculated_number)0x00ffffff) {
			error("Number " CALCULATED_NUMBER_FORMAT " is too big.", value);
			r += 0x00ffffff;
			return r;
		}
	}
	else {
		// 0x0019999e is the number that can be multiplied
		// by 10 to give 0x00ffffff
		// while the value is below 0x0019999e we can
		// multiply it by 10, up to 7 times, increasing
		// the multiplier
		while(m < 7 && n < (calculated_number)0x0019999e) {
			n *= 10;
			m++;
		}

		// the value was small enough and we multiplied it
		// so we add a divider to unpack it
		r += (0 << 30) + (m << 27); // the divider m
	}

#ifdef STORAGE_WITH_MATH
	// without this there are rounding problems
	// example: 0.9 becomes 0.89
	r += lrint((double) n);
#else
	r += (storage_number)n;
#endif

	return r;
}

calculated_number unpack_storage_number(storage_number value)
{
	if(!value) return 0;

	int sign = 0, exp = 0;

	value ^= get_storage_number_flags(value);

	if(value & (1 << 31)) {
		sign = 1;
		value ^= 1 << 31;
	}

	if(value & (1 << 30)) {
		exp = 1;
		value ^= 1 << 30;
	}

	int mul = value >> 27;
	value ^= mul << 27;

	calculated_number n = value;

	// fprintf(stderr, "UNPACK: %08X, sign = %d, exp = %d, mul = %d, n = " CALCULATED_NUMBER_FORMAT "\n", value, sign, exp, mul, n);

	while(mul > 0) {
		if(exp) n *= 10;
		else n /= 10;
		mul--;
	}

	if(sign) n = -n;
	return n;
}

#ifdef ENVIRONMENT32
// This trick seems to give an 80% speed increase in 32bit systems
// print_calculated_number_llu_r() will just print the digits up to the
// point the remaining value fits in 32 bits, and then calls
// print_calculated_number_lu_r() to print the rest with 32 bit arithmetic.

static char *print_calculated_number_lu_r(char *str, unsigned long uvalue) {
	char *wstr = str;

	// print each digit
	do *wstr++ = (char)('0' + (uvalue % 10)); while(uvalue /= 10);
	return wstr;
}

static char *print_calculated_number_llu_r(char *str, unsigned long long uvalue) {
	char *wstr = str;

	// print each digit
	do *wstr++ = (char)('0' + (uvalue % 10)); while((uvalue /= 10) && uvalue > (unsigned long long)0xffffffff);
	if(uvalue) return print_calculated_number_lu_r(wstr, uvalue);
	return wstr;
}
#endif

int print_calculated_number(char *str, calculated_number value)
{
	char *wstr = str;

	int sign = (value < 0) ? 1 : 0;
	if(sign) value = -value;

#ifdef STORAGE_WITH_MATH
	// without llrint() there are rounding problems
	// for example 0.9 becomes 0.89
	unsigned long long uvalue = (unsigned long long int) llrint(value * (calculated_number)100000);
#else
	unsigned long long uvalue = value * (calculated_number)100000;
#endif

#ifdef ENVIRONMENT32
	if(uvalue > (unsigned long long)0xffffffff)
		wstr = print_calculated_number_llu_r(str, uvalue);
	else
		wstr = print_calculated_number_lu_r(str, uvalue);
#else
	do *wstr++ = (char)('0' + (uvalue % 10)); while(uvalue /= 10);
#endif

	// make sure we have 6 bytes at least
	while((wstr - str) < 6) *wstr++ = '0';

	// put the sign back
	if(sign) *wstr++ = '-';

	// reverse it
    char *begin = str, *end = --wstr, aux;
    while (end > begin) aux = *end, *end-- = *begin, *begin++ = aux;
	// wstr--;
	// strreverse(str, wstr);

	// remove trailing zeros
	int decimal = 5;
	while(decimal > 0 && *wstr == '0') {
		*wstr-- = '\0';
		decimal--;
	}

	// terminate it, one position to the right
	// to let space for a dot
	wstr[2] = '\0';

	// make space for the dot
	int i;
	for(i = 0; i < decimal ;i++) {
		wstr[1] = wstr[0];
		wstr--;
	}

	// put the dot
	if(wstr[2] == '\0') { wstr[1] = '\0'; decimal--; }
	else wstr[1] = '.';

	// return the buffer length
	return (int) ((wstr - str) + 2 + decimal );
}