gluecodium
gluecodium copied to clipboard
When generating equality operator for Float and Double properties, consider an epsilon
In case of Float
and Double
properties, the equality check should utilise an epsilon as exact equality check is misleading. For example, we have a SpeedLimit
struct:
@Equatable
struct SpeedLimit {
special_speed_situations: List<SpecialSpeedSituation> = []
advisory_speed_limit_in_meters_per_second: Double? = null
snow_speed_limit_in_meters_per_second: Double? = null
rain_speed_limit_in_meters_per_second: Double? = null
fog_speed_limit_in_meters_per_second: Double? = null
optimal_weather_speed_limit_in_meters_per_second: Double? = null
school_zone_speed_limit_in_meters_per_second: Double? = null
time_dependent_speed_limit_in_meters_per_second: Double? = null
fun effective_speed_limit_in_meters_per_second(): Double?
...
}
The Gluecodium generated equality operator:
bool SpeedLimit::operator==( const SpeedLimit& rhs ) const
{
return ( ( speedLimitInMetersPerSecond && rhs.speedLimitInMetersPerSecond )
? ( *speedLimitInMetersPerSecond == *rhs.speedLimitInMetersPerSecond )
: ( static_cast< bool >( speedLimitInMetersPerSecond ) == static_cast< bool >( rhs.speedLimitInMetersPerSecond ) ) ) &&
specialSpeedSituations == rhs.specialSpeedSituations &&
( ( advisorySpeedLimitInMetersPerSecond && rhs.advisorySpeedLimitInMetersPerSecond )
? ( *advisorySpeedLimitInMetersPerSecond == *rhs.advisorySpeedLimitInMetersPerSecond )
: ( static_cast< bool >( advisorySpeedLimitInMetersPerSecond ) == static_cast< bool >( rhs.advisorySpeedLimitInMetersPerSecond ) ) ) &&
( ( snowSpeedLimitInMetersPerSecond && rhs.snowSpeedLimitInMetersPerSecond )
? ( *snowSpeedLimitInMetersPerSecond == *rhs.snowSpeedLimitInMetersPerSecond )
: ( static_cast< bool >( snowSpeedLimitInMetersPerSecond ) == static_cast< bool >( rhs.snowSpeedLimitInMetersPerSecond ) ) ) &&
( ( rainSpeedLimitInMetersPerSecond && rhs.rainSpeedLimitInMetersPerSecond )
? ( *rainSpeedLimitInMetersPerSecond == *rhs.rainSpeedLimitInMetersPerSecond )
: ( static_cast< bool >( rainSpeedLimitInMetersPerSecond ) == static_cast< bool >( rhs.rainSpeedLimitInMetersPerSecond ) ) ) &&
( ( fogSpeedLimitInMetersPerSecond && rhs.fogSpeedLimitInMetersPerSecond )
? ( *fogSpeedLimitInMetersPerSecond == *rhs.fogSpeedLimitInMetersPerSecond )
: ( static_cast< bool >( fogSpeedLimitInMetersPerSecond ) == static_cast< bool >( rhs.fogSpeedLimitInMetersPerSecond ) ) ) &&
( ( optimalWeatherSpeedLimitInMetersPerSecond && rhs.optimalWeatherSpeedLimitInMetersPerSecond )
? ( *optimalWeatherSpeedLimitInMetersPerSecond == *rhs.optimalWeatherSpeedLimitInMetersPerSecond )
: ( static_cast< bool >( optimalWeatherSpeedLimitInMetersPerSecond ) == static_cast< bool >( rhs.optimalWeatherSpeedLimitInMetersPerSecond ) ) ) &&
( ( schoolZoneSpeedLimitInMetersPerSecond && rhs.schoolZoneSpeedLimitInMetersPerSecond )
? ( *schoolZoneSpeedLimitInMetersPerSecond == *rhs.schoolZoneSpeedLimitInMetersPerSecond )
: ( static_cast< bool >( schoolZoneSpeedLimitInMetersPerSecond ) == static_cast< bool >( rhs.schoolZoneSpeedLimitInMetersPerSecond ) ) ) &&
( ( timeDependentSpeedLimitInMetersPerSecond && rhs.timeDependentSpeedLimitInMetersPerSecond )
? ( *timeDependentSpeedLimitInMetersPerSecond == *rhs.timeDependentSpeedLimitInMetersPerSecond )
: ( static_cast< bool >( timeDependentSpeedLimitInMetersPerSecond ) == static_cast< bool >( rhs.timeDependentSpeedLimitInMetersPerSecond ) ) );
}
Note that when checking the equality of Double values, it looks for exact equality! Instead, for the Float
and Double
properties, we should utilize an epsilon value when two such properties are compared for equality. Ideally, Float
and Double
should utilize two default epsilon values which are defaulted to some values when not specified at all and let each property specify its custom epsilon.
@Equatable
@DoubleEpsilon(0.01) # Default epsilon value for the Double's
@FloatEpsilon(0.05) # Default epsilon value for the Float's
struct SpeedLimit {
. . .
@DoubleEpsilon(0.001) # Custom epsilon value for this Double property,
# it will generate a warning (or error?) if the parent
# class/struct is not @Equatable
advisory_speed_limit_in_meters_per_second: Double? = null
. . .
}
I'd propose to simplify the custom epsilons to just one @Epsilon()
attribute, with the following semantics:
- applicable per-field only, not on struct level (to avoid the need to distinguish
Float
andDouble
) - specifying the epsilon in a non-
@Equatable
struct does nothing (that's the general approach for attributes we have) - a
Float
orDouble
field with no@Epsilon
works as before (i.e. direct comparison). This avoids the need for a hard-coded default epsilon. Also gives the IDL author full control over the feature.
I agree with your simplification.
Using epsilon on equality breaks A = B -> A.hash = B.hash invariants.