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.datamosaic.com/>

5

*

6

* @author Larry Battle <http://bateru.com/news/contactme>

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/codeofthedayjavascriptconvertdecimaltoasimplifiedfraction/>

12

*/

13


14

/**

15

* Highest denominator to attempt to match on

16

* @type {Number}

17

* @private

18

*

19

* @properties={typeid:35,uuid:"D2D263FE7078410EA1305DC6427D39D2",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:"B3D8AC4E18C442EFA64326B531D7F733",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:"DFCD9BB3AFAF429980F130A89B6EF19C",variableType:4}

38

*/

39

var _fractionObject = getFractionObject();

40


41

/**

42

* Get object with all possible fractions

43

* @private

44

*

45

* @properties={typeid:24,uuid:"74B39990D0A644A6A2095E2823AF2380"}

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 {NumberBoolean}

84

* @private

85

*

86

* @properties={typeid:24,uuid:"F76DF87615E84AD3949B22E3C16602E4"}

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 {StringBoolean}

111

*

112

* @properties={typeid:24,uuid:"9654BA304AA44C93BCEF108F4730E4E0"}

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 {NumberBoolean}

140

*

141

* @properties={typeid:24,uuid:"41703256520A42B484ECD8813CE6DA77"}

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 {StringNumber} displayedValue The displayed value.

195

*

196

* @return {Number} the database value.

197

*

198

* @properties={typeid:24,uuid:"861CC360A31D48FC991E416A5A7C3941"}

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:"6F37C38532FF4B62A54BB4E82BF9A5FB"}

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

}
