Skip to content

Commit 08e5a38

Browse files
AldenMBMikeMcl
authored andcommitted
#217 Compute acos with less cancellation near x=1
1 parent 7f01abd commit 08e5a38

File tree

8 files changed

+141
-7
lines changed

8 files changed

+141
-7
lines changed

decimal.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -744,14 +744,13 @@
744744

745745
Ctor.precision = pr + 6;
746746
Ctor.rounding = 1;
747-
748-
x = x.asin();
749-
halfPi = getPi(Ctor, pr + 4, rm).times(0.5);
747+
748+
x = (new Ctor(1)).minus(x).div(x.plus(1)).sqrt().atan();
750749

751750
Ctor.precision = pr;
752751
Ctor.rounding = rm;
753752

754-
return halfPi.minus(x);
753+
return x.times(2);
755754
};
756755

757756

decimal.mjs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -741,13 +741,12 @@ P.inverseCosine = P.acos = function () {
741741
Ctor.precision = pr + 6;
742742
Ctor.rounding = 1;
743743

744-
x = x.asin();
745-
halfPi = getPi(Ctor, pr + 4, rm).times(0.5);
744+
x = (new Ctor(1)).minus(x).div(x.plus(1)).sqrt().atan();
746745

747746
Ctor.precision = pr;
748747
Ctor.rounding = rm;
749748

750-
return halfPi.minus(x);
749+
return x.times(2);
751750
};
752751

753752

test/hypothesis/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
__pycache__
2+
.hypothesis
3+
.ipynb_checkpoints

test/hypothesis/error_hunt.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
from decimal import Decimal, getcontext
2+
import json
3+
import subprocess
4+
import hypothesis
5+
from hypothesis import given, settings
6+
from hypothesis.strategies import decimals, integers, tuples
7+
from mpmath import mp
8+
import pytest
9+
10+
mp.prec = 500
11+
getcontext().prec = 14
12+
13+
node = subprocess.Popen(
14+
["node", "evaluate.mjs"], stdout=subprocess.PIPE, stdin=subprocess.PIPE
15+
)
16+
17+
18+
def get_decimal(func: str, args: list, config: dict):
19+
arg = json.dumps({"func": func, "args": args, "config": config}).encode() + b"\r"
20+
node.stdin.write(arg)
21+
node.stdin.flush()
22+
return Decimal(node.stdout.readline().strip().decode())
23+
24+
25+
def assert_matches(x, mpfunc, jsfunc=None):
26+
if jsfunc is None:
27+
jsfunc = mpfunc
28+
y = Decimal(str(getattr(mp, mpfunc)(x))) * Decimal("1.0")
29+
z = get_decimal(jsfunc, [str(x)], {"precision": 14})
30+
assert y == z
31+
32+
33+
@pytest.mark.parametrize("fn", "sin cos tan atan asinh".split())
34+
@given(
35+
x=tuples(
36+
decimals(
37+
allow_nan=False, allow_infinity=False, min_value=-1, max_value=1, places=14
38+
),
39+
integers(min_value=-99, max_value=99),
40+
).map(lambda tup: tup[0] * Decimal(10) ** tup[1])
41+
)
42+
@settings(max_examples=100_000)
43+
def test_matches(x, fn):
44+
assert_matches(x, fn)
45+
46+
47+
@pytest.mark.parametrize("fn", "ln log10 sqrt".split())
48+
@given(
49+
x=tuples(
50+
decimals(
51+
allow_nan=False,
52+
allow_infinity=False,
53+
min_value=1e-13,
54+
max_value=1,
55+
places=14,
56+
),
57+
integers(min_value=-99, max_value=99),
58+
).map(lambda tup: tup[0] * Decimal(10) ** tup[1])
59+
)
60+
@settings(max_examples=100_000)
61+
def test_positive_domain(x, fn):
62+
assert_matches(x, fn)
63+
64+
65+
@pytest.mark.parametrize("fn", "asin acos atanh".split())
66+
@given(
67+
x=decimals(
68+
allow_nan=False, allow_infinity=False, min_value=-1, max_value=1, places=14
69+
)
70+
)
71+
@settings(max_examples=100_000)
72+
def test_inverse_trig(x, fn):
73+
assert_matches(x, fn)
74+
75+
76+
@pytest.mark.parametrize("fn", "sinh cosh tanh exp".split())
77+
@given(
78+
x=tuples(
79+
decimals(
80+
allow_nan=False, allow_infinity=False, min_value=-1, max_value=1, places=14
81+
),
82+
integers(min_value=-99, max_value=3),
83+
).map(lambda tup: tup[0] * Decimal(10) ** tup[1])
84+
)
85+
@settings(max_examples=100_000)
86+
def test_small_domain(x, fn):
87+
assert_matches(x, fn)
88+
89+
@given(
90+
x=tuples(
91+
decimals(
92+
allow_nan=False, allow_infinity=False, min_value=1, max_value=10, places=14
93+
),
94+
integers(min_value=0, max_value=99),
95+
).map(lambda tup: tup[0] * Decimal(10) ** tup[1])
96+
)
97+
@settings(max_examples=100_000)
98+
def test_acosh(x):
99+
assert_matches(x, 'acosh')

test/hypothesis/evaluate.mjs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// listen for test cases, and provide the results.
2+
// give JSON of the test case, receive the result
3+
// Example:
4+
// > {"func":"tan", "args":["12.5"], "config":{"precision":8}}
5+
// -0.066468242
6+
7+
8+
import {Decimal} from '../../decimal.mjs';
9+
import {createInterface} from 'readline';
10+
11+
const readline = createInterface({
12+
input: process.stdin,
13+
output: process.stdout
14+
});
15+
16+
readline.on("close", () => {console.log('\n'); process.exit(0);});
17+
18+
readline.on("line", (line) => {
19+
if (line) {
20+
const {func, args, config} = JSON.parse(line);
21+
config.defaults = true;
22+
Decimal.set(config);
23+
const result = Decimal[func](...args);
24+
console.log(result);
25+
}
26+
});

test/hypothesis/requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
hypothesis
2+
mpmath
3+
node

test/modules/acos.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ T('acos', function () {
6868
t('0.41923186648524998285699814886092351368793359300547574', 42, 3, '1.13819724675000902666504291062053681280681');
6969
t('-0.19508761025300975791021816036', 27, 1, '1.76714310275532020878366926');
7070
t('0.0623252416', 19, 0, '1.508430664767542249');
71+
t('0.9999999297625', 14, 4, '0.00037479994883195');
72+
t('0.99999999467518', 14, 4, '0.0001031970930281');
73+
t('0.9999999999999999995', 25, 4, '0.000000001000000000000000000041667');
74+
t('0.99999999999999999999995', 30, 4, '0.0000000000100000000000000000000000416667');
7175

7276
/*
7377
t('0.95', 6, 5, '0.31756');

test/modules/sin.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,4 +119,5 @@ T('sin', function () {
119119
t('22011131111011111.111111611113111111111151111111', 143, 2, '0.82582504036277799386306063085803210583251158969990606609364360685569588545519071481543672724620118406694191888115120286393881609546697317692404');
120120
t('996270725099515169352424536636186062915113219400094989.8763797268889422850038402633796294758036260533902551191769915343780424028900449342752548782035', 46, 2, '0.6613706114081017074779805460666900787572253475');
121121
t('0.780360750628373', 37, 5, '0.7035358359376557390803090830882458906');
122+
t('5900', 14, 4, '0.088879123681079');
122123
});

0 commit comments

Comments
 (0)