Math.clamp method?
Tagir Valeev
amaembo at gmail.com
Fri Jan 27 10:46:32 UTC 2023
Hello, all!
Thank you for an interesting discussion. As I see some interest
towards this feature, I've created a new issue to track this:
https://bugs.openjdk.org/browse/JDK-8301226
I haven't found an exact duplicate, only an issue with related
discussion JDK-4466549. Feel free to link duplicates if you can search
JIRA better than me :-)
On Thu, Jan 26, 2023 at 12:47 AM John Rose <john.r.rose at oracle.com> wrote:
>
> Dealing with numeric ranges is error-prone, so I welcome these.
>
> Another operation on ranges is membership. This is easy to derive
> from the clamp methods, I think:
>
> checkRange(x,min,max) := clamp(x,min,max) == x
checkRange relates somehow to the methods we have in
java.util.Objects, namely checkFromIndexSize, checkIndex, etc. Though
these methods are throwing an exception, rather than returning false.
This could be considered as a separate enhancement.
> But I think there is no harm and real benefit from widening
> the value parameters, so value is always either long or double.
> That will save casts (which also cause bugs) for some use
> cases, such as doing 32x32-to-64-bit arithmetic followed
> by overflow checks and/or saturation. That’s a common use
> case for clamping.
>
> public static int clamp(long value, int min, int max)
> public static long clamp(long value, long min, long max)
> public static double clamp(double value, double min, double max)
> public static float clamp(double value, float min, float max)
I really like the idea of int clamp(long value, int min, int max), as
this makes the method much more useful. On the other hand, I have
doubles about
float clamp(double value, float min, float max), as it could be called
accidentally and we will have accidental precision loss. E.g., imagine
the following code:
// bounds are declared as floats for some reason
// this usually doesn't matter, as these numbers are exact in float
and in double
float lower = 0.0f;
float upper = 10.0f;
double v = 1/3.;
System.out.println(v);
v = Math.clamp(v, lower, upper); // oops!
System.out.println(v);
Having float clamp(double, float, float), this method will be linked
here, resulting in accidental precision loss. The output is:
0.3333333333333333
0.3333333432674408
Given that float type is rarely used, I'd stick to the following signatures:
public static int clamp(long value, int min, int max)
public static long clamp(long value, long min, long max)
public static double clamp(double value, double min, double max)
public static float clamp(float value, float min, float max)
> I thought about cross-domain clamps, which preserve 64 bit
> precision and double range at the same time in different places:
>
> public static long clamp(long value, double min, double max)
> public static long clamp(double value, long min, long max)
This is even more dangerous, as simply calling
double value = ...
value = clamp(value, 0, 1); // not 0.0 and 1.0
you will get 0 instead of the original value that fits the range.
I imagine that many people will step on this. If these methods are desired,
it would be better to name them differently, to avoid overload ambiguity.
For now, I would avoid this.
> The subexpression max-min never overflows, when it
> is regarded as an unsigned number, and the “>>>1”
> immediately converts that unsigned number into the
> correctly signed positive number. That’s the only
> way to avoid overflow for all inputs. The third
> idiom above (the “smartest” of the three) will fail,
> for example, if you feed it negative values.
> For some uses that is OK, but for others it isn’t.
>
> public static int midpoint(int min, int max)
> public static long midpoint(long min, long max)
> public static double midpoint(double min, double max)
> public static float midpoint(float min, float max)
midpoint is another great idea, I like it! Could also be considered separately.
With best regards,
Tagir Valeev.
More information about the core-libs-dev
mailing list