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
- note: This specimen retains the original C coding style.
<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
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>
- include <time.h>
- 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>
- 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
<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
:
<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]
}
- 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
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 until (1.00, 0.01)) println("%.2f %.2f".format(n, priceFraction(n)))</lang>
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
}
- 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]
- 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 listsle
andout
into a list of pairs- A function of the form
f\y
applied to an argumentx
evaluates tof(x,y)
- A function of the form
f*|
applied to a pair(x,y)
wherey
is a list, makes a list of pairs withx
on the left of each item and an item ofy
on the right. Then it appliesf
to each pair, makes a list of the right sides of those for whichf
returned true, and makes a separate list of the right sides of those for whichf
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>