Price fraction

From Rosetta Code
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>

D

<lang d>import std.stdio: writeln;

double priceRounder(double price)

   in { assert(price >= 0 && price <= 1.0); }
   body {
       enum cin  = [.06, .11, .16, .21, .26, .31, .36, .41, .46, .51,
                    .56, .61, .66, .71, .76, .81, .86, .91, .96, 1.01];
       enum cout = [.10, .18, .26, .32, .38, .44, .50, .54, .58, .62,
                    .66, .70, .74, .78, .82, .86, .90, .94, .98, 1.00];
       foreach (i, p; cin)
           if (p >= price)
               return cout[i];
       assert(0);
   }

void main() {

   foreach (price; [0.7388727, 0.8593103, 0.826687, 0.3444635])
       writeln(priceRounder(price));

}</lang> Output:

0.82
0.9
0.9
0.5

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

Scala

<lang scala>def priceFraction(x:Double)=x match {

  case n if n>=0 && n<0.06 => 0.10
  case n if n<0.11 => 0.18
  case n if n<0.36 => ((((n*100).toInt-11)/5)*6+26)/100.toDouble
  case n if n<0.96 => ((((n*100).toInt-31)/5)*4+50)/100.toDouble
  case _ => 1.00

}

def testPriceFraction()=

  for(n <- 0.00 to (1.00, 0.01)) println("%.2f  %.2f".format(n, priceFraction(n)))</lang>

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,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,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

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>