Skip to content

Commit 00410d1

Browse files
committed
Handle some corner cases.
1 parent 504ea87 commit 00410d1

File tree

1 file changed

+49
-6
lines changed

1 file changed

+49
-6
lines changed

src/main/java/org/truffleruby/core/numeric/FloatNodes.java

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -650,18 +650,51 @@ protected Object round(double n,
650650
public abstract static class FloatRoundUpDecimalPrimitiveNode extends PrimitiveArrayArgumentsNode {
651651

652652
@Specialization
653-
protected double roundNDecimal(double n, int ndigits) {
653+
protected double roundNDecimal(double n, int ndigits,
654+
@Cached ConditionProfile boundaryCase) {
654655
long intPart = (long) n;
655656
double s = Math.pow(10.0, ndigits) * Math.signum(n);
656657
double f = (n % 1) * s;
657658
long fInt = (long) f;
658659
double d = f % 1;
659-
if (d > 0.5 || Math.abs(n) - Math.abs((intPart + (fInt + 0.5) / s)) >= 0) {
660+
int limit = Math.getExponent(n) + Math.getExponent(s) - 51;
661+
if (boundaryCase.profile((Math.getExponent(d) <= limit) ||
662+
(Math.getExponent(1.0 - d) <= limit))) {
663+
return findClosest(n, ndigits, s, d);
664+
} else if (d > 0.5 || Math.abs(n) - Math.abs((intPart + (fInt + 0.5) / s)) >= 0) {
660665
fInt += 1;
661666
}
662667
return intPart + fInt / s;
663668
}
669+
}
664670

671+
/* If the rounding result is very near to an integer boundary then we need to find the number that is closest to the
672+
* correct result. If we don't do this then it's possible to get errors in the least significant bit of the result.
673+
* We'll test the adjacent double in the direction closest to the boundary and compare the fractional portions. If
674+
* we're already at the minimum error we'll return the original number as it is already rounded as well as it can
675+
* be. In the case of a tie we return the lower number, otherwise we check the go round again. */
676+
private static double findClosest(double n, int ndigits, double s, double d) {
677+
double n2;
678+
while (true) {
679+
if (d > 0.5) {
680+
n2 = Math.nextAfter(n, n + s);
681+
} else {
682+
n2 = Math.nextAfter(n, n - s);
683+
}
684+
long intPart = (long) n2;
685+
double f = (n2 % 1) * s;
686+
long fInt = (long) f;
687+
double d2 = f % 1;
688+
int limit = Math.getExponent(n2) + Math.getExponent(s) - 52;
689+
if (((d > 0.5) ? 1 - d : d) < ((d2 > 0.5) ? 1 - d2 : d2)) {
690+
return n;
691+
} else if (((d > 0.5) ? 1 - d : d) == ((d2 > 0.5) ? 1 - d2 : d2)) {
692+
return Math.abs(n) < Math.abs(n2) ? n : n2;
693+
} else {
694+
d = d2;
695+
n = n2;
696+
}
697+
}
665698
}
666699

667700
@SuppressFBWarnings("FE_FLOATING_POINT_EQUALITY")
@@ -716,13 +749,18 @@ protected Object round(double n,
716749
public abstract static class FloatRoundEvenDecimalPrimitiveNode extends PrimitiveArrayArgumentsNode {
717750

718751
@Specialization
719-
protected double roundNDecimal(double n, int ndigits) {
752+
protected double roundNDecimal(double n, int ndigits,
753+
@Cached ConditionProfile boundaryCase) {
720754
long intPart = (long) n;
721755
double s = Math.pow(10.0, ndigits) * Math.signum(n);
722756
double f = (n % 1) * s;
723757
long fInt = (long) f;
724758
double d = f % 1;
725-
if (d > 0.5) {
759+
int limit = Math.getExponent(n) + Math.getExponent(s) - 51;
760+
if (boundaryCase.profile((Math.getExponent(d) <= limit) ||
761+
(Math.getExponent(1.0 - d) <= limit))) {
762+
return findClosest(n, ndigits, s, d);
763+
} else if (d > 0.5) {
726764
fInt += 1;
727765
} else if (d == 0.5 || Math.abs(n) - Math.abs((intPart + (fInt + 0.5) / s)) >= 0) {
728766
fInt += fInt % 2;
@@ -775,13 +813,18 @@ protected Object round(double n,
775813
public abstract static class FloatRoundDownDecimalPrimitiveNode extends PrimitiveArrayArgumentsNode {
776814

777815
@Specialization
778-
protected double roundNDecimals(double n, int ndigits) {
816+
protected double roundNDecimal(double n, int ndigits,
817+
@Cached ConditionProfile boundaryCase) {
779818
long intPart = (long) n;
780819
double s = Math.pow(10.0, ndigits) * Math.signum(n);
781820
double f = (n % 1) * s;
782821
long fInt = (long) f;
783822
double d = f % 1;
784-
if (d > 0.5 && Math.abs(n) - Math.abs((intPart + (fInt + 0.5) / s)) > 0) {
823+
int limit = Math.getExponent(n) + Math.getExponent(s) - 51;
824+
if (boundaryCase.profile((Math.getExponent(d) <= limit) ||
825+
(Math.getExponent(1.0 - d) <= limit))) {
826+
return findClosest(n, ndigits, s, d);
827+
} else if (d > 0.5 && Math.abs(n) - Math.abs((intPart + (fInt + 0.5) / s)) > 0) {
785828
fInt += 1;
786829
}
787830
return intPart + fInt / s;

0 commit comments

Comments
 (0)