nraynaud
nraynaud

Reputation: 5122

Can I clamp a value into a range in Ada?

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

Answers (2)

Shark8
Shark8

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

ajb
ajb

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

Related Questions