Price fraction

From Rosetta Code
Revision as of 20:47, 6 August 2010 by rosettacode>Dmitry-kazakov (→‎{{header|Ada}}: 'constant' modifier added)
Task
Price fraction
You are encouraged to solve this task according to the task description, using any language you may know.

A friend of mine runs a Pharmacy. He has a specialised function in his Dispensary application which receives a decimal value of currency and replaces it to a standard value. This value is regulated by a government department.

Task: Given a floating point value between 0.00 and 1.00, rescale according to the following table:

>=  0.00  <  0.06  :=  0.10
>=  0.06  <  0.11  :=  0.18
>=  0.11  <  0.16  :=  0.26
>=  0.16  <  0.21  :=  0.32
>=  0.21  <  0.26  :=  0.38
>=  0.26  <  0.31  :=  0.44
>=  0.31  <  0.36  :=  0.50
>=  0.36  <  0.41  :=  0.54
>=  0.41  <  0.46  :=  0.58
>=  0.46  <  0.51  :=  0.62
>=  0.51  <  0.56  :=  0.66
>=  0.56  <  0.61  :=  0.70
>=  0.61  <  0.66  :=  0.74
>=  0.66  <  0.71  :=  0.78
>=  0.71  <  0.76  :=  0.82
>=  0.76  <  0.81  :=  0.86
>=  0.81  <  0.86  :=  0.90
>=  0.86  <  0.91  :=  0.94
>=  0.91  <  0.96  :=  0.98
>=  0.96  <  1.01  :=  1.00

Ada

<lang Ada> type Price is delta 0.01 digits 3 range 0.0..1.0; function Scale (Value : Price) return Price is

  X : constant array (1..19) of Price :=
         (  0.06, 0.11, 0.16, 0.21, 0.26,  0.31, 0.36, 0.41, 0.46, 0.51,
            0.56, 0.61, 0.66, 0.71, 0.76,  0.81, 0.86, 0.91, 0.96
         );
  Y : constant array (1..20) of Price :=
         (  0.10, 0.18, 0.26, 0.32, 0.38,  0.44, 0.50, 0.54, 0.58, 0.62,
            0.66, 0.70, 0.74, 0.78, 0.82,  0.86, 0.90, 0.94, 0.98, 1.0
         );
  Low    : Natural := X'First;
  High   : Natural := X'Last;
  Middle : Natural;

begin

  loop
     Middle := (Low + High) / 2;
     if Value = X (Middle) then
        return Y (Middle + 1);
     elsif Value < X (Middle) then
        if Low = Middle then
           return Y (Low);
        end if;
        High := Middle - 1;
     else
        if High = Middle then
           return Y (High + 1);
        end if;
        Low := Middle + 1;
     end if;
  end loop;

end Scale; </lang> The solution uses fixed point type to prevent rounding and representation issues. With the above declarations a full coverage test: <lang Ada> with Ada.Text_IO; use Ada.Text_IO; procedure Test_Price_Fraction is

  -- Put the declarations here
  Value : Price := Price'First;

begin

  loop
     Put_Line (Price'Image (Value) & "->" & Price'Image (Scale (Value)));
     exit when Value = Price'Last;
     Value := Price'Succ (Value);
  end loop;

end Test_Price_Fraction; </lang> Sample output:

 0.00-> 0.10
 0.01-> 0.10
 0.02-> 0.10
 0.03-> 0.10
 0.04-> 0.10
 0.05-> 0.10
 0.06-> 0.18
 0.07-> 0.18
 0.08-> 0.18
 0.09-> 0.18
 0.10-> 0.18
 0.11-> 0.26
 0.12-> 0.26
 0.13-> 0.26
 0.14-> 0.26
 0.15-> 0.26
 0.16-> 0.32
 0.17-> 0.32
 0.18-> 0.32
 0.19-> 0.32
 0.20-> 0.32
 0.21-> 0.38
 0.22-> 0.38
 0.23-> 0.38
 0.24-> 0.38
 0.25-> 0.38
 0.26-> 0.44
 0.27-> 0.44
 0.28-> 0.44
 0.29-> 0.44
 0.30-> 0.44
 0.31-> 0.50
 0.32-> 0.50
 0.33-> 0.50
 0.34-> 0.50
 0.35-> 0.50
 0.36-> 0.54
 0.37-> 0.54
 0.38-> 0.54
 0.39-> 0.54
 0.40-> 0.54
 0.41-> 0.58
 0.42-> 0.58
 0.43-> 0.58
 0.44-> 0.58
 0.45-> 0.58
 0.46-> 0.62
 0.47-> 0.62
 0.48-> 0.62
 0.49-> 0.62
 0.50-> 0.62
 0.51-> 0.66
 0.52-> 0.66
 0.53-> 0.66
 0.54-> 0.66
 0.55-> 0.66
 0.56-> 0.70
 0.57-> 0.70
 0.58-> 0.70
 0.59-> 0.70
 0.60-> 0.70
 0.61-> 0.74
 0.62-> 0.74
 0.63-> 0.74
 0.64-> 0.74
 0.65-> 0.74
 0.66-> 0.78
 0.67-> 0.78
 0.68-> 0.78
 0.69-> 0.78
 0.70-> 0.78
 0.71-> 0.82
 0.72-> 0.82
 0.73-> 0.82
 0.74-> 0.82
 0.75-> 0.82
 0.76-> 0.86
 0.77-> 0.86
 0.78-> 0.86
 0.79-> 0.86
 0.80-> 0.86
 0.81-> 0.90
 0.82-> 0.90
 0.83-> 0.90
 0.84-> 0.90
 0.85-> 0.90
 0.86-> 0.94
 0.87-> 0.94
 0.88-> 0.94
 0.89-> 0.94
 0.90-> 0.94
 0.91-> 0.98
 0.92-> 0.98
 0.93-> 0.98
 0.94-> 0.98
 0.95-> 0.98
 0.96-> 1.00
 0.97-> 1.00
 0.98-> 1.00
 0.99-> 1.00
 1.00-> 1.00

