1
|
/**
|
2
|
* Wrapped an open source fractional library for Servoy use.
|
3
|
* Note: this is an early version of ratio.js. (https://github.com/LarryBattle/Ratio.js)
|
4
|
* @author Data Mosaic <http://www.data-mosaic.com/>
|
5
|
*
|
6
|
* @author Larry Battle <http://bateru.com/news/contact-me>
|
7
|
* @license MIT
|
8
|
* @version 1.0
|
9
|
* @date May 08, 2012
|
10
|
* @purpose To provide a function that converts a decimal to a simplified fraction.
|
11
|
* @info <http://bateru.com/news/2012/05/code-of-the-day-javascript-convert-decimal-to-a-simplified-fraction/>
|
12
|
*/
|
13
|
|
14
|
/**
|
15
|
* Highest denominator to attempt to match on
|
16
|
* @type {Number}
|
17
|
* @private
|
18
|
*
|
19
|
* @properties={typeid:35,uuid:"D2D263FE-7078-410E-A130-5DC6427D39D2",variableType:4}
|
20
|
*/
|
21
|
var _fractionPrecision = 64;
|
22
|
|
23
|
/**
|
24
|
* Highest number of decimals
|
25
|
* @type {Number}
|
26
|
* @private
|
27
|
*
|
28
|
* @properties={typeid:35,uuid:"B3D8AC4E-18C4-42EF-A643-26B531D7F733",variableType:4}
|
29
|
*/
|
30
|
var _decimalPrecision = 6;
|
31
|
|
32
|
/**
|
33
|
* Only calculate the fraction array once
|
34
|
* @type {Object}
|
35
|
* @private
|
36
|
*
|
37
|
* @properties={typeid:35,uuid:"DFCD9BB3-AFAF-4299-80F1-30A89B6EF19C",variableType:-4}
|
38
|
*/
|
39
|
var _fractionObject = getFractionObject();
|
40
|
|
41
|
/**
|
42
|
* Get object with all possible fractions
|
43
|
* @private
|
44
|
*
|
45
|
* @properties={typeid:24,uuid:"74B39990-D0A6-44A6-A209-5E2823AF2380"}
|
46
|
*/
|
47
|
function getFractionObject() {
|
48
|
function getKeys(obj) {
|
49
|
var props = [];
|
50
|
for (var prop in obj) {
|
51
|
if (obj.hasOwnProperty(prop)) {
|
52
|
props.push(prop);
|
53
|
}
|
54
|
}
|
55
|
return props;
|
56
|
};
|
57
|
|
58
|
var obj = {
|
59
|
0 : '0',
|
60
|
1 : '1'
|
61
|
},
|
62
|
num = _fractionPrecision,
|
63
|
den = _fractionPrecision + 1,
|
64
|
value;
|
65
|
while (--num) {
|
66
|
while (--den > 1) {
|
67
|
value = (num / den).toFixed(_decimalPrecision);
|
68
|
if (value < 1) {
|
69
|
obj[value] = num + "/" + den;
|
70
|
}
|
71
|
}
|
72
|
den = _fractionPrecision + 1;
|
73
|
}
|
74
|
obj.keys = getKeys(obj);
|
75
|
return obj;
|
76
|
};
|
77
|
|
78
|
/**
|
79
|
* Get the closest number
|
80
|
*
|
81
|
* @param {Number[]} arr
|
82
|
* @param {Number} val
|
83
|
* @return {Number|Boolean}
|
84
|
* @private
|
85
|
*
|
86
|
* @properties={typeid:24,uuid:"F76DF876-15E8-4AD3-949B-22E3C16602E4"}
|
87
|
*/
|
88
|
function getClosestNum(arr, val) {
|
89
|
if (typeof arr !== "object" || !(arr.hasOwnProperty('length')) || isNaN(val)) {
|
90
|
return false;
|
91
|
}
|
92
|
var i = arr.length,
|
93
|
j = i - 1,
|
94
|
minDiff = Math.abs(+val - arr[j]),
|
95
|
diff;
|
96
|
while (i--) {
|
97
|
diff = Math.abs(+val - arr[i]);
|
98
|
if (diff < minDiff) {
|
99
|
minDiff = diff;
|
100
|
j = i;
|
101
|
}
|
102
|
}
|
103
|
return arr[j];
|
104
|
};
|
105
|
|
106
|
/**
|
107
|
* Get a fraction representation from a decimal
|
108
|
*
|
109
|
* @param {Number} dec
|
110
|
* @return {String|Boolean}
|
111
|
*
|
112
|
* @properties={typeid:24,uuid:"9654BA30-4AA4-4C93-BCEF-108F4730E4E0"}
|
113
|
*/
|
114
|
function getFractionFromDecimal(dec) {
|
115
|
if (isNaN(dec) || !isFinite(dec)) {
|
116
|
return false;
|
117
|
}
|
118
|
if (!/\./.test(dec)) {
|
119
|
return dec;
|
120
|
}
|
121
|
var fracs = _fractionObject,
|
122
|
matches = dec.toString().match(/(\d+)(\.\d+)/),
|
123
|
fraction = fracs[getClosestNum(fracs.keys, Math.abs(+matches[2]))],
|
124
|
sign = ( 0 < dec || (fraction == "0" && Math.abs(dec) < 1) ) ? '' : '-';
|
125
|
if (1 < Math.abs(dec)) {
|
126
|
if (isNaN(fraction)) {
|
127
|
fraction = +matches[1] + " " + fraction;
|
128
|
} else {
|
129
|
fraction = +matches[1] + (+fraction);
|
130
|
}
|
131
|
}
|
132
|
return sign + fraction;
|
133
|
};
|
134
|
|
135
|
/**
|
136
|
* Get a decimal representation from a fraction
|
137
|
*
|
138
|
* @param {String} fraction
|
139
|
* @return {Number|Boolean}
|
140
|
*
|
141
|
* @properties={typeid:24,uuid:"41703256-520A-42B4-84EC-D8813CE6DA77"}
|
142
|
*/
|
143
|
function getDecimalFromFraction(fraction) {
|
144
|
var parts = [],
|
145
|
j,
|
146
|
arr = [];
|
147
|
|
148
|
/**
|
149
|
* @param {Array} parts
|
150
|
* @param {Array} arr
|
151
|
* @return {Number}
|
152
|
*/
|
153
|
function frac(parts,arr) {
|
154
|
function getNumeratorWithSign(top, bottom) {
|
155
|
var sign = (+top * (+bottom || 1)) < 0 ? -1 : 1;
|
156
|
return Math.abs(+top) * sign;
|
157
|
}
|
158
|
|
159
|
parts = parts.split(/\//);
|
160
|
arr[0] = getNumeratorWithSign(parts[0], parts[1]);
|
161
|
arr[1] = Math.abs(+parts[1]);
|
162
|
|
163
|
return arr
|
164
|
}
|
165
|
|
166
|
//nothing
|
167
|
if (!fraction) {
|
168
|
return 0
|
169
|
}
|
170
|
//whole number
|
171
|
else if (fraction == parseInt(fraction)) {
|
172
|
return parseInt(fraction)
|
173
|
}
|
174
|
else if (/\d\s*\//.test(fraction)) {
|
175
|
//mixed number
|
176
|
if (/\d\s+[+\-]?\d/.test(fraction)) {
|
177
|
parts = fraction.match(/(\S+)\s+(\S.*)/);
|
178
|
arr = frac(parts[2],[]);
|
179
|
j = 0 < (parseFloat(parts[1]) * arr[0]) ? 1 : -1;
|
180
|
arr[0] = j * (Math.abs(arr[0]) + Math.abs(parts[1] * arr[1]));
|
181
|
}
|
182
|
//fraction
|
183
|
else {
|
184
|
arr = frac(fraction,arr)
|
185
|
}
|
186
|
|
187
|
return arr[0] / arr[1]
|
188
|
}
|
189
|
}
|
190
|
|
191
|
/**
|
192
|
* Called for performing a conversion between a displayed value and a database value.
|
193
|
*
|
194
|
* @param {String|Number} displayedValue The displayed value.
|
195
|
*
|
196
|
* @return {Number} the database value.
|
197
|
*
|
198
|
* @properties={typeid:24,uuid:"861CC360-A31D-48FC-991E-416A5A7C3941"}
|
199
|
*/
|
200
|
function convertFraction2DB(displayedValue) {
|
201
|
if (typeof displayedValue == 'string') {
|
202
|
return getDecimalFromFraction(displayedValue)
|
203
|
}
|
204
|
}
|
205
|
|
206
|
/**
|
207
|
* Called for performing a conversion between a database value and a displayed value.
|
208
|
*
|
209
|
* @param {Number} databaseValue The database value.
|
210
|
*
|
211
|
* @return {String} the displayed value.
|
212
|
*
|
213
|
* @properties={typeid:24,uuid:"6F37C385-32FF-4B62-A54B-B4E82BF9A5FB"}
|
214
|
*/
|
215
|
function convertDB2Fraction(databaseValue) {
|
216
|
//don't show .0 for whole numbers
|
217
|
if (typeof databaseValue == 'number' && parseInt(databaseValue) == databaseValue) {
|
218
|
return utils.numberFormat(databaseValue,'#')
|
219
|
}
|
220
|
//format the fraction
|
221
|
else if (typeof databaseValue == 'number') {
|
222
|
return getFractionFromDecimal(databaseValue)
|
223
|
}
|
224
|
//shouldn't need this return; needed to break out of loop when called with display instead of database value as input
|
225
|
else {
|
226
|
return databaseValue
|
227
|
}
|
228
|
}
|