Skip to content

Commit b6e064d

Browse files
committed
Fix ComplexBuiltin.__abs__ performance
1 parent 0ddcc33 commit b6e064d

File tree

1 file changed

+126
-15
lines changed
  • graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/complex

1 file changed

+126
-15
lines changed

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/complex/ComplexBuiltins.java

Lines changed: 126 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@
6363
import static com.oracle.graal.python.nodes.SpecialMethodNames.__STR__;
6464
import static com.oracle.graal.python.nodes.SpecialMethodNames.__SUB__;
6565
import static com.oracle.graal.python.nodes.SpecialMethodNames.__TRUEDIV__;
66-
import static com.oracle.graal.python.runtime.exception.PythonErrorType.OverflowError;
6766

6867
import java.util.List;
6968

@@ -104,27 +103,139 @@ public abstract static class AbsNode extends PythonUnaryBuiltinNode {
104103

105104
public abstract double executeDouble(Object arg);
106105

107-
public static AbsNode create() {
108-
return ComplexBuiltinsFactory.AbsNodeFactory.create();
109-
}
110-
111106
@Specialization
112107
double abs(PComplex c) {
113-
if (!Double.isFinite(c.getReal()) || !Double.isFinite(c.getImag())) {
114-
if (Double.isInfinite(c.getReal())) {
115-
return Math.abs(c.getReal());
116-
} else if (Double.isInfinite(c.getImag())) {
117-
return Math.abs(c.getImag());
108+
double x = c.getReal();
109+
double y = c.getImag();
110+
if (Double.isInfinite(x) || Double.isInfinite(y)) {
111+
return Double.POSITIVE_INFINITY;
112+
} else if (Double.isNaN(x) || Double.isNaN(y)) {
113+
return Double.NaN;
114+
} else {
115+
116+
final int expX = getExponent(x);
117+
final int expY = getExponent(y);
118+
if (expX > expY + 27) {
119+
// y is neglectible with respect to x
120+
return abs(x);
121+
} else if (expY > expX + 27) {
122+
// x is neglectible with respect to y
123+
return abs(y);
118124
} else {
119-
return Double.NaN;
125+
126+
// find an intermediate scale to avoid both overflow and
127+
// underflow
128+
final int middleExp = (expX + expY) / 2;
129+
130+
// scale parameters without losing precision
131+
final double scaledX = scalb(x, -middleExp);
132+
final double scaledY = scalb(y, -middleExp);
133+
134+
// compute scaled hypotenuse
135+
final double scaledH = Math.sqrt(scaledX * scaledX + scaledY * scaledY);
136+
137+
// remove scaling
138+
return scalb(scaledH, middleExp);
120139
}
121140
}
122-
double r = Math.hypot(c.getReal(), c.getImag());
123-
if (Double.isInfinite(r)) {
124-
throw raise(OverflowError, ErrorMessages.ABSOLUTE_VALUE_TOO_LARGE);
141+
}
142+
143+
private static final long MASK_NON_SIGN_LONG = 0x7fffffffffffffffL;
144+
145+
static double abs(double x) {
146+
return Double.longBitsToDouble(MASK_NON_SIGN_LONG & Double.doubleToRawLongBits(x));
147+
}
148+
149+
static double scalb(final double d, final int n) {
150+
151+
// first simple and fast handling when 2^n can be represented using
152+
// normal numbers
153+
if ((n > -1023) && (n < 1024)) {
154+
return d * Double.longBitsToDouble(((long) (n + 1023)) << 52);
155+
}
156+
157+
// handle special cases
158+
if (Double.isNaN(d) || Double.isInfinite(d) || (d == 0)) {
159+
return d;
125160
}
126-
return r;
161+
if (n < -2098) {
162+
return (d > 0) ? 0.0 : -0.0;
163+
}
164+
if (n > 2097) {
165+
return (d > 0) ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY;
166+
}
167+
168+
// decompose d
169+
final long bits = Double.doubleToRawLongBits(d);
170+
final long sign = bits & 0x8000000000000000L;
171+
int exponent = ((int) (bits >>> 52)) & 0x7ff;
172+
long mantissa = bits & 0x000fffffffffffffL;
173+
174+
// compute scaled exponent
175+
int scaledExponent = exponent + n;
176+
177+
if (n < 0) {
178+
// we are really in the case n <= -1023
179+
if (scaledExponent > 0) {
180+
// both the input and the result are normal numbers, we only
181+
// adjust the exponent
182+
return Double.longBitsToDouble(sign | (((long) scaledExponent) << 52) | mantissa);
183+
} else if (scaledExponent > -53) {
184+
// the input is a normal number and the result is a subnormal
185+
// number
186+
187+
// recover the hidden mantissa bit
188+
mantissa = mantissa | (1L << 52);
189+
190+
// scales down complete mantissa, hence losing least significant
191+
// bits
192+
final long mostSignificantLostBit = mantissa & (1L << (-scaledExponent));
193+
mantissa = mantissa >>> (1 - scaledExponent);
194+
if (mostSignificantLostBit != 0) {
195+
// we need to add 1 bit to round up the result
196+
mantissa++;
197+
}
198+
return Double.longBitsToDouble(sign | mantissa);
199+
200+
} else {
201+
// no need to compute the mantissa, the number scales down to 0
202+
return (sign == 0L) ? 0.0 : -0.0;
203+
}
204+
} else {
205+
// we are really in the case n >= 1024
206+
if (exponent == 0) {
207+
208+
// the input number is subnormal, normalize it
209+
while ((mantissa >>> 52) != 1) {
210+
mantissa = mantissa << 1;
211+
--scaledExponent;
212+
}
213+
++scaledExponent;
214+
mantissa = mantissa & 0x000fffffffffffffL;
215+
216+
if (scaledExponent < 2047) {
217+
return Double.longBitsToDouble(sign | (((long) scaledExponent) << 52) | mantissa);
218+
} else {
219+
return (sign == 0L) ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY;
220+
}
221+
222+
} else if (scaledExponent < 2047) {
223+
return Double.longBitsToDouble(sign | (((long) scaledExponent) << 52) | mantissa);
224+
} else {
225+
return (sign == 0L) ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY;
226+
}
227+
}
228+
}
229+
230+
static int getExponent(final double d) {
231+
// NaN and Infinite will return 1024 anywho so can use raw bits
232+
return (int) ((Double.doubleToRawLongBits(d) >>> 52) & 0x7ff) - 1023;
233+
}
234+
235+
public static AbsNode create() {
236+
return ComplexBuiltinsFactory.AbsNodeFactory.create();
127237
}
238+
128239
}
129240

130241
@Builtin(name = __ADD__, minNumOfPositionalArgs = 2)

0 commit comments

Comments
 (0)