AutoHotkey

<lang AutoHotkey>; Submitted by MasterFocus --- http://tiny.cc/iTunis

Loop {

 InputBox, OutputVar, Price Fraction Example, Insert the value to be rounded.`n* [ 0 < value < 1 ]`n* Press ESC or Cancel to exit, , 200, 150
 If ErrorLevel
   Break
 MsgBox % "Input: " OutputVar "`nResult: " PriceFraction( OutputVar )

}

-----------------------------------------

PriceFraction( p_Input ) {

 If p_Input is not float ; returns 0 if input is not a float
   Return 0
 If ( ( p_Input <= 0 ) OR ( p_Input >= 1 ) ) ; returns 0 is input is out of range
   Return 0
 ; declaring the table (arbitrary delimiters in use are '§' and '|')
 l_List := "0.06|0.10§0.11|0.18§0.16|0.26§0.21|0.32§0.26|0.38§0.31|0.44§0.36|0.50§0.41|0.54§0.46|0.58§0.51|0.62§0.56|0.66§0.61|0.70§0.66|0.74§0.71|0.78§0.76|0.82§0.81|0.86§0.86|0.90§0.91|0.94§0.96|0.98§1.01|1.00"
 Loop, Parse, l_List, § ; retrieves each field (delimited by '§')
 {
   StringSplit, l_Array, A_LoopField, | ; splits current field (using delimiter '|')
   If ( p_Input <= l_Array1 )
     Return l_Array2 ; returns the second value if input <= first value
 }
 Return 0 ; returns 0, indicating failure (shouldn't be reached though)

}</lang>

ALGOL 68

Translation of: C

- note: This specimen retains the original C coding style.

Works with: ALGOL 68 version Revision 1 - no extensions to language used
Works with: ALGOL 68G version Any - tested with release 1.18.0-9h.tiny

<lang algol68>main: (

   # Just get a random price between 0 and 1 #
   # srand(time(NIL)); #
   REAL price := random;
   REAL tops := 0.06;
   REAL std val := 0.10;
   # Conditionals are a little odd here "(price-0.001 < tops AND
   price+0.001 > tops)" is to check if they are equal. Stupid
   C floats, right?   :) #
   WHILE ( price>tops OR (price-0.001 < tops AND price+0.001 > tops) ) AND tops<=1.01
   DO
       tops+:=0.05;
       IF std val < 0.26 THEN
               std val +:= 0.08
       ELIF std val < 0.50 THEN
               std val +:= 0.06
       ELSE
               std val +:= 0.04
       FI;
       IF std val > 0.98 THEN
               std val := 1.0
       FI
   OD;
   printf(($"Value :   "z.2dl,"Converted to standard :   "z.2dl$, price, std val))

)</lang> Sample Output:

Value :   0.38
Converted to standard :   0.54

BASIC

Works with: QBasic

This could also be done by building an array, but I felt that this was simpler.

<lang qbasic>DECLARE FUNCTION PriceFraction! (price AS SINGLE)

RANDOMIZE TIMER DIM x AS SINGLE x = RND PRINT x, PriceFraction(x)

FUNCTION PriceFraction! (price AS SINGLE)

   'returns price unchanged if invalid value
   SELECT CASE price
       CASE IS < 0!
           PriceFraction! = price
       CASE IS < .06
           PriceFraction! = .1
       CASE IS < .11
           PriceFraction! = .18
       CASE IS < .16
           PriceFraction! = .26
       CASE IS < .21
           PriceFraction! = .32
       CASE IS < .26
           PriceFraction! = .38
       CASE IS < .31
           PriceFraction! = .44
       CASE IS < .36
           PriceFraction! = .5
       CASE IS < .41
           PriceFraction! = .54
       CASE IS < .46
           PriceFraction! = .58
       CASE IS < .51
           PriceFraction! = .62
       CASE IS < .56
           PriceFraction! = .66
       CASE IS < .61
           PriceFraction! = .7
       CASE IS < .66
           PriceFraction! = .74
       CASE IS < .71
           PriceFraction! = .78
       CASE IS < .76
           PriceFraction! = .82
       CASE IS < .81
           PriceFraction! = .86
       CASE IS < .86
           PriceFraction! = .9
       CASE IS < .91
           PriceFraction! = .94
       CASE IS < .96
           PriceFraction! = .98
       CASE IS < 1.01
           PriceFraction! = 1!
       CASE ELSE
           PriceFraction! = price
   END SELECT

END FUNCTION</lang>

Sample outputs (run 5 times):

.7388727      .82
.8593103      .9
.826687       .9
.3444635      .5
.0491907      .1

C

<lang c>#include <stdlib.h>

  1. include <time.h>
  2. include <stdio.h>

int main() { //Just get a random price between 0 and 1 srand(time(NULL)); float price = (float)((int)rand()%101)/100; float tops=0.06; float std_val = 0.10;

   //Conditionals are a little odd here
   //"(price-0.001 < tops && price+0.001 > tops)" is to check if they are equal. Stupid C floats, right? :)
   while((price>tops || (price-0.001 < tops && price+0.001 > tops)) && tops<=1.01)
   {
       tops+=0.05;
       if(std_val < 0.26)

std_val += 0.08; else if(std_val<0.50) std_val +=0.06; else std_val +=0.04;

if(std_val > 0.98) std_val = 1.0;

   }

printf("Value: %.2f\nConverted to standard: %.2f\n", price, std_val); }</lang>

C#

<lang csharp>namespace ConsoleApplication1 {

   class Program
   {
       static void Main(string[] args)
       {
           for (int x = 0; x < 10; x++)
           {
               Console.WriteLine("In: {0:0.00}, Out: {1:0.00}", ((double)x) / 10, SpecialRound(((double)x) / 10));
           }
           Console.WriteLine();
           for (int x = 0; x < 10; x++)
           {
               Console.WriteLine("In: {0:0.00}, Out: {1:0.00}", ((double)x) / 10 + 0.05, SpecialRound(((double)x) / 10 + 0.05));
           }
           Console.WriteLine();
           Console.WriteLine("In: {0:0.00}, Out: {1:0.00}", 1.01, SpecialRound(1.01));
           Console.Read();
       }
       private static double SpecialRound(double inValue)
       {
           if (inValue > 1) return 1;
           double[] Splitters = new double[] { 
                  0.00 , 0.06 , 0.11 , 0.16 , 0.21 , 
                  0.26 , 0.31 , 0.36 , 0.41 , 0.46 , 
                  0.51 , 0.56 , 0.61 , 0.66 , 0.71 , 
                  0.76 , 0.81 , 0.86 , 0.91 , 0.96 };
           double[] replacements = new double[] { 
                   0.10 , 0.18 , 0.26 , 0.32 , 0.38 ,
                   0.44 , 0.50 , 0.54 , 0.58 , 0.62 , 
                   0.66 , 0.70 , 0.74 , 0.78 , 0.82 , 
                   0.86 , 0.90 , 0.94 , 0.98 , 1.00 };
           for (int x = 0; x < Splitters.Length - 1; x++)
           {
               if (inValue >= Splitters[x] &&
                   inValue < Splitters[x + 1])
               {
                   return replacements[x];
               }
           }
           return inValue;
       }
   }

}</lang>

C++

<lang cpp>#include <iostream>

  1. include <cmath>

int main( ) {

  double froms[ ] = { 0.00 , 0.06 , 0.11 , 0.16 , 0.21 , 0.26 , 
      0.31 , 0.36 , 0.41 , 0.46 , 0.51 , 0.56 , 0.61 , 0.66 ,
      0.71 , 0.76 , 0.81 , 0.86 , 0.91 , 0.96 } ;
  double tos[ ] = { 0.06 , 0.11 , 0.16 , 0.21 , 0.26 , 0.31 ,
     0.36 , 0.41 , 0.46 , 0.51 , 0.56 , 0.61 , 0.66 , 0.71 ,
     0.76 , 0.81 , 0.86 , 0.91 , 0.96 , 1.01 } ;
  double replacements [] = { 0.10 , 0.18 , 0.26 , 0.32 , 0.38 ,
     0.44 , 0.50 , 0.54 , 0.58 , 0.62 , 0.66 , 0.70 , 0.74 ,
     0.78 , 0.82 , 0.86 , 0.90 , 0.94 , 0.98 , 1.00 } ;
  double number = 0.1 ;
  std::cout << "Enter a fractional number between 0 and 1 ( 0 to end )!\n" ;
  std::cin >> number ;
  while ( number != 0 ) {
     if ( number < 0 || number > 1 ) {

std::cerr << "Error! Only positive values between 0 and 1 are allowed!\n" ; return 1 ;

     }
     int n = 0 ;
     while ( ! ( number >= froms[ n ] && number < tos[ n ] ) ) 

n++ ;

     std::cout << "-->" << replacements[ n ] << '\n' ;
     std::cout << "Enter a fractional number ( 0 to end )!\n" ;
     std::cin >> number ;
  }
  return 0 ;

} </lang>

Sample output:
Enter a fractional number between 0 and 1 ( 0 to end )!
0.7
-->0.78
Enter a fractional number ( 0 to end )!
0.32
-->0.5
Enter a fractional number ( 0 to end )!
0.12
-->0.26
Enter a fractional number ( 0 to end )!
0

Clipper

<lang dbase>FUNCTION PriceFraction( npQuantDispensed )

   LOCAL aPriceFraction := { {0,.06,.1},;
                           {.06,.11,.18}, ;
                           {.11,.16,.26}, ;
                           {.16,.21,.32}, ;
                           {.21,.26,.38}, ;
                           {.26,.31,.44}, ;
                           {.31,.36,.5}, ;
                           {.36,.41,.54}, ;
                           {.41,.46,.58}, ;
                           {.46,.51,.62}, ;
                           {.51,.56,.66}, ;
                           {.56,.61,.7}, ;
                           {.61,.66,.74}, ;
                           {.66,.71,.78}, ;
                           {.71,.76,.82}, ;
                           {.76,.81,.86}, ;
                           {.81,.86,.9}, ;
                           {.86,.91,.94}, ;
                           {.91,.96,.98} }
   LOCAL nResult
   LOCAL nScan
   IF npQuantDispensed = 0
           nResult = 0
   ELSEIF npQuantDispensed >= .96
           nResult = 1
   ELSE
           nScan := ASCAN( aPriceFraction, ;
                  { |aItem| npQuantDispensed >= aItem[ 1 ] .AND.;
                            npQuantDispensed <  aItem[ 2 ] } )
           nResult := aPriceFraction[ nScan ][ 3 ]
   END IF
   RETURN nResult</lang>

Erlang

<lang erlang>priceFraction(N) when N < 0 orelse N > 1 ->

   erlang:error('Values must be between 0 and 1.');

priceFraction(N) when N < 0.06 -> 0.10; priceFraction(N) when N < 0.11 -> 0.18; priceFraction(N) when N < 0.16 -> 0.26; priceFraction(N) when N < 0.21 -> 0.32; priceFraction(N) when N < 0.26 -> 0.38; priceFraction(N) when N < 0.31 -> 0.44; priceFraction(N) when N < 0.36 -> 0.50; priceFraction(N) when N < 0.41 -> 0.54; priceFraction(N) when N < 0.46 -> 0.58; priceFraction(N) when N < 0.51 -> 0.62; priceFraction(N) when N < 0.56 -> 0.66; priceFraction(N) when N < 0.61 -> 0.70; priceFraction(N) when N < 0.66 -> 0.74; priceFraction(N) when N < 0.71 -> 0.78; priceFraction(N) when N < 0.76 -> 0.82; priceFraction(N) when N < 0.81 -> 0.86; priceFraction(N) when N < 0.86 -> 0.90; priceFraction(N) when N < 0.91 -> 0.94; priceFraction(N) when N < 0.96 -> 0.98; priceFraction(N) -> 1.00.</lang>

Forth

A floating-point version wouldn't be hard -- four words would change ( , @ @ cell+ -to- f, f@ f@ float+ ), EVALUATE would be replaced with a small word that forced a floating-point interpretation, and the return stack would not be used in ROUND -- but it would be strikingly unusual. See this page's discussion.

<lang>: as begin parse-word dup while evaluate , repeat 2drop ;

create bounds as 96 91 86 81 76 71 66 61 56 51 46 41 36 31 26 21 16 11 6 0 create official as 100 98 94 90 86 82 78 74 70 66 62 58 54 50 44 38 32 26 18 10

official@ ( a-bounds -- +n )
 \ (a+n) - a + b = (a+n) + (b - a) = (b+n)
 [ official bounds - literal ] + @ ;
