Skip to content

Fix problem of unnecessarily widening float values#768

Open
opeongo wants to merge 3 commits intomasterfrom
Issue_767
Open

Fix problem of unnecessarily widening float values#768
opeongo wants to merge 3 commits intomasterfrom
Issue_767

Conversation

@opeongo
Copy link
Contributor

@opeongo opeongo commented Jul 29, 2024

This is a fix for issue #767 where float expressions are widened to doubles.

Copy link
Member

@nickl- nickl- left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs work.

I'm wondering if there isn't a simpler solution, to use the double functionality as is, but if it is floats we do a shrink (if they fit) else expand to Double/BigDecimal as needed. This will be a much smaller change and less things that can cause additional headaches.

if (lhs instanceof BigDecimal)
return bigDecimalBinaryOperation( (BigDecimal) lhs, (BigDecimal) rhs, kind );
if (lhs instanceof Float)
return floatBinaryOperation( (Float) lhs, (Float) rhs, kind );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will cause a precision problem, and even cast error, if rhs is double.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

binaryOperationImpl only has one usage (in Operators.binaryOperation()), and that is after lhs and rhs have been passed through Operators.promotePrimitves(). At this point if lhs is Float it is not possible for rhs to not be Float.


}
if ( OVERFLOW_OPS.contains(kind) )
return bigDecimalBinaryOperation(BigDecimal.valueOf(lhs), BigDecimal.valueOf(rhs), kind);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we shouldn't first overflow to double here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just stick with Java math please? Some of us depend on it.

case POWER:
case POWERX:
double check = Math.pow(lhs, rhs);
if ( Double.isInfinite(check) )
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we not try and return a float here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Math.pow returns a double, so no one is expecting to get a float here.

lhs = Long.valueOf(lnum.longValue());
if ( !(rhs instanceof Long) )
rhs = Long.valueOf(rnum.longValue());
if (!(lhs instanceof Float && rhs instanceof Float)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is hard to see what happens here but this seems wrong.

From the existing function:

static Object[] promotePrimitives(Object lhs, Object rhs)
{
Number lnum = promoteToInteger(lhs);
Number rnum = promoteToInteger(rhs);
if ( lhs instanceof BigDecimal ) {
if ( !(rhs instanceof BigDecimal) )
rhs = Primitive.castNumber(BigDecimal.class, rnum);
} else if ( rhs instanceof BigDecimal ) {
lhs = Primitive.castNumber(BigDecimal.class, lnum);
} else if ( Types.isFloatingpoint(lhs) || Types.isFloatingpoint(rhs)) {
if ( !(lhs instanceof Double) )
lhs = Double.valueOf(lnum.doubleValue());
if ( !(rhs instanceof Double) )
rhs = Double.valueOf(rnum.doubleValue());
} else if ( lhs instanceof BigInteger ) {
if ( !(rhs instanceof BigInteger) )
rhs = Primitive.castNumber(BigInteger.class, rnum);
} else if ( rhs instanceof BigInteger ) {
lhs = Primitive.castNumber(BigInteger.class, lnum);
} else {
if ( !(lhs instanceof Long) )
lhs = Long.valueOf(lnum.longValue());
if ( !(rhs instanceof Long) )
rhs = Long.valueOf(rnum.longValue());
}
return new Object[] { lhs, rhs };
}

There is a precedence BigDecimal, Double, BigInteger, Long and it seems this check needs to be around line 524 when we are checking for Doubles.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can refactor this but the outcome will be the same.

throw new InterpreterError("Can only shrink wrap Number types");
Number value = (Number) number;
if (value instanceof Float)
return new Primitive(value.floatValue());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this needs to do something similar to int and long, comparing the values instead of the type.

See full function 193 and 195:

private static final BigInteger INTEGER_MAX = BigInteger.valueOf(Integer.MAX_VALUE);
private static final BigInteger INTEGER_MIN = BigInteger.valueOf(Integer.MIN_VALUE);
static final BigInteger LONG_MAX = BigInteger.valueOf(Long.MAX_VALUE);
static final BigInteger LONG_MIN = BigInteger.valueOf(Long.MIN_VALUE);
public static Primitive shrinkWrap(Object number) {
if (!(number instanceof Number))
throw new InterpreterError("Can only shrink wrap Number types");
Number value = (Number) number;
if ( Types.isFloatingpoint(number) ) {
if ( !Double.isInfinite(value.doubleValue()) )
return new Primitive(value.doubleValue());
return new Primitive((BigDecimal) number);
}
BigInteger bi = number instanceof BigInteger
? (BigInteger) number : BigInteger.valueOf(value.longValue());
if ( bi.compareTo(INTEGER_MIN) >= 0 && bi.compareTo(INTEGER_MAX) <= 0 )
return new Primitive(bi.intValue());
if ( bi.compareTo(LONG_MIN) >= 0 && bi.compareTo(LONG_MAX) <= 0 )
return new Primitive(bi.longValue());
return new Primitive(bi);
}

I wonder if this would not be sufficient, to just shrink the double result to float if it fits?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you do the widen float to double, do the operation, and then narrow you will get a different result than doing the operation in float. Best to keep this the same as Java.

In Java byte and short use int as a carrier type. Arithmetic on any combination of these types will yield an int. Best to keep this the same as Java.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants