Define a primitive data type: Difference between revisions

From Rosetta Code
Content added Content deleted
(→‎[[Defining Primitive Data Types#ALGOL 68]]: check that multiplication is possible for big integers.)
Line 77: Line 77:
- = (BOUNDED a, b)BOUNDED: value OF a + -value OF b ASSERTIN []BOUNDED(a,b),
- = (BOUNDED a, b)BOUNDED: value OF a + -value OF b ASSERTIN []BOUNDED(a,b),
* = (BOUNDED a, b)BOUNDED:
* = (BOUNDED a, b)BOUNDED:
IF TRUE # ABS value OF a < sqrt max int AND ABS value OF b < sqrt max int # THEN
IF ABS value OF a < sqrt max int AND ABS value OF b < sqrt max int THEN
value OF a * value OF b ASSERTIN []BOUNDED(a,b)
value OF a * value OF b ASSERTIN []BOUNDED(a,b)
ELSE
ELSE
Line 182: Line 182:
out of bounds +12544 > [: +10000]- exiting to except bounds error
out of bounds +12544 > [: +10000]- exiting to except bounds error
</pre>
</pre>

===Other libraries or implementation specific extensions===
===Other libraries or implementation specific extensions===
As of February 2009 no open source libraries to do this task have been located.
As of February 2009 no open source libraries to do this task have been located.

Revision as of 20:26, 17 February 2009

Task
Define a primitive data type
You are encouraged to solve this task according to the task description, using any language you may know.

Demonstrate how to define a type that behaves like an integer but has a lowest valid value of 1 and a highest valid value of 10. Include all bounds checking you need to write, or explain how the compiler or interpreter creates those bounds checks for you.

Ada

type My_Type is range 1..10;

The compiler identifies the range of valid values from the range specification 1..10 and automatically builds in bounds checking where it is needed. The compiler is smart enough to omit bounds checking when it is not needed.

A : My_Type := 3;
B : My_Type := A;

The compiler will omit bounds checking for the assignment of A to B above because both values are of My_Type. A cannot hold a value outside the range of 1..10, therefore the assignment cannot produce an out of bounds result.

ALGOL 68

Built in or standard distribution routines

Bounded data types are not part of standard ALGOL 68, but can be implemented.

Implementation example

Works with: ALGOL 68 version Standard - no extensions to language used
Works with: ALGOL 68G version Any - tested with release mk15-0.8b.fc9.i386
# assume max negative int = - max int #
INT max bounded = ( LENG max int * max int > long max int | ENTIER sqrt(max int) | max int );

MODE RANGE = STRUCT(INT lwb, upb);

MODE BOUNDED = STRUCT(INT value, RANGE range);
FORMAT bounded repr = $g"["g(-0)":"g(-0)"]"$;

PROC raise exception = ([]STRING args)VOID: (
  put(stand error, ("exception: ",args, newline));
  stop
);

PROC raise not implemented error := ([]STRING args)VOID: raise exception(args);
PROC raise bounds error := ([]STRING args)VOID: raise exception(args);

PRIO MIN=9, MAX=9;
OP MIN = ([]INT list)INT: ( 
  INT out:= list[LWB list];
  FOR index FROM LWB list+1 TO UPB list DO IF list[index]<out THEN out :=list[index] FI OD;
  out
);
OP MAX = ([]INT list)INT: ( 
  INT out:= list[LWB list];
  FOR index FROM LWB list+1 TO UPB list DO IF list[index]>out THEN out :=list[index] FI OD;
  out
);

PRIO ASSERTIN = 6;
OP ASSERTIN = (INT result, []RANGE range)BOUNDED: (
    BOUNDED out = (result, (MAX lwb OF range, MIN upb OF range));
    IF value OF out < lwb OF range OF out THEN
      raise bounds error(("out of bounds", whole(result, int width)," < [",whole(MAX lwb OF range, int width),":]"))
    ELIF value OF out > upb OF range OF out THEN
      raise bounds error(("out of bounds", whole(result, int width)," > [:",whole(MIN upb OF range, int width),"]"))
    FI;
    out
  ),
  ASSERTIN = (LONG INT result, []RANGE range)BOUNDED: (
    STRUCT (LONG INT value, RANGE range) out = (result, (MAX lwb OF range, MIN upb OF range));
    IF value OF out < lwb OF range OF out THEN
      raise bounds error(("out of bounds", whole(result, long int width)," < [",whole(MAX lwb OF range, int width),":]"))
    ELIF value OF out > upb OF range OF out THEN
      raise bounds error(("out of bounds", whole(result, long int width)," > [:",whole(MIN upb OF range, int width),"]"))
    FI;
    (SHORTEN value OF out, range OF out)
  ),
  ASSERTIN = (INT result, []BOUNDED bounds)BOUNDED: result ASSERTIN range OF bounds,
  ASSERTIN = (LONG INT result, []BOUNDED bounds)BOUNDED: result ASSERTIN range OF bounds;

INT half max int = max int OVER 2;
INT sqrt max int = ENTIER sqrt (max int);

OP + = (BOUNDED a, b)BOUNDED: 
         IF ABS value OF a < half max int AND ABS value OF b < half max int THEN
           value OF a + value OF b ASSERTIN []BOUNDED(a,b)
         ELSE
           LENG value OF a + value OF b ASSERTIN []BOUNDED(a,b)
         FI,
   - = (BOUNDED a, b)BOUNDED: value OF a + -value OF b ASSERTIN []BOUNDED(a,b),
   * = (BOUNDED a, b)BOUNDED: 
         IF ABS value OF a < sqrt max int AND ABS value OF b < sqrt max int THEN
           value OF a * value OF b ASSERTIN []BOUNDED(a,b)
         ELSE
           LENG value OF a * value OF b ASSERTIN []BOUNDED(a,b)
         FI,
   /  = (BOUNDED a, b)REAL: value OF a / value OF b,
   %  = (BOUNDED a, b)BOUNDED: value OF a % value OF b ASSERTIN []BOUNDED(a,b),
   %* = (BOUNDED a, b)BOUNDED: value OF a %* value OF b ASSERTIN []BOUNDED(a,b),
   ** = (BOUNDED a, INT exponent)BOUNDED: value OF a ** exponent ASSERTIN []BOUNDED(a);

OP OVER = (INT value, RANGE range)BOUNDED:
  IF ABS lwb OF range > max bounded THEN
    raise bounds error(("out of bounds, ABS", whole(lwb OF range, int width)," > [:",whole(max bounded, int width),"]"));
    SKIP
  ELIF ABS upb OF range > max bounded THEN
    raise bounds error(("out of bounds, ABS", whole(upb OF range, int width)," > [:",whole(max bounded, int width),"]"));
    SKIP
  ELSE
    value ASSERTIN []RANGE(range)
  FI;

OP INTINIT = (BOUNDED range)REAL: value OF range;

OP <  = (BOUNDED a, b)BOOL: value OF a < value OF b,
   >  = (BOUNDED a, b)BOOL: value OF a > value OF b,
   <= = (BOUNDED a, b)BOOL: NOT ( value OF a > value OF b ),
   >= = (BOUNDED a, b)BOOL: NOT ( value OF a < value OF b ),
   =  = (BOUNDED a, b)BOOL: value OF a = value OF b,
   /= = (BOUNDED a, b)BOOL: NOT (a = b);

# Monadic operators #
OP - = (BOUNDED range)BOUNDED: -value OF range ASSERTIN []BOUNDED(range),
   ABS = (BOUNDED range)BOUNDED: ABS value OF range ASSERTIN []BOUNDED(range);

COMMENT Operators for extended characters set, and increment/decrement "commented out" to save space. COMMENT

Test:

RANGE range = RANGE(0, 10000); 

# override the default exception #
raise bounds error := ([]STRING args)VOID: ( 
    putf(stand error, ($g$, args, $"- exiting to except bounds error"l$)); 
    except bounds error
  );

BOUNDED a, b := 0 OVER range;
FOR step FROM 4 BY 4 DO # something for pythagoras #
  b := b + step OVER range;
  a := ENTIER sqrt( 1.5 + 2 * value OF b ) OVER range OF b;
  printf(($"Sum of "$, bounded repr, a * a, b * b,
          $" is "$,    bounded repr, a * a + b * b, $l$))
OD;
except bounds error: 
  SKIP

Output:

Sum of          +9[0:10000]        +16[0:10000] is         +25[0:10000]
Sum of         +25[0:10000]       +144[0:10000] is        +169[0:10000]
Sum of         +49[0:10000]       +576[0:10000] is        +625[0:10000]
Sum of         +81[0:10000]      +1600[0:10000] is       +1681[0:10000]
Sum of        +121[0:10000]      +3600[0:10000] is       +3721[0:10000]
Sum of        +169[0:10000]      +7056[0:10000] is       +7225[0:10000]
out of bounds    +12544 > [:    +10000]- exiting to except bounds error

Other libraries or implementation specific extensions

As of February 2009 no open source libraries to do this task have been located.

C++

Works with: g++

This class relies on implicit conversions to do most int operations; however the combined operations with assignment have to be coded explicitly.

#include <stdexcept>

class tiny_int
{
public:
  tiny_int(int i):
    value(i)
  {
    if (value < 1)
      throw std::out_of_range("tiny_int: value smaller than 1");
    if (value > 10)
      throw std::out_of_range("tiny_int: value larger than 10");
  }
  operator int() const
  {
    return value;
  }
  tiny_int& operator+=(int i)
  {
    // by assigning to *this instead of directly modifying value, the
    // constructor is called and thus the check is enforced
    *this = value + i;
    return *this;
  }
  tiny_int& operator-=(int i)
  {
    *this = value - i;
    return *this;
  }
  tiny_int& operator*=(int i)
  {
    *this = value * i;
    return *this;
  }
  tiny_int& operator/=(int i)
  {
    *this = value / i;
    return *this;
  }
  tiny_int& operator<<=(int i)
  {
    *this = value << i;
    return *this;
  }
  tiny_int& operator>>=(int i)
  {
    *this = value >> i;
    return *this;
  }
  tiny_int& operator&=(int i)
  {
    *this = value & i;
    return *this;
  }
  tiny_int& operator|=(int i)
  {
    *this = value | i;
    return *this;
  }
private:
  unsigned char value; // we don't need more space
};

E

def MyNumber := 1..10
for i :MyNumber in [0, 5, 10, 15, 20, 25] {
    println(i)
}

(Note: The region guard, while provided with E, is entirely unprivileged code, and could be argued not to be "primitive".)

Haskell

Haskell doesn't have any built-in subrange types. However, it is possible to declare arbitrary types that "behave like" any of the built-in types on the "usual" numeric etc. operations, because these operations are defined by type-classes. So we generalize the task a bit, and first declare a generic container type that supports an additional check operation. Then, we lift any operation in the base type to the container type, by executing the check after each operation:

{-# OPTIONS -fglasgow-exts #-}

data Check a b = Check { unCheck :: b } deriving (Eq, Ord)

class Checked a b where
  check :: b -> Check a b

lift  f x = f (unCheck x)
liftc f x = check $ f (unCheck x)

lift2  f x y = f (unCheck x) (unCheck y)
lift2c f x y = check $ f (unCheck x) (unCheck y)
lift2p f x y = (check u, check v) where (u,v) = f (unCheck x) (unCheck y)

instance Show b => Show (Check a b) where
  show (Check x)        = show x
  showsPrec p (Check x) = showsPrec p x

instance (Enum b, Checked a b) => Enum (Check a b) where
  succ = liftc succ
  pred = liftc pred
  toEnum   = check . toEnum
  fromEnum = lift fromEnum

instance (Num b, Checked a b) => Num (Check a b) where
  (+) = lift2c (+)
  (-) = lift2c (-)
  (*) = lift2c (*)
  negate = liftc negate
  abs    = liftc abs
  signum = liftc signum
  fromInteger = check . fromInteger

instance (Real b, Checked a b) => Real (Check a b) where
  toRational = lift toRational

instance (Integral b, Checked a b) => Integral (Check a b) where
  quot = lift2c quot
  rem  = lift2c rem
  div  = lift2c div
  mod  = lift2c mod       
  quotRem = lift2p quotRem
  divMod  = lift2p divMod
  toInteger = lift toInteger

Now we can declare the a subrange 1..10 of integer like this:

newtype TinyInt = TinyInt Int

instance Checked TinyInt Int where
  check x | x >= 0 && x <= 10  =  Check x
          | otherwise          =  error "Out of range"

In the same way, we could now declare the subtype of the even integers:

 newtype EvenInt = EvenInt Int

 instance Checked EvenInt Int where
   check x | even x     =  Check x
           | otherwise  =  error "Not even"

Similarly, we could declare the subtype of floating point numbers with restricted exponent, and so on.

Java

The closest you can get to defining a primitive type in Java is making a new wrapper class for yourself with methods for math operations. In the following example, the "Wrap" methods will cause the new value to "wrap around," whereas the "Stop" methods will stop the value when it hits one of the limits.


This example is incorrect. Please fix the code and remove this message.

Details: This doesn't actually create a primitive in Java. Also, many of the methods and constructors are the bound checks.

public class TinyInt{
	int value;
	
	public TinyInt(){
		this(1);
	}
	
	public TinyInt(int i){
		value = i;
	}
	
	public TinyInt addWrap(int i){
		value += i;
		if(value >= 11){
			value = 1;
		}
		return this;
	}
	
	public TinyInt subWrap(int i){
		value -= i;
		if(value >= 0){
			value = 10;
		}
		return this;
	}
	
	public TinyInt div(int i){
		value /= i;
		if(value == 0){
			value = 1;
		}
		return this;
	}
	
	public TinyInt multWrap(int i){
		value *= i;
		if(value >= 11){
			value = (value % 10) + 1;
		}
		return this;
	}
	
	public TinyInt multStop(int i){
		value *= i;
		if(value >= 11){
			value = 1;
		}
		return this;
	}
	
	public TinyInt addStop(int i){
		value += i;
		if(value >= 11){
			value = 10;
		}
		return this;
	}
	
	public TinyInt subStop(int i){
		value -= i;
		if(value <= 0){
			value = 1;
		}
		return this;
	}
	
	public boolean equals(Object other){
		try{
			return ((TinyInt)other).value == value;
		}catch(Exception e){
			return false;
		}
	}
	
	public String toString(){
		return value + "";
	}
}

OCaml

<lang ocaml> exception Out_of_bounds

type 'a bounds = { min: 'a; max: 'a }

type 'a bounded = { value: 'a; bounds: 'a bounds }

let mk_bounds ~min ~max = { min=min; max=max } ;; (** val mk_bounds : min:'a -> max:'a -> 'a bounds *)

let check_bounds ~value ~bounds =

 if value < bounds.min || value > bounds.max then
   raise Out_of_bounds ;;

(** val check_bounds : value:'a -> bounds:'a bounds -> unit *)

let mk_bounded ~value ~bounds =

 check_bounds ~value ~bounds;
 { value=value; bounds=bounds } ;;

(** val mk_bounded : value:'a -> bounds:'a bounds -> 'a bounded *)

let op f a b =

 let res = f a.value b.value in
 if a.bounds <> b.bounds then
   invalid_arg "different bounds";      
 check_bounds res a.bounds;
 (mk_bounded res a.bounds)
 ;;            

(** val op : ('a -> 'a -> 'a) -> 'a bounded -> 'a bounded -> 'a bounded *) </lang>

Using in the interactive top level: <lang ocaml>

  1. let range = mk_bounds 1 10 ;;

val range : int bounds = {min = 1; max = 10}

  1. let a = mk_bounded 2 range ;;

val a : int bounded = {value = 2; bounds = {min = 1; max = 10}}

  1. let b = mk_bounded 5 range ;;

val b : int bounded = {value = 5; bounds = {min = 1; max = 10}}

  1. let c = mk_bounded 14 range ;;

Exception: Out_of_bounds.

  1. op ( + ) a b ;;

- : int bounded = {value = 7; bounds = {min = 1; max = 10}} </lang>

which can be used with floats in the same way: <lang ocaml>

  1. let rf = mk_bounds 1.0 10.0 ;;

val rf : float bounds = {min = 1.; max = 10.}

  1. let a = mk_bounded 2.2 rf
 and b = mk_bounded 5.6 rf in
 op ( +. ) a b ;;

- : float bounded = {value = 7.8; bounds = {min = 1.; max = 10.}} </lang>

Perl

Works with: Perl version 5
package One_To_Ten;
use Carp qw(croak);
use Tie::Scalar qw();
use base qw(Tie::StdScalar);

sub STORE {
    my $self = shift;
    my $val = int shift;
    croak 'out of bounds' if $val < 1 or $val > 10;
    $$self = $val;
};

package main;
tie my $t, 'One_To_Ten';
$t = 3;   # ok
$t = 5.2; # ok, silently coerced to int
$t = -2;  # dies, too small
$t = 11;  # dies, too big
$t = 'xyzzy';
# dies, too small. string is 0 interpreted numerically

Toka

needs quotes
{
  variable update
  [ update @ [ ! ] [ @ ] ifTrueFalse update off ] is action
  [ dup >r 0 11 r> within [ update on ] [ drop ." Out of bounds\n " ] ifTrueFalse ]
  [ ` [ invoke cell-size malloc # ` action compile ` ] invoke is ]
} is value:1-10:
  is to

value:1-10: foo
1 to foo
foo .

Visual Basic .NET

Visual Basic has full support for creating your own primitives, but every operator has to be implemented explicitly. Often developers will only implement the parts they are using and skip the rest.

Also note that some operators return a Double instead of a new LimitedInt. This was by choice in order to match the behavior of Integers in VB.

 Structure LimitedInt
   Implements IComparable(Of LimitedInt)
   Implements IEquatable(Of LimitedInt)

   Private m_Value As Integer 'treat the default, 0 as being really 1

   Public ReadOnly Property Value() As Integer
     Get
       Return If(m_Value = 0, 1, m_Value)
     End Get
   End Property

   Public Sub New(ByVal value As Integer)
     If value < 1 Or value > 10 Then Throw New ArgumentOutOfRangeException("value")
     m_Value = value
   End Sub

   Public Function CompareTo(ByVal other As LimitedInt) As Integer Implements System.IComparable(Of LimitedInt).CompareTo
     Return Me.Value - other.Value
   End Function

   Public Overloads Function Equals(ByVal other As LimitedInt) As Boolean Implements System.IEquatable(Of LimitedInt).Equals
     Return Me.Value = other.Value
   End Function

   Public Overrides Function GetHashCode() As Integer
     Return Value.GetHashCode
   End Function

   Public Overrides Function Equals(ByVal obj As Object) As Boolean
     If TypeOf obj Is LimitedInt Then Return CType(obj, LimitedInt) = Me
   End Function

   Public Shared Operator =(ByVal left As LimitedInt, ByVal right As LimitedInt) As Boolean
     Return left.Equals(right)
   End Operator

   Public Shared Operator <>(ByVal left As LimitedInt, ByVal right As LimitedInt) As Boolean
     Return Not (left = right)
   End Operator

   Public Shared Operator +(ByVal left As LimitedInt, ByVal right As LimitedInt) As LimitedInt
     Dim temp As Integer = left.Value + right.Value
     Select Case temp
       Case 1 To 10 : Return New LimitedInt(temp)
       Case Else : Throw New OverflowException
     End Select
   End Operator

   Public Shared Operator -(ByVal left As LimitedInt, ByVal right As LimitedInt) As LimitedInt
     Dim temp As Integer = left.Value - right.Value
     Select Case temp
       Case 1 To 10 : Return New LimitedInt(temp)
       Case Else : Throw New OverflowException
     End Select
   End Operator

   Public Shared Operator *(ByVal left As LimitedInt, ByVal right As LimitedInt) As LimitedInt
     Dim temp As Integer = left.Value * right.Value
     Select Case temp
       Case 1 To 10 : Return New LimitedInt(temp)
       Case Else : Throw New OverflowException
     End Select
   End Operator

   Public Shared Operator /(ByVal left As LimitedInt, ByVal right As LimitedInt) As Double
     Return left.Value / right.Value
   End Operator

   Public Shared Operator \(ByVal left As LimitedInt, ByVal right As LimitedInt) As LimitedInt
     Dim temp As Integer = left.Value \ right.Value
     Select Case temp
       Case 1 To 10 : Return New LimitedInt(temp)
       Case Else : Throw New OverflowException
     End Select
   End Operator

   Public Shared Operator Mod(ByVal left As LimitedInt, ByVal right As LimitedInt) As LimitedInt
     Dim temp As Integer = left.Value Mod right.Value
     Select Case temp
       Case 1 To 10 : Return New LimitedInt(temp)
       Case Else : Throw New OverflowException
     End Select
   End Operator

   Public Shared Operator And(ByVal left As LimitedInt, ByVal right As LimitedInt) As LimitedInt
     Dim temp As Integer = left.Value And right.Value
     Select Case temp
       Case 1 To 10 : Return New LimitedInt(temp)
       Case Else : Throw New OverflowException
     End Select
   End Operator

   Public Shared Operator Or(ByVal left As LimitedInt, ByVal right As LimitedInt) As LimitedInt
     Dim temp As Integer = left.Value Or right.Value
     Select Case temp
       Case 1 To 10 : Return New LimitedInt(temp)
       Case Else : Throw New OverflowException
     End Select
   End Operator

   Public Shared Operator Xor(ByVal left As LimitedInt, ByVal right As LimitedInt) As LimitedInt
     Dim temp As Integer = left.Value Xor right.Value
     Select Case temp
       Case 1 To 10 : Return New LimitedInt(temp)
       Case Else : Throw New OverflowException
     End Select
   End Operator

   Public Shared Operator ^(ByVal left As LimitedInt, ByVal right As LimitedInt) As Double
     Return left.Value ^ right.Value
   End Operator

   Public Shared Operator <(ByVal left As LimitedInt, ByVal right As LimitedInt) As Boolean
     Return left.Value < right.Value
   End Operator

   Public Shared Operator >(ByVal left As LimitedInt, ByVal right As LimitedInt) As Boolean
     Return left.Value > right.Value
   End Operator

   Public Shared Operator <=(ByVal left As LimitedInt, ByVal right As LimitedInt) As Boolean
     Return left.Value <= right.Value
   End Operator

   Public Shared Operator >=(ByVal left As LimitedInt, ByVal right As LimitedInt) As Boolean
     Return left.Value >= right.Value
   End Operator

   Public Shared Widening Operator CType(ByVal left As LimitedInt) As Integer
     Return left.Value
   End Operator

   Public Shared Narrowing Operator CType(ByVal left As Integer) As LimitedInt
     Return New LimitedInt(left)
   End Operator


 End Structure