Project

General

Profile

Files » FRACTION.js

v1 - David Workman, 11/19/2012 11:03 PM

 
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
}
(1-1/2)