Skip to content

Commit 11bb00b

Browse files
authored
Merge pull request #1807 from ds26gte/horizon
Fix for #1799
2 parents 6732459 + 8f71137 commit 11bb00b

File tree

3 files changed

+124
-16
lines changed

3 files changed

+124
-16
lines changed

src/js/base/js-numbers.js

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -813,6 +813,8 @@ define("pyret-base/js/js-numbers", function() {
813813
// NB: all of these trig-gy generic functions should now return roughnum rather than float
814814
// (except for an arg of 0, etc)
815815

816+
var ln10 = Math.log(10)
817+
816818
// log: pyretnum -> pyretnum
817819
var log = function(n, errbacks) {
818820
if ( eqv(n, 1, errbacks) ) {
@@ -824,7 +826,35 @@ define("pyret-base/js/js-numbers", function() {
824826
if (typeof(n) === 'number') {
825827
return Roughnum.makeInstance(Math.log(n), errbacks);
826828
}
827-
return n.log(errbacks);
829+
var nFix = n.toFixnum();
830+
if (typeof(nFix) === 'number' && nFix !== Infinity) {
831+
return Roughnum.makeInstance(Math.log(nFix), errbacks);
832+
}
833+
// at this point, n must be a very large positive number;
834+
// n > 1e308, i.e, has at least 308 digits;
835+
// we can safely ignore its fractional part;
836+
var nStr = n.round(errbacks).toString();
837+
var nLen = nStr.length;
838+
// we furthermore need only the integer part's first few digits
839+
// although we must remember the number of digits ignored;
840+
var firstFewLen = 308; // has to be <= 308
841+
// say integer N = yyy...yyyxxx...xxx
842+
// where the number of x's is nx;
843+
// So N ~= yyy...yyy * 10^nx
844+
// We'll first find the common (base 10) log of N
845+
// log10(N) ~= log10(yyy...yyy * 10^nx)
846+
// = log10(yyy...yyy) + nx
847+
// Now to convert this to the natural log
848+
// ln(N) = log10(N) / log10(e)
849+
// = log10(N) * ln(10)
850+
// ~= [log10(yyy...yyy) + nx] * ln(10)
851+
// = log10(yyy...yyy) * ln(10) + nx * ln(10)
852+
// = ln(yyy...yyy) + nx * ln(10)
853+
// JS gives us ln(yyy...yyy) and ln(10) so we have a good
854+
// approximation for ln(N)
855+
var nFirstFew = parseInt(nStr.substring(0, firstFewLen));
856+
var nLog = Math.log(nFirstFew) + (nLen - firstFewLen) * ln10;
857+
return Roughnum.makeInstance(nLog, errbacks);
828858
};
829859

830860
// tan: pyretnum -> pyretnum
@@ -2889,8 +2919,13 @@ define("pyret-base/js/js-numbers", function() {
28892919
function bnpIsEven() { return ((this.t>0)?(this[0]&1):this.s) == 0; }
28902920

28912921
// (protected) this^e, e < 2^32, doing sqr and mul with "r" (HAC 14.79)
2892-
function bnpExp(e,z) {
2893-
if(e > 0xffffffff || e < 1) return BigInteger.ONE;
2922+
function bnpExp(e, z, errbacks) {
2923+
if (greaterThan(e, 0xffffffff, errbacks)) {
2924+
errbacks.throwDomainError('expt: exponent ' + e + ' too large');
2925+
}
2926+
if (lessThan(e, 1, errbacks)) {
2927+
return BigInteger.ONE;
2928+
}
28942929
var r = nbi(), r2 = nbi(), g = z.convert(this), i = nbits(e)-1;
28952930
g.copyTo(r);
28962931
while(--i >= 0) {
@@ -2902,10 +2937,10 @@ define("pyret-base/js/js-numbers", function() {
29022937
}
29032938

29042939
// (public) this^e % m, 0 <= e < 2^32
2905-
function bnModPowInt(e,m) {
2940+
function bnModPowInt(e, m, errbacks) {
29062941
var z;
29072942
if(e < 256 || m.isEven()) z = new Classic(m); else z = new Montgomery(m);
2908-
return this.bnpExp(e,z);
2943+
return this.bnpExp(e, z, errbacks);
29092944
}
29102945

29112946
// protected
@@ -3274,7 +3309,9 @@ define("pyret-base/js/js-numbers", function() {
32743309
NullExp.prototype.sqrTo = nSqrTo;
32753310

32763311
// (public) this^e
3277-
function bnPow(e) { return this.bnpExp(e,new NullExp()); }
3312+
function bnPow(e, errbacks) {
3313+
return this.bnpExp(e,new NullExp(), errbacks);
3314+
}
32783315

32793316
// (protected) r = lower n words of "this * a", a.t <= n
32803317
// "this" should be the larger one if appropriate.
@@ -3774,48 +3811,48 @@ define("pyret-base/js/js-numbers", function() {
37743811

37753812
// round: -> pyretnum
37763813
// Round to the nearest integer.
3777-
BigInteger.prototype.round = function(n, errbacks) {
3814+
BigInteger.prototype.round = function(errbacks) {
37783815
return this;
37793816
};
37803817

3781-
BigInteger.prototype.roundEven = function(n, errbacks) {
3818+
BigInteger.prototype.roundEven = function(errbacks) {
37823819
return this;
37833820
};
37843821

37853822
// log: -> pyretnum
37863823
// Produce the log.
3787-
BigInteger.prototype.log = function(n, errbacks) {
3824+
BigInteger.prototype.log = function(errbacks) {
37883825
return log(this.toFixnum(), errbacks);
37893826
};
37903827

37913828
// tan: -> pyretnum
37923829
// Produce the tan.
3793-
BigInteger.prototype.tan = function(n, errbacks) {
3830+
BigInteger.prototype.tan = function(errbacks) {
37943831
return tan(this.toFixnum(), errbacks);
37953832
};
37963833

37973834
// atan: -> pyretnum
37983835
// Produce the arc tangent.
3799-
BigInteger.prototype.atan = function(n, errbacks) {
3836+
BigInteger.prototype.atan = function(errbacks) {
38003837
return atan(this.toFixnum(), errbacks);
38013838
};
38023839

38033840
// cos: -> pyretnum
38043841
// Produce the cosine.
3805-
BigInteger.prototype.cos = function(n, errbacks) {
3842+
BigInteger.prototype.cos = function(errbacks) {
38063843
return cos(this.toFixnum(), errbacks);
38073844
};
38083845

38093846
// sin: -> pyretnum
38103847
// Produce the sine.
3811-
BigInteger.prototype.sin = function(n, errbacks) {
3848+
BigInteger.prototype.sin = function(errbacks) {
38123849
return sin(this.toFixnum(), errbacks);
38133850
};
38143851

38153852
// expt: pyretnum -> pyretnum
38163853
// Produce the power to the input.
38173854
BigInteger.prototype.expt = function(n, errbacks) {
3818-
return bnPow.call(this, n);
3855+
return bnPow.call(this, n, errbacks);
38193856
};
38203857

38213858
// exp: -> pyretnum
@@ -3829,13 +3866,13 @@ define("pyret-base/js/js-numbers", function() {
38293866

38303867
// acos: -> pyretnum
38313868
// Produce the arc cosine.
3832-
BigInteger.prototype.acos = function(n, errbacks) {
3869+
BigInteger.prototype.acos = function(errbacks) {
38333870
return acos(this.toFixnum(), errbacks);
38343871
};
38353872

38363873
// asin: -> pyretnum
38373874
// Produce the arc sine.
3838-
BigInteger.prototype.asin = function(n, errbacks) {
3875+
BigInteger.prototype.asin = function(errbacks) {
38393876
return asin(this.toFixnum(), errbacks);
38403877
};
38413878

tests/jsnums-test/jsnums-test.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// To test: Build Pyret, then cd to this directory, and type
2+
// node jsnums-test.js
3+
4+
var Jasmine = require('jasmine');
5+
var jazz = new Jasmine();
6+
const R = require("requirejs");
7+
var build = process.env["PHASE"] || "build/phaseA";
8+
R.config({
9+
waitSeconds: 15000,
10+
paths: {
11+
"pyret-base": "../../" + build
12+
}
13+
});
14+
R(["pyret-base/js/js-numbers"], function(JN) {
15+
var sampleErrorBacks = {
16+
throwDomainError: function() { throw 'domainError'; },
17+
throwLogNonPositive: function() { throw 'logNonPositive'; },
18+
};
19+
describe("check exceptions in js-numbers methods that can't be tested in Pyret", function() {
20+
it("bnpExp", function() {
21+
// BigInteger.*.expt calls bnPow, which calls bnpExp
22+
// shd raise exc for too-large
23+
expect(function() { JN.makeBignum(2).expt(JN.makeBignum(0xffffffff + 1), sampleErrorBacks); }).toThrow('domainError');
24+
25+
// BigInteger.*.log
26+
// should raise exception for arg <= 0
27+
expect(function() { JN.makeBignum(-1).log(sampleErrorBacks); }).toThrow('logNonPositive');
28+
29+
// BigInteger.*asin
30+
// should raise exception for arg ∉ [-1, +1]
31+
expect(function() { JN.makeBignum(-1.5).asin(sampleErrorBacks); }).toThrow('domainError');
32+
expect(function() { JN.makeBignum(+1.5).asin(sampleErrorBacks); }).toThrow('domainError');
33+
34+
// BigInteger.*acos
35+
// should raise exception for arg ∉ [-1, +1]
36+
expect(function() { JN.makeBignum(-1.5).acos(sampleErrorBacks); }).toThrow('domainError');
37+
expect(function() { JN.makeBignum(+1.5).acos(sampleErrorBacks); }).toThrow('domainError');
38+
39+
});
40+
});
41+
42+
jazz.execute();
43+
44+
});

tests/pyret/tests/test-numbers.arr

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,19 +71,24 @@ check:
7171
num-abs(0) is 0
7272
num-abs(1) is 1
7373

74+
very-bignum = num-expt(10, 23456)
75+
7476
# These are just sanity end the js-nums library has more rigorous tests
7577
# for the accuracy of the trig functions. Here we just make sure the
7678
# Pyret functions are bound to plausible underlying operations
7779
num-sin(0) is 0
7880
num-sin(3.14 / 2) satisfies around(1, 0.1)
7981
num-sin(3.14) satisfies around(0, 0.1)
82+
num-sin(very-bignum) raises "roughnum overflow"
8083

8184
num-cos(0) is 1
8285
num-cos(3.14 / 2) satisfies around(0, 0.1)
8386
num-cos(3.14) satisfies around(-1, 0.1)
87+
num-cos(very-bignum) raises "roughnum overflow"
8488

8589
num-tan(0) is 0
8690
num-tan(3.14 / 4) satisfies around(1, 0.01)
91+
num-tan(very-bignum) raises "roughnum overflow"
8792

8893
num-asin(0) is 0
8994
num-asin(1) satisfies around(1.57, 0.01)
@@ -163,6 +168,27 @@ check:
163168
num-log(1) is 0
164169
num-log(num-exp(1)) satisfies around(1, 0.0001)
165170

171+
# in following logs, wolframalpha.com gives a lot more digits; rounding to our precision
172+
173+
# Racket, Wolfram match Pyret
174+
num-log(9e15) is-roughly ~36.736000972246906
175+
176+
# Wolfram gives ~36.841361487904731, Racket matches
177+
num-log(1e16) is-roughly ~36.841361487904734
178+
179+
# Racket, Wolfram match
180+
num-log(1e308) is-roughly ~709.1962086421661
181+
182+
# Racket gives ~711.49879373516, Wolfram matches
183+
num-log(1e309) is-roughly ~711.4987937351601
184+
185+
# Racket gives ~84709.80298615794, Wolfram ~84709.80298615795
186+
num-log(1e36789) is-roughly ~84709.80298615796
187+
188+
# Racket, Wolfram match
189+
# commenting because arg calculation takes much time
190+
# num-log(num-expt(10, 1e5)) is-roughly ~230258.50929940457
191+
166192
2 is num-exact(2)
167193
1 / 3 is num-exact(1 / 3)
168194
# NOTE(joe): This seems a big algorithm-dependent end mainly here
@@ -187,6 +213,7 @@ check:
187213
num-expt(3, 2) is 9
188214
num-expt("nan", 2) raises "Number"
189215
num-expt(2, "nan") raises "Number"
216+
num-expt(7, num-expt(10, 36789)) raises "too large"
190217

191218
num-ceiling(2.5) is 3
192219
num-ceiling("nan") raises "Number"

0 commit comments

Comments
 (0)