UNPKG

39.7 kBtext/coffeescriptView Raw
1###
2 Guesses a rational for each float in the passed expression
3###
4
5
6Eval_approxratio = ->
7 theArgument = cadr(p1)
8 push(theArgument)
9 approxratioRecursive()
10
11approxratioRecursive = ->
12 i = 0
13 save()
14 p1 = pop(); # expr
15 if (istensor(p1))
16 p4 = alloc_tensor(p1.tensor.nelem)
17 p4.tensor.ndim = p1.tensor.ndim
18 for i in [0...p1.tensor.ndim]
19 p4.tensor.dim[i] = p1.tensor.dim[i]
20 for i in [0...p1.tensor.nelem]
21 push(p1.tensor.elem[i])
22 approxratioRecursive()
23 p4.tensor.elem[i] = pop()
24
25 check_tensor_dimensions p4
26
27 push(p4)
28 else if p1.k == DOUBLE
29 push(p1)
30 approxOneRatioOnly()
31 else if (iscons(p1))
32 push(car(p1))
33 approxratioRecursive()
34 push(cdr(p1))
35 approxratioRecursive()
36 cons()
37 else
38 push(p1)
39 restore()
40
41approxOneRatioOnly = ->
42 zzfloat()
43 supposedlyTheFloat = pop()
44 if supposedlyTheFloat.k == DOUBLE
45 theFloat = supposedlyTheFloat.d
46 splitBeforeAndAfterDot = theFloat.toString().split(".")
47 if splitBeforeAndAfterDot.length == 2
48 numberOfDigitsAfterTheDot = splitBeforeAndAfterDot[1].length
49 precision = 1/Math.pow(10,numberOfDigitsAfterTheDot)
50 theRatio = floatToRatioRoutine(theFloat,precision)
51 push_rational(theRatio[0], theRatio[1])
52 else
53 push_integer(theFloat)
54 return
55
56 # we didn't manage, just leave unexpressed
57 push_symbol(APPROXRATIO)
58 push(theArgument)
59 list(2)
60
61# original routine by John Kennedy, see
62# https://web.archive.org/web/20111027100847/http://homepage.smc.edu/kennedy_john/DEC2FRAC.PDF
63# courtesy of Michael Borcherds
64# who ported this to JavaScript under MIT licence
65# also see
66# https://github.com/geogebra/geogebra/blob/master/common/src/main/java/org/geogebra/common/kernel/algos/AlgoFractionText.java
67# potential other ways to do this:
68# https://rosettacode.org/wiki/Convert_decimal_number_to_rational
69# http://www.homeschoolmath.net/teaching/rational_numbers.php
70# http://stackoverflow.com/questions/95727/how-to-convert-floats-to-human-readable-fractions
71
72floatToRatioRoutine = (decimal, AccuracyFactor) ->
73 FractionNumerator = undefined
74 FractionDenominator = undefined
75 DecimalSign = undefined
76 Z = undefined
77 PreviousDenominator = undefined
78 ScratchValue = undefined
79 ret = [
80 0
81 0
82 ]
83 if isNaN(decimal)
84 return ret
85 # return 0/0
86 if decimal == Infinity
87 ret[0] = 1
88 ret[1] = 0
89 # 1/0
90 return ret
91 if decimal == -Infinity
92 ret[0] = -1
93 ret[1] = 0
94 # -1/0
95 return ret
96 if decimal < 0.0
97 DecimalSign = -1.0
98 else
99 DecimalSign = 1.0
100 decimal = Math.abs(decimal)
101 if Math.abs(decimal - Math.floor(decimal)) < AccuracyFactor
102 # handles exact integers including 0
103 FractionNumerator = decimal * DecimalSign
104 FractionDenominator = 1.0
105 ret[0] = FractionNumerator
106 ret[1] = FractionDenominator
107 return ret
108 if decimal < 1.0e-19
109 # X = 0 already taken care of
110 FractionNumerator = DecimalSign
111 FractionDenominator = 9999999999999999999.0
112 ret[0] = FractionNumerator
113 ret[1] = FractionDenominator
114 return ret
115 if decimal > 1.0e19
116 FractionNumerator = 9999999999999999999.0 * DecimalSign
117 FractionDenominator = 1.0
118 ret[0] = FractionNumerator
119 ret[1] = FractionDenominator
120 return ret
121 Z = decimal
122 PreviousDenominator = 0.0
123 FractionDenominator = 1.0
124 loop
125 Z = 1.0 / (Z - Math.floor(Z))
126 ScratchValue = FractionDenominator
127 FractionDenominator = FractionDenominator * Math.floor(Z) + PreviousDenominator
128 PreviousDenominator = ScratchValue
129 FractionNumerator = Math.floor(decimal * FractionDenominator + 0.5)
130 # Rounding Function
131 unless Math.abs(decimal - (FractionNumerator / FractionDenominator)) > AccuracyFactor and Z != Math.floor(Z)
132 break
133 FractionNumerator = DecimalSign * FractionNumerator
134 ret[0] = FractionNumerator
135 ret[1] = FractionDenominator
136 ret
137
138approx_just_an_integer = 0
139approx_sine_of_rational = 1
140approx_sine_of_pi_times_rational = 2
141approx_rationalOfPi = 3
142approx_radicalOfRatio = 4
143approx_nothingUseful = 5
144approx_ratioOfRadical = 6
145approx_rationalOfE = 7
146approx_logarithmsOfRationals = 8
147approx_rationalsOfLogarithms = 9
148
149approxRationalsOfRadicals = (theFloat) ->
150 splitBeforeAndAfterDot = theFloat.toString().split(".")
151
152 if splitBeforeAndAfterDot.length == 2
153 numberOfDigitsAfterTheDot = splitBeforeAndAfterDot[1].length
154 precision = 1/Math.pow(10,numberOfDigitsAfterTheDot)
155 else
156 return ["" + Math.floor(theFloat), approx_just_an_integer, Math.floor(theFloat), 1, 2]
157
158 console.log "precision: " + precision
159
160 # simple radicals.
161
162 bestResultSoFar = null
163 minimumComplexity = Number.MAX_VALUE
164
165 for i in [2,3,5,6,7,8,10]
166 for j in [1..10]
167 #console.log "i,j: " + i + "," + j
168 hypothesis = Math.sqrt(i)/j
169 #console.log "hypothesis: " + hypothesis
170 if Math.abs(hypothesis) > 1e-10
171 ratio = theFloat/hypothesis
172 likelyMultiplier = Math.round(ratio)
173 #console.log "ratio: " + ratio
174 error = Math.abs(1 - ratio/likelyMultiplier)
175 else
176 ratio = 1
177 likelyMultiplier = 1
178 error = Math.abs(theFloat - hypothesis)
179 #console.log "error: " + error
180 if error < 2 * precision
181 complexity = simpleComplexityMeasure likelyMultiplier, i, j
182 if complexity < minimumComplexity
183 #console.log "MINIMUM MULTIPLIER SO FAR"
184 minimumComplexity = complexity
185 result = likelyMultiplier + " * sqrt( " + i + " ) / " + j
186 #console.log result + " error: " + error
187 bestResultSoFar = [result, approx_ratioOfRadical, likelyMultiplier, i, j]
188
189 return bestResultSoFar
190
191approxRadicalsOfRationals = (theFloat) ->
192 splitBeforeAndAfterDot = theFloat.toString().split(".")
193
194 if splitBeforeAndAfterDot.length == 2
195 numberOfDigitsAfterTheDot = splitBeforeAndAfterDot[1].length
196 precision = 1/Math.pow(10,numberOfDigitsAfterTheDot)
197 else
198 return ["" + Math.floor(theFloat), approx_just_an_integer, Math.floor(theFloat), 1, 2]
199
200 console.log "precision: " + precision
201
202 # simple radicals.
203
204 bestResultSoFar = null
205 minimumComplexity = Number.MAX_VALUE
206
207 # this one catches things like Math.sqrt(3/4), but
208 # things like Math.sqrt(1/2) are caught by the paragraph
209 # above (and in a better form)
210 for i in [1,2,3,5,6,7,8,10]
211 for j in [1,2,3,5,6,7,8,10]
212 #console.log "i,j: " + i + "," + j
213 hypothesis = Math.sqrt(i/j)
214 #console.log "hypothesis: " + hypothesis
215 if Math.abs(hypothesis) > 1e-10
216 ratio = theFloat/hypothesis
217 likelyMultiplier = Math.round(ratio)
218 #console.log "ratio: " + ratio
219 error = Math.abs(1 - ratio/likelyMultiplier)
220 else
221 ratio = 1
222 likelyMultiplier = 1
223 error = Math.abs(theFloat - hypothesis)
224 #console.log "error: " + error
225 if error < 2 * precision
226 complexity = simpleComplexityMeasure likelyMultiplier, i, j
227 if complexity < minimumComplexity
228 #console.log "MINIMUM MULTIPLIER SO FAR"
229 minimumComplexity = complexity
230 result = likelyMultiplier + " * (sqrt( " + i + " / " + j + " )"
231 #console.log result + " error: " + error
232 bestResultSoFar = [result, approx_radicalOfRatio, likelyMultiplier, i, j]
233
234 return bestResultSoFar
235
236approxRadicals = (theFloat) ->
237 splitBeforeAndAfterDot = theFloat.toString().split(".")
238
239 if splitBeforeAndAfterDot.length == 2
240 numberOfDigitsAfterTheDot = splitBeforeAndAfterDot[1].length
241 precision = 1/Math.pow(10,numberOfDigitsAfterTheDot)
242 else
243 return ["" + Math.floor(theFloat), approx_just_an_integer, Math.floor(theFloat), 1, 2]
244
245 console.log "precision: " + precision
246
247 # simple radicals.
248
249 # we always prefer a rational of a radical of an integer
250 # to a radical of a rational. Radicals of rationals generate
251 # radicals at the denominator which we'd rather avoid
252
253 approxRationalsOfRadicalsResult = approxRationalsOfRadicals theFloat
254 if approxRationalsOfRadicalsResult?
255 return approxRationalsOfRadicalsResult
256
257 approxRadicalsOfRationalsResult = approxRadicalsOfRationals theFloat
258 if approxRadicalsOfRationalsResult?
259 return approxRadicalsOfRationalsResult
260
261 return null
262
263approxLogs = (theFloat) ->
264 splitBeforeAndAfterDot = theFloat.toString().split(".")
265
266 if splitBeforeAndAfterDot.length == 2
267 numberOfDigitsAfterTheDot = splitBeforeAndAfterDot[1].length
268 precision = 1/Math.pow(10,numberOfDigitsAfterTheDot)
269 else
270 return ["" + Math.floor(theFloat), approx_just_an_integer, Math.floor(theFloat), 1, 2]
271
272 console.log "precision: " + precision
273
274 # we always prefer a rational of a log to a log of
275 # a rational
276
277 approxRationalsOfLogsResult = approxRationalsOfLogs theFloat
278 if approxRationalsOfLogsResult?
279 return approxRationalsOfLogsResult
280
281 approxLogsOfRationalsResult = approxLogsOfRationals theFloat
282 if approxLogsOfRationalsResult?
283 return approxLogsOfRationalsResult
284
285 return null
286
287approxRationalsOfLogs = (theFloat) ->
288 splitBeforeAndAfterDot = theFloat.toString().split(".")
289
290 if splitBeforeAndAfterDot.length == 2
291 numberOfDigitsAfterTheDot = splitBeforeAndAfterDot[1].length
292 precision = 1/Math.pow(10,numberOfDigitsAfterTheDot)
293 else
294 return ["" + Math.floor(theFloat), approx_just_an_integer, Math.floor(theFloat), 1, 2]
295
296 console.log "precision: " + precision
297
298 bestResultSoFar = null
299 minimumComplexity = Number.MAX_VALUE
300
301 # simple rationals of logs
302 for i in [2..5]
303 for j in [1..5]
304 #console.log "i,j: " + i + "," + j
305 hypothesis = Math.log(i)/j
306 #console.log "hypothesis: " + hypothesis
307 if Math.abs(hypothesis) > 1e-10
308 ratio = theFloat/hypothesis
309 likelyMultiplier = Math.round(ratio)
310 #console.log "ratio: " + ratio
311 error = Math.abs(1 - ratio/likelyMultiplier)
312 else
313 ratio = 1
314 likelyMultiplier = 1
315 error = Math.abs(theFloat - hypothesis)
316 #console.log "error: " + error
317
318 # it does happen that due to roundings
319 # a "higher multiple" is picked, which is obviously
320 # unintended.
321 # E.g. 1 * log(1 / 3 ) doesn't match log( 3 ) BUT
322 # it matches -5 * log( 3 ) / 5
323 # so we avoid any case where the multiplier is a multiple
324 # of the divisor.
325 if likelyMultiplier != 1 and Math.abs(Math.floor(likelyMultiplier/j)) == Math.abs(likelyMultiplier/j)
326 continue
327
328 if error < 2.2 * precision
329 complexity = simpleComplexityMeasure likelyMultiplier, i, j
330 if complexity < minimumComplexity
331 #console.log "MINIMUM MULTIPLIER SO FAR"
332 minimumComplexity = complexity
333 result = likelyMultiplier + " * log( " + i + " ) / " + j
334 #console.log result + " error: " + error
335 bestResultSoFar = [result, approx_rationalsOfLogarithms, likelyMultiplier, i, j]
336
337 return bestResultSoFar
338
339approxLogsOfRationals = (theFloat) ->
340 splitBeforeAndAfterDot = theFloat.toString().split(".")
341
342 if splitBeforeAndAfterDot.length == 2
343 numberOfDigitsAfterTheDot = splitBeforeAndAfterDot[1].length
344 precision = 1/Math.pow(10,numberOfDigitsAfterTheDot)
345 else
346 return ["" + Math.floor(theFloat), approx_just_an_integer, Math.floor(theFloat), 1, 2]
347
348 console.log "precision: " + precision
349
350 bestResultSoFar = null
351 minimumComplexity = Number.MAX_VALUE
352
353 # simple logs of rationals
354 for i in [1..5]
355 for j in [1..5]
356 #console.log "i,j: " + i + "," + j
357 hypothesis = Math.log(i/j)
358 #console.log "hypothesis: " + hypothesis
359 if Math.abs(hypothesis) > 1e-10
360 ratio = theFloat/hypothesis
361 likelyMultiplier = Math.round(ratio)
362 #console.log "ratio: " + ratio
363 error = Math.abs(1 - ratio/likelyMultiplier)
364 else
365 ratio = 1
366 likelyMultiplier = 1
367 error = Math.abs(theFloat - hypothesis)
368 #console.log "error: " + error
369 if error < 1.96 * precision
370 complexity = simpleComplexityMeasure likelyMultiplier, i, j
371 if complexity < minimumComplexity
372 #console.log "MINIMUM MULTIPLIER SO FAR"
373 minimumComplexity = complexity
374 result = likelyMultiplier + " * log( " + i + " / " + j + " )"
375 #console.log result + " error: " + error
376 bestResultSoFar = [result, approx_logarithmsOfRationals, likelyMultiplier, i, j]
377
378 return bestResultSoFar
379
380approxRationalsOfPowersOfE = (theFloat) ->
381 splitBeforeAndAfterDot = theFloat.toString().split(".")
382
383 if splitBeforeAndAfterDot.length == 2
384 numberOfDigitsAfterTheDot = splitBeforeAndAfterDot[1].length
385 precision = 1/Math.pow(10,numberOfDigitsAfterTheDot)
386 else
387 return ["" + Math.floor(theFloat), approx_just_an_integer, Math.floor(theFloat), 1, 2]
388
389 console.log "precision: " + precision
390
391 bestResultSoFar = null
392 minimumComplexity = Number.MAX_VALUE
393
394 # simple rationals of a few powers of e
395 for i in [1..2]
396 for j in [1..12]
397 #console.log "i,j: " + i + "," + j
398 hypothesis = Math.pow(Math.E,i)/j
399 #console.log "hypothesis: " + hypothesis
400 if Math.abs(hypothesis) > 1e-10
401 ratio = theFloat/hypothesis
402 likelyMultiplier = Math.round(ratio)
403 #console.log "ratio: " + ratio
404 error = Math.abs(1 - ratio/likelyMultiplier)
405 else
406 ratio = 1
407 likelyMultiplier = 1
408 error = Math.abs(theFloat - hypothesis)
409 #console.log "error: " + error
410 if error < 2 * precision
411 complexity = simpleComplexityMeasure likelyMultiplier, i, j
412 if complexity < minimumComplexity
413 #console.log "MINIMUM MULTIPLIER SO FAR"
414 minimumComplexity = complexity
415 result = likelyMultiplier + " * (e ^ " + i + " ) / " + j
416 #console.log result + " error: " + error
417 bestResultSoFar = [result, approx_rationalOfE, likelyMultiplier, i, j]
418
419 return bestResultSoFar
420
421approxRationalsOfPowersOfPI = (theFloat) ->
422 splitBeforeAndAfterDot = theFloat.toString().split(".")
423
424 if splitBeforeAndAfterDot.length == 2
425 numberOfDigitsAfterTheDot = splitBeforeAndAfterDot[1].length
426 precision = 1/Math.pow(10,numberOfDigitsAfterTheDot)
427 else
428 return ["" + Math.floor(theFloat), approx_just_an_integer, Math.floor(theFloat), 1, 2]
429
430 console.log "precision: " + precision
431 bestResultSoFar = null
432
433 # here we do somethng a little special: since
434 # the powers of pi can get quite big, there might
435 # be multiple hypothesis where more of the
436 # magnitude is shifted to the multiplier, and some
437 # where more of the magnitude is shifted towards the
438 # exponent of pi. So we prefer the hypotheses with the
439 # lower multiplier since it's likely to insert more
440 # information.
441 minimumComplexity = Number.MAX_VALUE
442
443 # simple rationals of a few powers of PI
444 for i in [1..5]
445 for j in [1..12]
446 #console.log "i,j: " + i + "," + j
447 hypothesis = Math.pow(Math.PI,i)/j
448 #console.log "hypothesis: " + hypothesis
449 if Math.abs(hypothesis) > 1e-10
450 ratio = theFloat/hypothesis
451 likelyMultiplier = Math.round(ratio)
452 #console.log "ratio: " + ratio
453 error = Math.abs(1 - ratio/likelyMultiplier)
454 else
455 ratio = 1
456 likelyMultiplier = 1
457 error = Math.abs(theFloat - hypothesis)
458 #console.log "error: " + error
459 if error < 2 * precision
460 complexity = simpleComplexityMeasure likelyMultiplier, i, j
461 if complexity < minimumComplexity
462 #console.log "MINIMUM MULTIPLIER SO FAR"
463 minimumComplexity = complexity
464 result = likelyMultiplier + " * (pi ^ " + i + " ) / " + j + " )"
465 #console.log result + " error: " + error
466 bestResultSoFar = [result, approx_rationalOfPi, likelyMultiplier, i, j]
467
468 #console.log "approxRationalsOfPowersOfPI returning: " + bestResultSoFar
469 return bestResultSoFar
470
471approxTrigonometric = (theFloat) ->
472 splitBeforeAndAfterDot = theFloat.toString().split(".")
473
474 if splitBeforeAndAfterDot.length == 2
475 numberOfDigitsAfterTheDot = splitBeforeAndAfterDot[1].length
476 precision = 1/Math.pow(10,numberOfDigitsAfterTheDot)
477 else
478 return ["" + Math.floor(theFloat), approx_just_an_integer, Math.floor(theFloat), 1, 2]
479
480 console.log "precision: " + precision
481
482 # we always prefer a sin of a rational without the PI
483
484 approxSineOfRationalsResult = approxSineOfRationals theFloat
485 if approxSineOfRationalsResult?
486 return approxSineOfRationalsResult
487
488 approxSineOfRationalMultiplesOfPIResult = approxSineOfRationalMultiplesOfPI theFloat
489 if approxSineOfRationalMultiplesOfPIResult?
490 return approxSineOfRationalMultiplesOfPIResult
491
492 return null
493
494approxSineOfRationals = (theFloat) ->
495 splitBeforeAndAfterDot = theFloat.toString().split(".")
496
497 if splitBeforeAndAfterDot.length == 2
498 numberOfDigitsAfterTheDot = splitBeforeAndAfterDot[1].length
499 precision = 1/Math.pow(10,numberOfDigitsAfterTheDot)
500 else
501 return ["" + Math.floor(theFloat), approx_just_an_integer, Math.floor(theFloat), 1, 2]
502
503 console.log "precision: " + precision
504
505 bestResultSoFar = null
506 minimumComplexity = Number.MAX_VALUE
507
508 # we only check very simple rationals because they begin to get tricky
509 # quickly, also they collide often with the "rational of pi" hypothesis.
510 # For example sin(11) is veeery close to 1 (-0.99999020655)
511 # (see: http://mathworld.wolfram.com/AlmostInteger.html )
512 # we stop at rationals that mention up to 10
513 for i in [1..4]
514 for j in [1..4]
515 #console.log "i,j: " + i + "," + j
516 fraction = i/j
517 hypothesis = Math.sin(fraction)
518 #console.log "hypothesis: " + hypothesis
519 if Math.abs(hypothesis) > 1e-10
520 ratio = theFloat/hypothesis
521 likelyMultiplier = Math.round(ratio)
522 #console.log "ratio: " + ratio
523 error = Math.abs(1 - ratio/likelyMultiplier)
524 else
525 ratio = 1
526 likelyMultiplier = 1
527 error = Math.abs(theFloat - hypothesis)
528 #console.log "error: " + error
529 if error < 2 * precision
530 complexity = simpleComplexityMeasure likelyMultiplier, i, j
531 if complexity < minimumComplexity
532 #console.log "MINIMUM MULTIPLIER SO FAR"
533 minimumComplexity = complexity
534 result = likelyMultiplier + " * sin( " + i + "/" + j + " )"
535 #console.log result + " error: " + error
536 bestResultSoFar = [result, approx_sine_of_rational, likelyMultiplier, i, j]
537
538 return bestResultSoFar
539
540approxSineOfRationalMultiplesOfPI = (theFloat) ->
541 splitBeforeAndAfterDot = theFloat.toString().split(".")
542
543 if splitBeforeAndAfterDot.length == 2
544 numberOfDigitsAfterTheDot = splitBeforeAndAfterDot[1].length
545 precision = 1/Math.pow(10,numberOfDigitsAfterTheDot)
546 else
547 return ["" + Math.floor(theFloat), approx_just_an_integer, Math.floor(theFloat), 1, 2]
548
549 console.log "precision: " + precision
550
551 bestResultSoFar = null
552 minimumComplexity = Number.MAX_VALUE
553
554 # check rational multiples of pi
555 for i in [1..13]
556 for j in [1..13]
557 #console.log "i,j: " + i + "," + j
558 fraction = i/j
559 hypothesis = Math.sin(Math.PI * fraction)
560 #console.log "hypothesis: " + hypothesis
561 if Math.abs(hypothesis) > 1e-10
562 ratio = theFloat/hypothesis
563 likelyMultiplier = Math.round(ratio)
564 #console.log "ratio: " + ratio
565 error = Math.abs(1 - ratio/likelyMultiplier)
566 else
567 ratio = 1
568 likelyMultiplier = 1
569 error = Math.abs(theFloat - hypothesis)
570 #console.log "error: " + error
571 # magic number 23 comes from the case sin(pi/10)
572 if error < 23 * precision
573 complexity = simpleComplexityMeasure likelyMultiplier, i, j
574 if complexity < minimumComplexity
575 #console.log "MINIMUM MULTIPLIER SO FAR"
576 minimumComplexity = complexity
577 result = likelyMultiplier + " * sin( " + i + "/" + j + " * pi )"
578 #console.log result + " error: " + error
579 bestResultSoFar = [result, approx_sine_of_pi_times_rational, likelyMultiplier, i, j]
580
581 return bestResultSoFar
582
583approxAll = (theFloat) ->
584 splitBeforeAndAfterDot = theFloat.toString().split(".")
585
586 if splitBeforeAndAfterDot.length == 2
587 numberOfDigitsAfterTheDot = splitBeforeAndAfterDot[1].length
588 precision = 1/Math.pow(10,numberOfDigitsAfterTheDot)
589 else
590 return ["" + Math.floor(theFloat), approx_just_an_integer, Math.floor(theFloat), 1, 2]
591
592 console.log "precision: " + precision
593
594 constantsSumMin = Number.MAX_VALUE
595 constantsSum = 0
596 bestApproxSoFar = null
597
598 LOG_EXPLANATIONS = true
599
600 approxRadicalsResult = approxRadicals theFloat
601 if approxRadicalsResult?
602 constantsSum = simpleComplexityMeasure approxRadicalsResult
603 if constantsSum < constantsSumMin
604 if LOG_EXPLANATIONS then console.log "better explanation by approxRadicals: " + approxRadicalsResult + " complexity: " + constantsSum
605 constantsSumMin = constantsSum
606 bestApproxSoFar = approxRadicalsResult
607 else
608 if LOG_EXPLANATIONS then console.log "subpar explanation by approxRadicals: " + approxRadicalsResult + " complexity: " + constantsSum
609
610 approxLogsResult = approxLogs(theFloat)
611 if approxLogsResult?
612 constantsSum = simpleComplexityMeasure approxLogsResult
613 if constantsSum < constantsSumMin
614 if LOG_EXPLANATIONS then console.log "better explanation by approxLogs: " + approxLogsResult + " complexity: " + constantsSum
615 constantsSumMin = constantsSum
616 bestApproxSoFar = approxLogsResult
617 else
618 if LOG_EXPLANATIONS then console.log "subpar explanation by approxLogs: " + approxLogsResult + " complexity: " + constantsSum
619
620 approxRationalsOfPowersOfEResult = approxRationalsOfPowersOfE(theFloat)
621 if approxRationalsOfPowersOfEResult?
622 constantsSum = simpleComplexityMeasure approxRationalsOfPowersOfEResult
623 if constantsSum < constantsSumMin
624 if LOG_EXPLANATIONS then console.log "better explanation by approxRationalsOfPowersOfE: " + approxRationalsOfPowersOfEResult + " complexity: " + constantsSum
625 constantsSumMin = constantsSum
626 bestApproxSoFar = approxRationalsOfPowersOfEResult
627 else
628 if LOG_EXPLANATIONS then console.log "subpar explanation by approxRationalsOfPowersOfE: " + approxRationalsOfPowersOfEResult + " complexity: " + constantsSum
629
630 approxRationalsOfPowersOfPIResult = approxRationalsOfPowersOfPI(theFloat)
631 if approxRationalsOfPowersOfPIResult?
632 constantsSum = simpleComplexityMeasure approxRationalsOfPowersOfPIResult
633 if constantsSum < constantsSumMin
634 if LOG_EXPLANATIONS then console.log "better explanation by approxRationalsOfPowersOfPI: " + approxRationalsOfPowersOfPIResult + " complexity: " + constantsSum
635 constantsSumMin = constantsSum
636 bestApproxSoFar = approxRationalsOfPowersOfPIResult
637 else
638 if LOG_EXPLANATIONS then console.log "subpar explanation by approxRationalsOfPowersOfPI: " + approxRationalsOfPowersOfPIResult + " complexity: " + constantsSum
639
640 approxTrigonometricResult = approxTrigonometric(theFloat)
641 if approxTrigonometricResult?
642 constantsSum = simpleComplexityMeasure approxTrigonometricResult
643 if constantsSum < constantsSumMin
644 if LOG_EXPLANATIONS then console.log "better explanation by approxTrigonometric: " + approxTrigonometricResult + " complexity: " + constantsSum
645 constantsSumMin = constantsSum
646 bestApproxSoFar = approxTrigonometricResult
647 else
648 if LOG_EXPLANATIONS then console.log "subpar explanation by approxTrigonometric: " + approxTrigonometricResult + " complexity: " + constantsSum
649
650
651 return bestApproxSoFar
652
653simpleComplexityMeasure = (aResult, b, c) ->
654
655 theSum = null
656
657 if aResult instanceof Array
658
659 # we want PI and E to somewhat increase the
660 # complexity of the expression, so basically they count
661 # more than any integer lower than 3, i.e. we consider
662 # 1,2,3 to be more fundamental than PI or E.
663 switch aResult[1]
664 when approx_sine_of_pi_times_rational
665 theSum = 4
666 # exponents of PI and E need to be penalised as well
667 # otherwise they come to explain any big number
668 # so we count them just as much as the multiplier
669 when approx_rationalOfPi
670 theSum = Math.pow(4,Math.abs(aResult[3])) * Math.abs(aResult[2])
671 when approx_rationalOfE
672 theSum = Math.pow(3,Math.abs(aResult[3])) * Math.abs(aResult[2])
673 else theSum = 0
674
675 theSum += Math.abs(aResult[2]) * (Math.abs(aResult[3]) + Math.abs(aResult[4]))
676 else
677 theSum += Math.abs(aResult) * (Math.abs(b) + Math.abs(c))
678
679 # heavily discount unit constants
680
681 if aResult[2] == 1
682 theSum -= 1
683 else
684 theSum += 1
685
686 if aResult[3] == 1
687 theSum -= 1
688 else
689 theSum += 1
690
691 if aResult[4] == 1
692 theSum -= 1
693 else
694 theSum += 1
695
696 if theSum < 0
697 theSum = 0
698
699 return theSum
700
701
702testApprox = () ->
703
704 for i in [2,3,5,6,7,8,10]
705 for j in [2,3,5,6,7,8,10]
706 if i == j then continue # this is just 1
707 console.log "testapproxRadicals testing: " + "1 * sqrt( " + i + " ) / " + j
708 fraction = i/j
709 value = Math.sqrt(i)/j
710 returned = approxRadicals(value)
711 returnedValue = returned[2] * Math.sqrt(returned[3])/returned[4]
712 if Math.abs(value - returnedValue) > 1e-15
713 console.log "fail testapproxRadicals: " + "1 * sqrt( " + i + " ) / " + j + " . obtained: " + returned
714
715 for i in [2,3,5,6,7,8,10]
716 for j in [2,3,5,6,7,8,10]
717 if i == j then continue # this is just 1
718 console.log "testapproxRadicals testing with 4 digits: " + "1 * sqrt( " + i + " ) / " + j
719 fraction = i/j
720 originalValue = Math.sqrt(i)/j
721 value = originalValue.toFixed(4)
722 returned = approxRadicals(value)
723 returnedValue = returned[2] * Math.sqrt(returned[3])/returned[4]
724 if Math.abs(originalValue - returnedValue) > 1e-15
725 console.log "fail testapproxRadicals with 4 digits: " + "1 * sqrt( " + i + " ) / " + j + " . obtained: " + returned
726
727 for i in [2,3,5,6,7,8,10]
728 for j in [2,3,5,6,7,8,10]
729 if i == j then continue # this is just 1
730 console.log "testapproxRadicals testing: " + "1 * sqrt( " + i + " / " + j + " )"
731 fraction = i/j
732 value = Math.sqrt(i/j)
733 returned = approxRadicals(value)
734 if returned?
735 returnedValue = returned[2] * Math.sqrt(returned[3]/returned[4])
736 if returned[1] == approx_radicalOfRatio and Math.abs(value - returnedValue) > 1e-15
737 console.log "fail testapproxRadicals: " + "1 * sqrt( " + i + " / " + j + " ) . obtained: " + returned
738
739 for i in [1,2,3,5,6,7,8,10]
740 for j in [1,2,3,5,6,7,8,10]
741 if i == 1 and j == 1 then continue
742 console.log "testapproxRadicals testing with 4 digits:: " + "1 * sqrt( " + i + " / " + j + " )"
743 fraction = i/j
744 originalValue = Math.sqrt(i/j)
745 value = originalValue.toFixed(4)
746 returned = approxRadicals(value)
747 returnedValue = returned[2] * Math.sqrt(returned[3]/returned[4])
748 if returned[1] == approx_radicalOfRatio and Math.abs(originalValue - returnedValue) > 1e-15
749 console.log "fail testapproxRadicals with 4 digits:: " + "1 * sqrt( " + i + " / " + j + " ) . obtained: " + returned
750
751 for i in [1..5]
752 for j in [1..5]
753 console.log "testApproxAll testing: " + "1 * log(" + i + " ) / " + j
754 fraction = i/j
755 value = Math.log(i)/j
756 returned = approxAll(value)
757 returnedValue = returned[2] * Math.log(returned[3])/returned[4]
758 if Math.abs(value - returnedValue) > 1e-15
759 console.log "fail testApproxAll: " + "1 * log(" + i + " ) / " + j + " . obtained: " + returned
760
761 for i in [1..5]
762 for j in [1..5]
763 console.log "testApproxAll testing with 4 digits: " + "1 * log(" + i + " ) / " + j
764 fraction = i/j
765 originalValue = Math.log(i)/j
766 value = originalValue.toFixed(4)
767 returned = approxAll(value)
768 returnedValue = returned[2] * Math.log(returned[3])/returned[4]
769 if Math.abs(originalValue - returnedValue) > 1e-15
770 console.log "fail testApproxAll with 4 digits: " + "1 * log(" + i + " ) / " + j + " . obtained: " + returned
771
772 for i in [1..5]
773 for j in [1..5]
774 console.log "testApproxAll testing: " + "1 * log(" + i + " / " + j + " )"
775 fraction = i/j
776 value = Math.log(i/j)
777 returned = approxAll(value)
778 returnedValue = returned[2] * Math.log(returned[3]/returned[4])
779 if Math.abs(value - returnedValue) > 1e-15
780 console.log "fail testApproxAll: " + "1 * log(" + i + " / " + j + " )" + " . obtained: " + returned
781
782 for i in [1..5]
783 for j in [1..5]
784 console.log "testApproxAll testing with 4 digits: " + "1 * log(" + i + " / " + j + " )"
785 fraction = i/j
786 originalValue = Math.log(i/j)
787 value = originalValue.toFixed(4)
788 returned = approxAll(value)
789 returnedValue = returned[2] * Math.log(returned[3]/returned[4])
790 if Math.abs(originalValue - returnedValue) > 1e-15
791 console.log "fail testApproxAll with 4 digits: " + "1 * log(" + i + " / " + j + " )" + " . obtained: " + returned
792
793 for i in [1..2]
794 for j in [1..12]
795 console.log "testApproxAll testing: " + "1 * (e ^ " + i + " ) / " + j
796 fraction = i/j
797 value = Math.pow(Math.E,i)/j
798 returned = approxAll(value)
799 returnedValue = returned[2] * Math.pow(Math.E,returned[3])/returned[4]
800 if Math.abs(value - returnedValue) > 1e-15
801 console.log "fail testApproxAll: " + "1 * (e ^ " + i + " ) / " + j + " . obtained: " + returned
802
803 for i in [1..2]
804 for j in [1..12]
805 console.log "approxRationalsOfPowersOfE testing with 4 digits: " + "1 * (e ^ " + i + " ) / " + j
806 fraction = i/j
807 originalValue = Math.pow(Math.E,i)/j
808 value = originalValue.toFixed(4)
809 returned = approxRationalsOfPowersOfE(value)
810 returnedValue = returned[2] * Math.pow(Math.E,returned[3])/returned[4]
811 if Math.abs(originalValue - returnedValue) > 1e-15
812 console.log "fail approxRationalsOfPowersOfE with 4 digits: " + "1 * (e ^ " + i + " ) / " + j + " . obtained: " + returned
813
814 for i in [1..2]
815 for j in [1..12]
816 console.log "testApproxAll testing: " + "1 * pi ^ " + i + " / " + j
817 fraction = i/j
818 value = Math.pow(Math.PI,i)/j
819 returned = approxAll(value)
820 returnedValue = returned[2] * Math.pow(Math.PI,returned[3])/returned[4]
821 if Math.abs(value - returnedValue) > 1e-15
822 console.log "fail testApproxAll: " + "1 * pi ^ " + i + " / " + j + " ) . obtained: " + returned
823
824 for i in [1..2]
825 for j in [1..12]
826 console.log "approxRationalsOfPowersOfPI testing with 4 digits: " + "1 * pi ^ " + i + " / " + j
827 fraction = i/j
828 originalValue = Math.pow(Math.PI,i)/j
829 value = originalValue.toFixed(4)
830 returned = approxRationalsOfPowersOfPI(value)
831 returnedValue = returned[2] * Math.pow(Math.PI,returned[3])/returned[4]
832 if Math.abs(originalValue - returnedValue) > 1e-15
833 console.log "fail approxRationalsOfPowersOfPI with 4 digits: " + "1 * pi ^ " + i + " / " + j + " ) . obtained: " + returned
834
835 for i in [1..4]
836 for j in [1..4]
837 console.log "testApproxAll testing: " + "1 * sin( " + i + "/" + j + " )"
838 fraction = i/j
839 value = Math.sin(fraction)
840 returned = approxAll(value)
841 returnedFraction = returned[3]/returned[4]
842 returnedValue = returned[2] * Math.sin(returnedFraction)
843 if Math.abs(value - returnedValue) > 1e-15
844 console.log "fail testApproxAll: " + "1 * sin( " + i + "/" + j + " ) . obtained: " + returned
845
846 # 5 digits create no problem
847 for i in [1..4]
848 for j in [1..4]
849 console.log "testApproxAll testing with 5 digits: " + "1 * sin( " + i + "/" + j + " )"
850 fraction = i/j
851 originalValue = Math.sin(fraction)
852 value = originalValue.toFixed(5)
853 returned = approxAll(value)
854 if !returned?
855 console.log "fail testApproxAll with 5 digits: " + "1 * sin( " + i + "/" + j + " ) . obtained: undefined "
856 returnedFraction = returned[3]/returned[4]
857 returnedValue = returned[2] * Math.sin(returnedFraction)
858 error = Math.abs(originalValue - returnedValue)
859 if error > 1e-14
860 console.log "fail testApproxAll with 5 digits: " + "1 * sin( " + i + "/" + j + " ) . obtained: " + returned + " error: " + error
861
862 # 4 digits create two collisions
863 for i in [1..4]
864 for j in [1..4]
865
866 console.log "testApproxAll testing with 4 digits: " + "1 * sin( " + i + "/" + j + " )"
867 fraction = i/j
868 originalValue = Math.sin(fraction)
869 value = originalValue.toFixed(4)
870 returned = approxAll(value)
871 if !returned?
872 console.log "fail testApproxAll with 4 digits: " + "1 * sin( " + i + "/" + j + " ) . obtained: undefined "
873 returnedFraction = returned[3]/returned[4]
874 returnedValue = returned[2] * Math.sin(returnedFraction)
875 error = Math.abs(originalValue - returnedValue)
876 if error > 1e-14
877 console.log "fail testApproxAll with 4 digits: " + "1 * sin( " + i + "/" + j + " ) . obtained: " + returned + " error: " + error
878
879 value = 0
880 if approxAll(value)[0] != "0" then console.log "fail testApproxAll: 0"
881
882 value = 0.0
883 if approxAll(value)[0] != "0" then console.log "fail testApproxAll: 0.0"
884
885 value = 0.00
886 if approxAll(value)[0] != "0" then console.log "fail testApproxAll: 0.00"
887
888 value = 0.000
889 if approxAll(value)[0] != "0" then console.log "fail testApproxAll: 0.000"
890
891 value = 0.0000
892 if approxAll(value)[0] != "0" then console.log "fail testApproxAll: 0.0000"
893
894 value = 1
895 if approxAll(value)[0] != "1" then console.log "fail testApproxAll: 1"
896
897 value = 1.0
898 if approxAll(value)[0] != "1" then console.log "fail testApproxAll: 1.0"
899
900 value = 1.00
901 if approxAll(value)[0] != "1" then console.log "fail testApproxAll: 1.00"
902
903 value = 1.000
904 if approxAll(value)[0] != "1" then console.log "fail testApproxAll: 1.000"
905
906 value = 1.0000
907 if approxAll(value)[0] != "1" then console.log "fail testApproxAll: 1.0000"
908
909 value = 1.00000
910 if approxAll(value)[0] != "1" then console.log "fail testApproxAll: 1.00000"
911
912 value = Math.sqrt(2)
913 if approxAll(value)[0] != "1 * sqrt( 2 ) / 1" then console.log "fail testApproxAll: Math.sqrt(2)"
914
915 value = 1.41
916 if approxAll(value)[0] != "1 * sqrt( 2 ) / 1" then console.log "fail testApproxAll: 1.41"
917
918 # if we narrow down to a particular family then we can get
919 # an OK guess even with few digits, expecially for really "famous" numbers
920
921 value = 1.4
922 if approxRadicals(value)[0] != "1 * sqrt( 2 ) / 1" then console.log "fail approxRadicals: 1.4"
923
924 value = 0.6
925 if approxLogs(value)[0] != "1 * log( 2 ) / 1" then console.log "fail approxLogs: 0.6"
926
927 value = 0.69
928 if approxLogs(value)[0] != "1 * log( 2 ) / 1" then console.log "fail approxLogs: 0.69"
929
930 value = 0.7
931 if approxLogs(value)[0] != "1 * log( 2 ) / 1" then console.log "fail approxLogs: 0.7"
932
933 value = 1.09
934 if approxLogs(value)[0] != "1 * log( 3 ) / 1" then console.log "fail approxLogs: 1.09"
935
936 value = 1.09
937 if approxAll(value)[0] != "1 * log( 3 ) / 1" then console.log "fail approxAll: 1.09"
938
939 value = 1.098
940 if approxAll(value)[0] != "1 * log( 3 ) / 1" then console.log "fail approxAll: 1.098"
941
942 value = 1.1
943 if approxAll(value)[0] != "1 * log( 3 ) / 1" then console.log "fail approxAll: 1.1"
944
945 value = 1.11
946 if approxAll(value)[0] != "1 * log( 3 ) / 1" then console.log "fail approxAll: 1.11"
947
948 value = Math.sqrt(3)
949 if approxAll(value)[0] != "1 * sqrt( 3 ) / 1" then console.log "fail testApproxAll: Math.sqrt(3)"
950
951 value = 1.0000
952 if approxAll(value)[0] != "1" then console.log "fail testApproxAll: 1.0000"
953
954
955 value = 3.141592
956 if approxAll(value)[0] != "1 * (pi ^ 1 ) / 1 )" then console.log "fail testApproxAll: 3.141592"
957
958 value = 31.41592
959 if approxAll(value)[0] != "10 * (pi ^ 1 ) / 1 )" then console.log "fail testApproxAll: 31.41592"
960
961 value = 314.1592
962 if approxAll(value)[0] != "100 * (pi ^ 1 ) / 1 )" then console.log "fail testApproxAll: 314.1592"
963
964 value = 31415926.53589793
965 if approxAll(value)[0] != "10000000 * (pi ^ 1 ) / 1 )" then console.log "fail testApproxAll: 31415926.53589793"
966
967 value = Math.sqrt(2)
968 if approxTrigonometric(value)[0] != "2 * sin( 1/4 * pi )" then console.log "fail approxTrigonometric: Math.sqrt(2)"
969
970 value = Math.sqrt(3)
971 if approxTrigonometric(value)[0] != "2 * sin( 1/3 * pi )" then console.log "fail approxTrigonometric: Math.sqrt(3)"
972
973 value = (Math.sqrt(6) - Math.sqrt(2))/4
974 if approxAll(value)[0] != "1 * sin( 1/12 * pi )" then console.log "fail testApproxAll: (Math.sqrt(6) - Math.sqrt(2))/4"
975
976 value = Math.sqrt(2 - Math.sqrt(2))/2
977 if approxAll(value)[0] != "1 * sin( 1/8 * pi )" then console.log "fail testApproxAll: Math.sqrt(2 - Math.sqrt(2))/2"
978
979 value = (Math.sqrt(6) + Math.sqrt(2))/4
980 if approxAll(value)[0] != "1 * sin( 5/12 * pi )" then console.log "fail testApproxAll: (Math.sqrt(6) + Math.sqrt(2))/4"
981
982 value = Math.sqrt(2 + Math.sqrt(3))/2
983 if approxAll(value)[0] != "1 * sin( 5/12 * pi )" then console.log "fail testApproxAll: Math.sqrt(2 + Math.sqrt(3))/2"
984
985 value = (Math.sqrt(5) - 1)/4
986 if approxAll(value)[0] != "1 * sin( 1/10 * pi )" then console.log "fail testApproxAll: (Math.sqrt(5) - 1)/4"
987
988 value = Math.sqrt(10 - 2*Math.sqrt(5))/4
989 if approxAll(value)[0] != "1 * sin( 1/5 * pi )" then console.log "fail testApproxAll: Math.sqrt(10 - 2*Math.sqrt(5))/4"
990
991 # this has a radical form but it's too long to write
992 value = Math.sin(Math.PI/7)
993 if approxAll(value)[0] != "1 * sin( 1/7 * pi )" then console.log "fail testApproxAll: Math.sin(Math.PI/7)"
994
995 # this has a radical form but it's too long to write
996 value = Math.sin(Math.PI/9)
997 if approxAll(value)[0] != "1 * sin( 1/9 * pi )" then console.log "fail testApproxAll: Math.sin(Math.PI/9)"
998
999 value = 1836.15267
1000 if approxRationalsOfPowersOfPI(value)[0] != "6 * (pi ^ 5 ) / 1 )" then console.log "fail approxRationalsOfPowersOfPI: 1836.15267"
1001
1002
1003 for i in [1..13]
1004 for j in [1..13]
1005 console.log "approxTrigonometric testing: " + "1 * sin( " + i + "/" + j + " * pi )"
1006 fraction = i/j
1007 value = Math.sin(Math.PI * fraction)
1008 # we specifically search for sines of rational multiples of PI
1009 # because too many of them would be picked up as simple
1010 # rationals.
1011 returned = approxTrigonometric(value)
1012 returnedFraction = returned[3]/returned[4]
1013 returnedValue = returned[2] * Math.sin(Math.PI * returnedFraction)
1014 if Math.abs(value - returnedValue) > 1e-15
1015 console.log "fail approxTrigonometric: " + "1 * sin( " + i + "/" + j + " * pi ) . obtained: " + returned
1016
1017 for i in [1..13]
1018 for j in [1..13]
1019
1020 # with four digits, there are two collisions with the
1021 # "simple fraction" argument hypotesis, which we prefer since
1022 # it's a simpler expression, so let's skip those
1023 # two tests
1024 if i == 5 and j == 11 or
1025 i == 6 and j == 11
1026 continue
1027
1028 console.log "approxTrigonometric testing with 4 digits: " + "1 * sin( " + i + "/" + j + " * pi )"
1029 fraction = i/j
1030 originalValue = Math.sin(Math.PI * fraction)
1031 value = originalValue.toFixed(4)
1032 # we specifically search for sines of rational multiples of PI
1033 # because too many of them would be picked up as simple
1034 # rationals.
1035 returned = approxTrigonometric(value)
1036 returnedFraction = returned[3]/returned[4]
1037 returnedValue = returned[2] * Math.sin(Math.PI * returnedFraction)
1038 error = Math.abs(originalValue - returnedValue)
1039 if error > 1e-14
1040 console.log "fail approxTrigonometric with 4 digits: " + "1 * sin( " + i + "/" + j + " * pi ) . obtained: " + returned + " error: " + error
1041
1042 console.log "testApprox done"
1043
1044$.approxRadicals = approxRadicals
1045$.approxRationalsOfLogs = approxRationalsOfLogs
1046$.approxAll = approxAll
1047$.testApprox = testApprox