round ( n-cents -- n-cents' )
 >r bounds begin dup @ r@ > while cell+ repeat
 r> drop official@ ;</lang>

Fortran

Works with: Fortran version 90 and later

<lang fortran>program price_fraction

 implicit none
 integer, parameter :: i_max = 10
 integer :: i
 real, dimension (20), parameter :: in =                           &
   & (/0.00, 0.06, 0.11, 0.16, 0.21, 0.26, 0.31, 0.36, 0.41, 0.46, &
   &   0.51, 0.56, 0.61, 0.66, 0.71, 0.76, 0.81, 0.86, 0.91, 0.96/)
 real, dimension (20), parameter :: out =                          &
   & (/0.10, 0.18, 0.26, 0.32, 0.38, 0.44, 0.50, 0.54, 0.58, 0.62, &
   &   0.66, 0.70, 0.74, 0.78, 0.82, 0.86, 0.90, 0.94, 0.98, 1.00/)
 real :: r
 do i = 1, i_max
   call random_number (r)
   write (*, '(f8.6, 1x, f4.2)') r, out (maxloc (in, r >= in))
 end do

end program price_fraction</lang> Sample output: <lang>0.997560 1.00 0.566825 0.70 0.965915 1.00 0.747928 0.82 0.367391 0.54 0.480637 0.62 0.073754 0.18 0.005355 0.10 0.347081 0.50 0.342244 0.50</lang>

Haskell

<lang haskell>price_fraction n

 | n < 0 || n > 1 = error "Values must be between 0 and 1."
 | n < 0.06 = 0.10
 | n < 0.11 = 0.18
 | n < 0.16 = 0.26
 | n < 0.21 = 0.32
 | n < 0.26 = 0.38
 | n < 0.31 = 0.44
 | n < 0.36 = 0.50
 | n < 0.41 = 0.54
 | n < 0.46 = 0.58
 | n < 0.51 = 0.62
 | n < 0.56 = 0.66
 | n < 0.61 = 0.70
 | n < 0.66 = 0.74
 | n < 0.71 = 0.78
 | n < 0.76 = 0.82
 | n < 0.81 = 0.86
 | n < 0.86 = 0.90
 | n < 0.91 = 0.94
 | n < 0.96 = 0.98
 | otherwise = 1.00</lang>

Alternative

Translation of: OCaml

:

<lang haskell>table = [

   (0.06, 0.10),   (0.11, 0.18),   (0.16, 0.26),   (0.21, 0.32),   (0.26, 0.38),
   (0.31, 0.44),   (0.36, 0.50),   (0.41, 0.54),   (0.46, 0.58),   (0.51, 0.62),
   (0.56, 0.66),   (0.61, 0.70),   (0.66, 0.74),   (0.71, 0.78),   (0.76, 0.82),
   (0.81, 0.86),   (0.86, 0.90),   (0.91, 0.94),   (0.96, 0.98),   (1.01, 1.00),
 ]

price_fraction n

 | n < 0 || n > 1 = error "Values must be between 0 and 1."
 | otherwise = snd $ head $ dropWhile ((<= n) . fst) table</lang>

J

Solution: <lang j>le =: -0.96 0.91 0.86 0.81 0.76 0.71 0.66 0.61 0.56 0.51 0.46 0.41 0.36 0.31 0.26 0.21 0.16 0.11 0.06 0.0 out =: 1.00 0.98 0.94 0.90 0.86 0.82 0.78 0.74 0.70 0.66 0.62 0.58 0.54 0.50 0.44 0.38 0.32 0.26 0.18 0.1

priceFraction =: out {~ le I. -</lang>

Example: <lang j> priceFraction 0.34 0.070145 0.06 0.05 0.50214 0.56 1 0.99 0 0.5 0.18 0.18 0.1 0.62 0.7 1 1 0.1</lang>


Java

<lang java>import java.util.Random;

public class Main { private static float priceFraction(float f) { if (0.00f <= f && f < 0.06f) return 0.10f; else if (f < 0.11f) return 0.18f; else if (f < 0.16f) return 0.26f; else if (f < 0.21f) return 0.32f; else if (f < 0.26f) return 0.38f; else if (f < 0.31f) return 0.44f; else if (f < 0.36f) return 0.50f; else if (f < 0.41f) return 0.54f; else if (f < 0.46f) return 0.58f; else if (f < 0.51f) return 0.62f; else if (f < 0.56f) return 0.66f; else if (f < 0.61f) return 0.70f; else if (f < 0.66f) return 0.74f; else if (f < 0.71f) return 0.78f; else if (f < 0.76f) return 0.82f; else if (f < 0.81f) return 0.86f; else if (f < 0.86f) return 0.90f; else if (f < 0.91f) return 0.94f; else if (f < 0.96f) return 0.98f; else if (f < 1.01f) return 1.00f; else throw new IllegalArgumentException(); }

public static void main(String[] args) { Random rnd = new Random(); for (int i = 0; i < 5; i++) { float f = rnd.nextFloat(); System.out.format("%8.6f -> %4.2f%n", f, priceFraction(f)); } } }</lang> Output:

0.149969 -> 0.26
0.310605 -> 0.50
0.616683 -> 0.74
0.194047 -> 0.32
0.724852 -> 0.82

MUMPS

<lang MUMPS>PRICFRAC(X)

;Outputs a specified value dependent upon the input value
;The non-inclusive upper limits are encoded in the PFMAX string, and the values
;to convert to are encoded in the PFRES string.
NEW PFMAX,PFRES,I,RESULT
SET PFMAX=".06^.11^.16^.21^.26^.31^.36^.41^.46^.51^.56^.61^.66^.71^.76^.81^.86^.91^.96^1.01"
SET PFRES=".10^.18^.26^.32^.38^.44^.50^.54^.58^.62^.66^.70^.74^.78^.82^.86^.90^.94^.98^1.00"
Q:(X<0)!(X>1.01) ""
FOR I=1:1:$LENGTH(PFMAX,"^") Q:($DATA(RESULT)'=0)  SET:X<$P(PFMAX,"^",I) RESULT=$P(PFRES,"^",I)
KILL PFMAX,PFRES,I
QUIT RESULT</lang>

Output:

USER>W $$PRICFRAC^ROSETTA(.04)
.10
USER>W $$PRICFRAC^ROSETTA(.06)
.18
USER>W $$PRICFRAC^ROSETTA(.40)
.54
USER>W $$PRICFRAC^ROSETTA(1.40)
 
USER>W $$PRICFRAC^ROSETTA(.81)
.90

OCaml

<lang ocaml>let price_fraction v =

 if v < 0.0 || v >= 1.01 then
   invalid_arg "price_fraction";
 let rec aux = function
 | (x,r)::tl ->
     if v < x then r
     else aux tl
 | [] -> assert false
 in
 aux [
   0.06, 0.10;   0.11, 0.18;   0.16, 0.26;   0.21, 0.32;   0.26, 0.38;
   0.31, 0.44;   0.36, 0.50;   0.41, 0.54;   0.46, 0.58;   0.51, 0.62;
   0.56, 0.66;   0.61, 0.70;   0.66, 0.74;   0.71, 0.78;   0.76, 0.82;
   0.81, 0.86;   0.86, 0.90;   0.91, 0.94;   0.96, 0.98;   1.01, 1.00;
 ]</lang>

<lang ocaml>let () =

 let ok_tests = [
   (0.3793, 0.54);
   (0.4425, 0.58);
   (0.0746, 0.18);
   (0.6918, 0.78);
   (0.2993, 0.44);
   (0.5486, 0.66);
   (0.7848, 0.86);
   (0.9383, 0.98);
   (0.2292, 0.38);
 ] in
 Printf.printf " input   res   ok\n";
 List.iter (fun (v,ok) ->
   let r = price_fraction v in
   Printf.printf " %6g  %g  %b\n" v r (r = ok);
 ) ok_tests;
</lang>


Oz

Using a for-loop with return and a default value for values >= 1.01. For out-of-range input, a "failed value" is returned, i.e. a value that throws an exception when it is accessed.

<lang oz>fun {PriceFraction X}

  OutOfRange = {Value.failed outOfRange(X)}

in

  for Limit#Result in
     [0.00#OutOfRange
      0.06#0.10 0.11#0.18 0.16#0.26 0.21#0.32 0.26#0.38 0.31#0.44 0.36#0.5
      0.41#0.54 0.46#0.58 0.51#0.62 0.56#0.66 0.61#0.70 0.66#0.74 0.71#0.78
      0.76#0.82 0.81#0.86 0.86#0.90 0.91#0.94 0.96#0.98 1.01#1.00
     ]
     return:Return
     default:OutOfRange
  do
     if X < Limit then {Return Result} end 
  end

end</lang>

PL/I

<lang PL/I> declare t(20) fixed decimal (3,2) static initial (

  .06, .11, .16, .21, .26, .31, .36, .41, .46,  .51,
  .56, .61, .66, .71, .76, .81, .86, .91, .96, 1.01);

declare r(20) fixed decimal (3,2) static initial (

  .10, .18, .26, .32, .38, .44, .50, .54, .58, .62,
  .66, .70, .74, .78, .82, .86, .90, .94, .98, 1);

declare x float, d fixed decimal (3,2); declare i fixed binary;

loop:

  do i = 1 to 20;
     if x < t(i) then
        do; d = r(i); leave loop; end;
  end;

</lang>

PicoLisp

<lang PicoLisp>(scl 2)

(de price (Pr)

  (format
     (cdr
        (rank Pr
           (quote
              (0.00 . 0.10)
              (0.06 . 0.18)
              (0.11 . 0.26)
              (0.16 . 0.32)
              (0.21 . 0.38)
              (0.26 . 0.44)
              (0.31 . 0.50)
              (0.36 . 0.54)
              (0.41 . 0.58)
              (0.46 . 0.62)
              (0.51 . 0.66)
              (0.56 . 0.70)
              (0.61 . 0.74)
              (0.66 . 0.78)
              (0.71 . 0.82)
              (0.76 . 0.86)
              (0.81 . 0.90)
              (0.86 . 0.94)
              (0.91 . 0.98)
              (0.96 . 1.00) ) ) )
     *Scl ) )

(for N (0.3793 0.4425 0.0746 0.6918 0.2993 0.5486 0.7848 0.9383 0.2292)

  (prinl (price N)) )</lang>

Output:

0.54
0.58
0.18
0.78
0.44
0.66
0.86
0.98
0.38

PureBasic

<lang PureBasic>Procedure.f PriceFraction(price.f)

 ;returns price unchanged if value is invalid 
 Protected fraction
 Select price * 100
   Case 0 To 5
     fraction = 10
   Case 06 To 10
     fraction = 18
   Case 11 To 15
     fraction = 26
   Case 16 To 20
     fraction = 32
   Case 21 To 25
     fraction = 38
   Case 26 To 30
     fraction = 44
   Case 31 To 35
     fraction = 5
   Case 36 To 40
     fraction = 54
   Case 41 To 45
     fraction = 58
   Case 46 To 50
     fraction = 62
   Case 51 To 55
     fraction = 66
   Case 56 To 60
     fraction = 7
   Case 61 To 65
     fraction = 74
   Case 66 To 70
     fraction = 78
   Case 71 To 75
     fraction = 82
   Case 76 To 80
     fraction = 86
   Case 81 To 85
     fraction = 9
   Case 86 To 90
     fraction = 94
   Case 91 To 95
     fraction = 98
   Case 96 To 100
     fraction = 100
   Default
     ProcedureReturn price
 EndSelect
 
 ProcedureReturn fraction / 100

EndProcedure

If OpenConsole()

 Define x.f, i
 
 For i = 1 To 10
   x = Random(10000)/10000
   PrintN(StrF(x, 4) + " -> " + StrF(PriceFraction(x), 2))
 Next
 
 Print(#CRLF$ + #CRLF$ + "Press ENTER to exit")
 Input()
 CloseConsole()

EndIf</lang> Sample output:

0.3793 -> 0.54
0.4425 -> 0.58
0.0746 -> 0.18
0.6918 -> 0.78
0.2993 -> 0.44
0.5486 -> 0.66
0.7848 -> 0.86
0.9383 -> 0.98
0.2292 -> 0.38
0.9560 -> 1.00

Python

Using the bisect standard module to reduce the comparisons with members of the cin array.

<lang python>>>> import bisect >>> _cin = [.06, .11, .16, .21, .26, .31, .36, .41, .46, .51, .56, .61, .66, .71, .76, .81, .86, .91, .96, 1.01] >>> _cout = [.10, .18, .26, .32, .38, .44, .50, .54, .58, .62, .66, .70, .74, .78, .82, .86, .90, .94, .98, 1.00] >>> def pricerounder(pricein): return _cout[ bisect.bisect_right(_cin, pricein) ]</lang>

When dealing with money it is good to think about possible loss of precision. If we change the units to be integer cents we could use the following exact routine: <lang python>>>> import bisect >>> _cin = [ 6, 11, 16, 21, 26, 31, 36, 41, 46, 51, 56, 61, 66, 71, 76, 81, 86, 91, 96, 101] >>> _cout = [10, 18, 26, 32, 38, 44, 50, 54, 57, 62, 66, 70, 74, 78, 82, 86, 90, 94, 98, 100] >>> def centsrounder(centsin): return _cout[ bisect.bisect_right(_cin, centsin) ]</lang> Other options are to use the fractions or decimals modules for calculating money to a known precision.


Bisection library code

The bisect Python standard library function uses the following code that improves on a simple linear scan through a sorted list:
<lang python>def bisect_right(a, x, lo=0, hi=None):
   """Return the index where to insert item x in list a, assuming a is sorted.
   The return value i is such that all e in a[:i] have e <= x, and all e in
   a[i:] have e > x.  So if x already appears in the list, a.insert(x) will
   insert just after the rightmost x already there.
   Optional args lo (default 0) and hi (default len(a)) bound the
   slice of a to be searched.
   """
   if lo < 0:
       raise ValueError('lo must be non-negative')
   if hi is None:
       hi = len(a)
   while lo < hi:
       mid = (lo+hi)//2
       if x < a[mid]: hi = mid
       else: lo = mid+1
   return lo</lang>

R

<lang r> price_fraction <- function(x) {

 stopifnot(all(x >= 0 & x <= 1))
 breaks <- seq(0.06, 1.01, 0.05)
 values <- c(.1, .18, .26, .32, .38, .44, .5, .54, .58, .62, .66, .7, .74, .78, .82, .86, .9, .94, .98, 1)
 indices <- sapply(x, function(x) which(x < breaks)[1])
 values[indices]

}

  1. Example usage:

price_fraction(c(0, .01, 0.06, 0.25, 1)) # 0.10 0.10 0.18 0.38 1.00 </lang>

You can extract the contents of the table as follows:

<lang r> dfr <- read.table(tc <- textConnection( ">= 0.00 < 0.06  := 0.10 >= 0.06 < 0.11  := 0.18 >= 0.11 < 0.16  := 0.26 >= 0.16 < 0.21  := 0.32 >= 0.21 < 0.26  := 0.38 >= 0.26 < 0.31  := 0.44 >= 0.31 < 0.36  := 0.50 >= 0.36 < 0.41  := 0.54 >= 0.41 < 0.46  := 0.58 >= 0.46 < 0.51  := 0.62 >= 0.51 < 0.56  := 0.66 >= 0.56 < 0.61  := 0.70 >= 0.61 < 0.66  := 0.74 >= 0.66 < 0.71  := 0.78 >= 0.71 < 0.76  := 0.82 >= 0.76 < 0.81  := 0.86 >= 0.81 < 0.86  := 0.90 >= 0.86 < 0.91  := 0.94 >= 0.91 < 0.96  := 0.98 >= 0.96 < 1.01  := 1.00")); close(tc) breaks <- dfr$V4 values <- dfr$V6 </lang>

Ruby

A simple function with hardcoded values. <lang ruby>def rescale_price_fraction(value)

 raise ArgumentError, "value=#{value}, must have: 0 <= value < 1.01" if value < 0 || value >= 1.01
 if     value < 0.06  then  0.10
 elsif  value < 0.11  then  0.18
 elsif  value < 0.16  then  0.26
 elsif  value < 0.21  then  0.32
 elsif  value < 0.26  then  0.38
 elsif  value < 0.31  then  0.44
 elsif  value < 0.36  then  0.50
 elsif  value < 0.41  then  0.54
 elsif  value < 0.46  then  0.58
 elsif  value < 0.51  then  0.62
 elsif  value < 0.56  then  0.66
 elsif  value < 0.61  then  0.70
 elsif  value < 0.66  then  0.74
 elsif  value < 0.71  then  0.78
 elsif  value < 0.76  then  0.82
 elsif  value < 0.81  then  0.86
 elsif  value < 0.86  then  0.90
 elsif  value < 0.91  then  0.94
 elsif  value < 0.96  then  0.98
 elsif  value < 1.01  then  1.00
 end

end</lang>

Or, where we can cut and paste the textual table in one place

Works with: Ruby version 1.8.7+

for the String#lines method.

For Ruby 1.8.6, use String#each_line

<lang ruby>class Price

 ConversionTable = <<-END_OF_TABLE
   >=  0.00  <  0.06  :=  0.10
   >=  0.06  <  0.11  :=  0.18
   >=  0.11  <  0.16  :=  0.26
   >=  0.16  <  0.21  :=  0.32
   >=  0.21  <  0.26  :=  0.38
   >=  0.26  <  0.31  :=  0.44
   >=  0.31  <  0.36  :=  0.50
   >=  0.36  <  0.41  :=  0.54
   >=  0.41  <  0.46  :=  0.58
   >=  0.46  <  0.51  :=  0.62
   >=  0.51  <  0.56  :=  0.66
   >=  0.56  <  0.61  :=  0.70
   >=  0.61  <  0.66  :=  0.74
   >=  0.66  <  0.71  :=  0.78
   >=  0.71  <  0.76  :=  0.82
   >=  0.76  <  0.81  :=  0.86
   >=  0.81  <  0.86  :=  0.90
   >=  0.86  <  0.91  :=  0.94
   >=  0.91  <  0.96  :=  0.98
   >=  0.96  <  1.01  :=  1.00
 END_OF_TABLE
 RE = %r{ ([<>=]+) \s* (\d\.\d\d) \s* ([<>=]+) \s* (\d\.\d\d) \D+ (\d\.\d\d) }x
 # extract the comparison operators and numbers from the table
 CONVERSION_TABLE = ConversionTable.lines.inject([]) do |table, line| 
   m = line.match(RE) 
   if not m.nil? and m.length == 6
     table << [m[1], m[2].to_f, m[3], m[4].to_f, m[5].to_f] 
   end
   table
 end
 MIN_COMP, MIN = CONVERSION_TABLE[0][0..1] 
 MAX_COMP, MAX = CONVERSION_TABLE[-1][2..3]
 def initialize(value)
   if (not value.send(MIN_COMP, MIN)) or (not value.send(MAX_COMP, MAX))
     raise ArgumentError, "value=#{value}, must have: #{MIN} #{MIN_COMP} value #{MAX_COMP} #{MAX}"
   end
   @standard_value = CONVERSION_TABLE.find do |comp1, lower, comp2, upper, standard|
     value.send(comp1, lower) and value.send(comp2, upper)
   end.last
 end
 attr_reader :standard_value

end</lang>

And a test suite <lang ruby>require 'test/unit'

class PriceFractionTests < Test::Unit::TestCase

 @@ok_tests = [
   [0.3793, 0.54],
   [0.4425, 0.58],
   [0.0746, 0.18],
   [0.6918, 0.78],
   [0.2993, 0.44],
   [0.5486, 0.66],
   [0.7848, 0.86],
   [0.9383, 0.98],
   [0.2292, 0.38],
 ]
 @@bad_tests = [1.02, -3]
 def test_ok
   @@ok_tests.each do |val, exp| 
     assert_equal(exp, rescale_price_fraction(val))
     assert_equal(exp, Price.new(val).standard_value)
   end
   @@bad_tests.each do |val| 
     assert_raise(ArgumentError) {rescale_price_fraction(val)}
     assert_raise(ArgumentError) {Price.new(val).standard_value}
   end
 end

end</lang>

output

Loaded suite price_fraction
Started
.
Finished in 0.001000 seconds.

1 tests, 22 assertions, 0 failures, 0 errors, 0 skips

Smalltalk

Works with: GNU Smalltalk

<lang smalltalk>"Table driven rescale" Object subclass: PriceRescale [

 |table|
 PriceRescale class  >> new: theTable [
   ^ self basicNew initialize: theTable
 ]
 initialize: theTable [
    table := theTable asOrderedCollection.
    ^self
 ]
 rescale: aPrice [ |v1 v2|
   1 to: (table size - 1) do: [:i|
     v1 := table at: i.
     v2 := table at: (i+1).
     ((aPrice >= (v1 x)) & (aPrice < (v2 x)))
       ifTrue: [ ^ v1 y ]
   ].
   (aPrice < ((v1:=(table first)) x)) ifTrue: [ ^ v1 y ]. 
   (aPrice >= ((v1:=(table last)) x)) ifTrue: [ ^ v1 y ]
 ]

].

|pr| pr := PriceRescale

        new: {  0.00@0.10 .
                0.06@0.18 .
                0.11@0.26 .
                0.16@0.32 .
                0.21@0.38 .
                0.26@0.44 .
                0.31@0.50 .
                0.36@0.54 .
                0.41@0.58 .
                0.46@0.62 .
                0.51@0.66 .
                0.56@0.70 .
                0.61@0.74 .
                0.66@0.78 .
                0.71@0.82 .
                0.76@0.86 .
                0.81@0.90 .
                0.86@0.94 .
                0.91@0.98 .
                0.96@1.00 .
                1.01@1.00
              }.

"get a price" (pr rescale: ( (Random between: 0 and: 100)/100 )) displayNl.</lang>

Tcl

Structured as two functions, one to parse the input data as described in the problem into a form which Tcl can work with easily, and the other to perform the mapping. <lang tcl># Used once to turn the table into a "nice" form proc parseTable table {

   set map {}
   set LINE_RE {^ *>= *([0-9.]+) *< *([0-9.]+) *:= *([0-9.]+) *$}
   foreach line [split $table \n] {

if {[string trim $line] eq ""} continue if {[regexp $LINE_RE $line -> min max target]} { lappend map $min $max $target } else { error "invalid table format: $line" }

   }
   return $map

}

  1. How to apply the "nice" table to a particular value

proc priceFraction {map value} {

   foreach {minimum maximum target} $map {

if {$value >= $minimum && $value < $maximum} {return $target}

   }
   # Failed to map; return the input
   return $value

}</lang> How it is used: <lang tcl># Make the mapping set inputTable {

   >=  0.00  <  0.06  :=  0.10
   >=  0.06  <  0.11  :=  0.18
   >=  0.11  <  0.16  :=  0.26
   >=  0.16  <  0.21  :=  0.32
   >=  0.21  <  0.26  :=  0.38
   >=  0.26  <  0.31  :=  0.44
   >=  0.31  <  0.36  :=  0.50
   >=  0.36  <  0.41  :=  0.54
   >=  0.41  <  0.46  :=  0.58
   >=  0.46  <  0.51  :=  0.62
   >=  0.51  <  0.56  :=  0.66
   >=  0.56  <  0.61  :=  0.70
   >=  0.61  <  0.66  :=  0.74
   >=  0.66  <  0.71  :=  0.78
   >=  0.71  <  0.76  :=  0.82
   >=  0.76  <  0.81  :=  0.86
   >=  0.81  <  0.86  :=  0.90
   >=  0.86  <  0.91  :=  0.94
   >=  0.91  <  0.96  :=  0.98
   >=  0.96  <  1.01  :=  1.00

} set map [parseTable $inputTable]

  1. Apply the mapping to some inputs (from the Oz example)

foreach example {.7388727 .8593103 .826687 .3444635 .0491907} {

   puts "$example -> [priceFraction $map $example]"

}</lang> Output:

.7388727 -> 0.82
.8593103 -> 0.90
.826687 -> 0.90
.3444635 -> 0.50
.0491907 -> 0.10

Ursala

<lang Ursala>#import flo

le = <0.06,.11,.16,.21,.26,.31,.36,.41,.46,.51,.56,.61,.66,.71,.76,.81,.86,.91,.96,1.01> out = <0.10,.18,.26,.32,.38,.44,.50,.54,.58,.62,.66,.70,.74,.78,.82,.86,.90,.94,.98,1.>

price_fraction = fleq@rlPlX*|rhr\~&p(le,out)</lang> main points:

  • ~&p(le,out) zips the pair of lists le and out into a list of pairs
  • A function of the form f\y applied to an argument x evaluates to f(x,y)
  • A function of the form f*| applied to a pair (x,y) where y is a list, makes a list of pairs with x on the left of each item and an item of y on the right. Then it applies f to each pair, makes a list of the right sides of those for which f returned true, and makes a separate list of the right sides of those for which f returned false.
  • The suffix rhr after the *| operator extracts the right side of the head of the right list from the result.
  • The operand to the *| operator, fleq@rlPlX is the less-or-equal predicate on floating point numbers, composed with the function ~&rlPlX which transforms a triple (u,(v,w)) to (v,u)

test program: <lang Ursala>#cast %eL

test = price_fraction* <0.34,0.070145,0.06,0.05,0.50214,0.56,1.,0.99,0.> </lang> output:

<
   5.000000e-01,
   1.800000e-01,
   1.800000e-01,
   1.000000e-01,
   6.200000e-01,
   7.000000e-01,
   1.000000e+00,
   1.000000e+00,
   1.000000e-01>