Reputation: 5122
Is there a nice way to clamp (clip?, coerce?) a value to a range in Ada? I have done that for now:
timer := Integer'Max(timer, Integer(Half_Word'First));
timer := Integer'Min(timer, Integer(Half_Word'Last));
TIM8.TIM.ARR := Half_Word(timer);
But this is a bit clunky, I'd like to clamp the variable timer into the range of Half_Word.
Upvotes: 1
Views: 344
Reputation: 4198
You can use defaults to make this a bit easier to use -- the biggest problem [maintenance-wise] then is that it looks like there's more going on than there really is, and the biggest drawback to this is that the constraint of DESTINATION_TYPE
being a subset of SOURCE_TYPE
is not checked/enforced at compile-time by the compiler.
generic
type Source_Type is range <>;
type Destination_Type is range <>;
S_First : Long_Long_Integer:= Long_Long_Integer(Source_Type'First); --' FIX FOR
S_Last : Long_Long_Integer:= Long_Long_Integer(Source_Type'Last); --' BUGGY
D_First : Long_Long_Integer:= Long_Long_Integer(Destination_Type'First);--' SYNTAX
D_Last : Long_Long_Integer:= Long_Long_Integer(Destination_Type'Last); --' HIGHLIGHT.
function Saturate (X : Source_Type) return Destination_Type
with Pre =>
D_First >= S_First
and D_Last <= S_Last;
function Saturate (X : Source_Type) return Destination_Type is
X_Pos : constant Long_Long_Integer:= Long_Long_Integer(X);
begin
if X_Pos in D_First..D_Last then
return Destination_Type(X);
elsif X_Pos < D_First then
return Destination_Type'First; --'
else
return Destination_Type'Last; --'
end if;
end Saturate;
Example usage:
subtype K is Integer range 2..24;
subtype J is Integer range 3..10;
Function P is new Saturate(Source_Type => K,
Destination_Type => J
);
Upvotes: 1
Reputation: 31699
Ada does not have a built-in way to do this. If this is the kind of thing you will be doing repeatedly, and on different types, a generic like this might help:
generic
type Source_Type is range <>;
type Destination_Type is range <>;
function Saturate (X : Source_Type) return Destination_Type;
function Saturate (X : Source_Type) return Destination_Type is
Result : Source_Type;
begin
Result := Source_Type'Max (X, Source_Type (Destination_Type'First));
Result := Source_Type'Min (Result, Source_Type (Destination_Type'Last));
return Destination_Type (Result);
end Saturate;
...
function Saturate_To_Half_Word is new Saturate (Integer, Half_Word);
...
TIM8.TIM.ARR := Saturate_To_Half_Word(timer);
EDIT: The above assumes that the bounds of Destination_Type
will always be included in Source_Type
. The code will raise a Constraint_Error
if this is not the case. An alternative that will work even if Destination_Type
could be larger than Source_Type
:
function Saturate (X : Source_Type) return Destination_Type is
type Largest_Int is range System.Min_Int .. System.Max_Int;
Result : Source_Type;
begin
Result := X;
if Largest_Int (Destination_Type'First) in
Largest_Int (Source_Type'First) .. Largest_Int (Source_Type'Last) then
Result := Source_Type'Max (Result, Source_Type (Destination_Type'First));
end if;
if Largest_Int (Destination_Type'Last) in
Largest_Int (Source_Type'First) .. Largest_Int (Source_Type'Last) then
Result := Source_Type'Min (Result, Source_Type (Destination_Type'Last));
end if;
return Destination_Type (Result);
end Saturate;
You have to code it carefully to avoid type conflicts but also avoid raising Constraint_Error
.
EDIT 2: I had First
and Last
backwards. Fixed.
EDIT 3: Following Simon's suggestion, this will also work:
function Saturate (X : Source_Type) return Destination_Type is
type Largest_Int is range System.Min_Int .. System.Max_Int;
Result : Largest_Int;
begin
Result := Largest_Int (X);
Result := Largest_Int'Max (Result, Largest_Int (Destination_Type'First));
Result := Largest_Int'Min (Result, Largest_Int (Destination_Type'Last));
return Destination_Type (Result);
end Saturate;
This looks simpler, but is potentially slightly less efficient because it involves more type conversions at run time. (Note that the complex-looking conditions in the previous example are all conditions that can be evaluated at compile time and therefore should not generate any code--plus it could cause the 'Max
and/or 'Min
operations to be eliminated. This is assuming the compiler uses a "macro expansion" model for generic instantiation, as GNAT does. A really good compiler may be able to figure out the same kinds of optimizations in the latter code too, but it's harder.)
Upvotes: 5