I've inherited a project written in PHP which has a double entry style cashbook, which requires that the amount going in is equal to the amount going out.
When validating figures entered via a form (to work out if it equals zero), it runs the code below which works fine until there is a decimal number, which seems to make it randomly fall over and return an incorrect value.
In the example below, it returns a really tiny number from 53.87 - 53.87, which should be zero.This has been stripped back to eliminate other things from causing problems, so I've removed a lot of input validation etc.
<?php $inputs = array(
array(
"in" => '',
"out" => '249.6',
),
array(
"in" => '',
"out" => '396',
),
array(
"in" => '554.4',
"out" => ''
),
array(
"in" => '145.07',
"out" => ''
),
array(
"in" => '',
"out" => '53.87',
),
);
$fTotal = 0;
echo "Start at 0: ";
foreach($inputs as $key=>$sRef) {
$itemValid = true;
$aItem = array();
$amountIn = $inputs[$key]['in'];
$amountOut = $inputs[$key]['out'];
if ($itemValid) {
echo "'".$fTotal."'";
$is_in = 0;
if ($amountIn > 0.0) {
$is_in = 1;
echo "+";
$amount = $amountIn;
$fTotal = $fTotal + $amountIn;
} else {
$is_in = 0;
echo "-";
$amount = $amountOut;
$fTotal = $fTotal - $amountOut;
}
echo "'".$amount."'=";
echo "'".$fTotal."' | ";
$aItem["is_in"] = $is_in;
$aItem["amount"] = $amount;
$aItems[] = $aItem;
}
}
You can run this on a sandbox here.
Here is the expected output:
Start at 0: '0'-'249.6'='-249.6' | '-249.6'-'396'='-645.6' | '-645.6'+'554.4'='-91.2' | '-91.2'+'145.07'='53.87' | '53.87'-'53.87'='0' |
Here is the actual output:
Start at 0: '0'-'249.6'='-249.6' | '-249.6'-'396'='-645.6' | '-645.6'+'554.4'='-91.2' | '-91.2'+'145.07'='53.87' | '53.87'-'53.87'='-4.9737991503207E-14' |
What is wrong here?
Update
Following help below, here's the working code for anyone who stumbles on this in the future.
This is happening because of floating point (decimal) maths where computers like binary maths. Sometimes decimal numbers don't have a nice representation in binary so these tiny differences happen.
If you can state that every number can be rounded to 2 decimal points, and based on your limited dataset, wrapping your 'sums' in number_format($fTotal + $amountIn , 2) etc can sort this out for you.
Alternatively for a "more accurate", just wrap the final one eg. echo "'".number_format($fTotal, 2)."' | "; (or 0 or whatever)
Your numbers are being interpreted as strings.
Try this:
<?php
$inputs = array(
array(
"in" => 0,
"out" => 249.6,
),
array(
"in" => 0,
"out" => 396,
),
array(
"in" => 554.4,
"out" => 0
),
array(
"in" => 145.07,
"out" => 0
),
array(
"in" => 0,
"out" => 53.87,
),
);
$fTotal = 0;
echo "Start at 0: ";
foreach($inputs as $key => $sRef) {
$itemValid = true;
$amountIn = $sRef['in'];
$amountOut = $sRef['out'];
if ($itemValid) {
echo $fTotal;
$is_in = 0;
if ($amountIn > 0.0) {
$is_in = 1;
echo "+";
$amount = floatval($amountIn);
$fTotal = floatval($fTotal) + floatval($amountIn);
} else {
$is_in = 0;
echo "-";
$amount = floatval($amountOut);
$fTotal = floatval($fTotal) - floatval($amountIn);
}
echo $amount;
echo ' = ' . $fTotal . " | ";
}
}
The bcsub() function in PHP is an inbuilt function and is used to subtract one arbitrary precision number from another.
Syntax:
string bcsub ( $num_str1, $num_str2, $scaleVal)
Example :
<?php $num_str1 = "8"; $num_str2 = "3"; $res = bcsub($num_str1, $num_str2) echo $res; ?>
Result:
5
I faced an interview question which i felt was very good. Couldn't achieve the complete answer, however, felt sharing and asking the right method to code it in PHP.
The question goes as :
Given the Japanese numeral reading system, write a program that converts an integer into the equivalent Japanese reading.
Basic numeral readings:
1: ichi
2: ni
3: san
4: yon
5: go
6: roku
7: nana
8: hachi
9: kyuu
10: juu
20: ni-juu
30: san-juu
100: hyaku
1000 : sen
10,000: man
100,000,000: oku
1,000,000,000,000: chou
10,000,000,000,000,000: kei
Exceptions due to voice rounding in Japanese reading:
300: sanbyaku
600: roppyaku
800: happyaku
3000: sanzen
8000: hassen
1,000,000,000,000: itchou
8,000,000,000,000: hatchou
10,000,000,000,000: jutchou (also applies to multiplies of 10,000,000,000,000)
10,000,000,000,000,000: ikkei
60,000,000,000,000,000: rokkei
80,000,000,000,000,000: hakkei
100,000,000,000,000,000: jukkei (also applies to multiplies of 10,000,000,000,000,000)
1,000,000,000,000,000,000: hyakkei (also applies to multiplies of 1,000,000,000,000,000,000)
Starting at 10,000, numbers begin with ichi if no digit would otherwise precede, e.g. 1,000 is sen but 10,000 is ichi-man.
Examples:
11: juu ichi
17: juu nana
151: hyaku go-juu ichi
302: san-byaku ni
469: yon-hyaku roku-juu kyuu
2025 : ni-sen ni-juu go
10,403: ichi-man yon-byaku san
41,892: yon-juu ichi-man happyaku kyuu-juu ni
80,000,000,000,000: hachi-jutchou
The code that i have tried is :
$inputNumber = 2025;
$inputString = (String)$inputNumber;
$numeralReadings = array(
1 => 'ichi',
2 => 'ni',
3 => 'san',
4 => 'yon',
5 => 'go',
6 => 'roku',
7 => 'nana',
8 => 'hachi',
9 => 'kyuu',
10 => 'juu',
20 => 'ni-juu',
30 => 'san-juu',
100 => 'hyaku',
1000 => 'sen',
10000 => 'man',
100000000 => 'oku',
1000000000000 => 'chou',
10000000000000000 => 'kei'
);
$numeralExceptions = array(
300 => 'sanbyaku',
600 => 'roppyaku',
800 => 'happyaku',
3000 => 'sanzen',
8000 => 'hassen',
1000000000000 => 'itchou',
8000000000000 => 'hatchou',
10000000000000 => 'jutchou',
10000000000000000 => 'ikkei',
60000000000000000 => 'rokkei',
80000000000000000 => 'hakkei',
100000000000000000 => 'jukkei',
1000000000000000000 => 'hyakkei'
);
if ($inputString > 10000) {
$inp1 = floor($inputString / 1000);
$inp = $inputString - ($inp1 * 1000);
if($inp !== 0) {
read($inp1, $numeralReadings, $numeralExceptions, false);
read($inp, $numeralReadings, $numeralExceptions);
} else {
read($inputString, $numeralReadings, $numeralExceptions);
}
} else {
read($inputString, $numeralReadings, $numeralExceptions);
}
function read($inputStr, $numeralReadings, $numeralExceptions, $parse1 = true)
{
$splitString = str_split($inputStr);
$returnString = '';
$appendIchi = false;
$firstNumber = null;
foreach ($splitString as $key => $number) {
if ($firstNumber == null) {
$firstNumber = $number;
}
if ($number !== 0) {
$int = 1;
$a = count($splitString) - 1 - $key;
for ($i = 0; $i < $a; $i++) {
$int = $int * 10;
}
$tempNumber = (int)$number * $int;
if (isset($numeralExceptions[$tempNumber])) {
$returnString .= $numeralExceptions[$tempNumber] . ' ';
continue;
}
if (isset($numeralReadings[$tempNumber])) {
if ($parse1 == false && $tempNumber == 1) {
continue;
}
$returnString .= $numeralReadings[$tempNumber] . ' ';
continue;
}
if (isset($numeralReadings[(int)$number])) {
if ($parse1 == false && $tempNumber == 1) {
continue;
}
$returnString .= $numeralReadings[(int)$number];
if ($int !== 1) {
$returnString .= '-' . $numeralReadings[$int];
}
$returnString .= ' ';
}
}
}
echo $returnString;
}
here is a fiddle that shows the code in running. You might want to try it online. Link
With the code above, i was able to achieve all the examples stated above other than the last 2.
Anyone who can solve this in a better way?
I guess you can simply use array_key_exists() here
function read($inputStr, $numeralReadings, $numeralExceptions)
{
if(array_key_exists($inputStr, $numeralReadings))
{
return $numeralReadings[$inputStr];
}
else if(array_key_exists($inputStr, $numeralExceptions))
{
return $numeralExceptions[$inputStr];
}
else
{
return "not found";
}
}
How can I validate a Vehicle Identification Number with PHP? I just need to check if the entered VIN number is correct or not.
Here's something I wrote up real quick using the example in the wikipedia article.
Not guaranteed perfect or bug free or super efficient, but should provide you with a solid starting point:
Note: I included the edits provided by Confluence below, making the procedure slightly more succinct.
function validate_vin($vin) {
$vin = strtolower($vin);
if (!preg_match('/^[^\Wioq]{17}$/', $vin)) {
return false;
}
$weights = array(8, 7, 6, 5, 4, 3, 2, 10, 0, 9, 8, 7, 6, 5, 4, 3, 2);
$transliterations = array(
"a" => 1, "b" => 2, "c" => 3, "d" => 4,
"e" => 5, "f" => 6, "g" => 7, "h" => 8,
"j" => 1, "k" => 2, "l" => 3, "m" => 4,
"n" => 5, "p" => 7, "r" => 9, "s" => 2,
"t" => 3, "u" => 4, "v" => 5, "w" => 6,
"x" => 7, "y" => 8, "z" => 9
);
$sum = 0;
for($i = 0 ; $i < strlen($vin) ; $i++ ) { // loop through characters of VIN
// add transliterations * weight of their positions to get the sum
if(!is_numeric($vin{$i})) {
$sum += $transliterations[$vin{$i}] * $weights[$i];
} else {
$sum += $vin{$i} * $weights[$i];
}
}
// find checkdigit by taking the mod of the sum
$checkdigit = $sum % 11;
if($checkdigit == 10) { // checkdigit of 10 is represented by "X"
$checkdigit = "x";
}
return ($checkdigit == $vin{8});
}
Note: there is a small percent error with verifying VINs because of the nature of the checksum:
...a match does not prove the VIN is correct, because there is still a 1 in 11 chance of any two distinct VINs having a matching check digit.
Also note: 11111111111111111 will validate against the procedure above. Whether or not you want to check for that is up to you:
Straight-ones (seventeen consecutive '1's) will suffice the check-digit. This is because a value of one, multiplied against 89 (sum of weights), is still 89. And 89 % 11 is 1, the check digit. This is an easy way to test a VIN-check algorithm.
reference: http://en.wikipedia.org/wiki/Vehicle_identification_number#Check_digit_calculation
Here's a version of the code by jordan ported to Javascript, hope it's helpful to someone...
function validate_vin(vin)
{
function isnumeric(mixed_var) {
return (typeof(mixed_var) === 'number' || typeof(mixed_var) === 'string') && mixed_var !== '' && !isNaN(mixed_var);
}
var pattern = /^[^\Wioq]{17}$/;
var weights = Array(8, 7, 6, 5, 4, 3, 2, 10, 0, 9, 8, 7, 6, 5, 4, 3, 2);
var transliterations = {
"a" : 1, "b" : 2, "c" : 3, "d" : 4,
"e" : 5, "f" : 6, "g" : 7, "h" : 8,
"j" : 1, "k" : 2, "l" : 3, "m" : 4,
"n" : 5, "p" : 7, "r" : 9, "s" : 2,
"t" : 3, "u" : 4, "v" : 5, "w" : 6,
"x" : 7, "y" : 8, "z" : 9
};
vin = vin.toLowerCase();
if(!vin.match(pattern)) { return false; }
var sum = 0;
for(var i=0; i<vin.length; i++) {
if(!isnumeric(vin.charAt(i))) {
sum += transliterations[vin.charAt(i)] * weights[i];
} else {
sum += parseInt(vin.charAt(i)) * weights[i];
}
}
var checkdigit = sum % 11;
if(checkdigit == 10) { // check digit of 10 represented by X
checkdigit = 'x';
}
return (checkdigit == vin.charAt(8));
}
It's "VIN." "VIN Number" = "Vehicle Identification Number Number," which doesn't make sense.
You can see a definition of the structure of VINs here:
http://en.wikipedia.org/wiki/Vehicle_identification_number
And you can work from there, or you can grab this script here:
http://www.geekpedia.com/code29_Check-if-VIN-number-is-valid.html
Here is an improved version of the function posted by jordan:
$vin = "1M8GDM9AXKP042788";
function validate_vin($vin) {
$vin = strtolower($vin);
if (!preg_match('/^[^\Wioq]{17}$/', $vin)) {
return false;
}
$weights = array(8, 7, 6, 5, 4, 3, 2, 10, 0, 9, 8, 7, 6, 5, 4, 3, 2);
$transliterations = array(
"a" => 1, "b" => 2, "c" => 3, "d" => 4,
"e" => 5, "f" => 6, "g" => 7, "h" => 8,
"j" => 1, "k" => 2, "l" => 3, "m" => 4,
"n" => 5, "p" => 7, "r" => 9, "s" => 2,
"t" => 3, "u" => 4, "v" => 5, "w" => 6,
"x" => 7, "y" => 8, "z" => 9
);
$sum = 0;
for($i = 0 ; $i < strlen($vin) ; $i++ ) { // loop through characters of VIN
// add transliterations * weight of their positions to get the sum
if(!is_numeric($vin{$i})) {
$sum += $transliterations[$vin{$i}] * $weights[$i];
} else {
$sum += $vin{$i} * $weights[$i];
}
}
// find checkdigit by taking the mod of the sum
$checkdigit = $sum % 11;
if($checkdigit == 10) { // checkdigit of 10 is represented by "X"
$checkdigit = "x";
}
return ($checkdigit == $vin{8});
}
I recently had to write a VIN validation class with PHP. I posted my class for everyone to use at:
http://dev.strategystar.net/2012/05/validate-vin-checksum-with-php/
class VIN
{
public static $transliteration = array(
'A'=>1, 'B'=>2, 'C'=>3, 'D'=>4, 'E'=>5, 'F'=>6, 'G'=>7, 'H'=>8,
'J'=>1, 'K'=>2, 'L'=>3, 'M'=>4, 'N'=>5, 'P'=>7, 'R'=>9,
'S'=>2, 'T'=>3, 'U'=>4, 'V'=>5, 'W'=>6, 'X'=>7, 'Y'=>8, 'Z'=>9,
);
public static $weights = array(8,7,6,5,4,3,2,10,0,9,8,7,6,5,4,3,2);
/***
* The checksum method is used to validate whether or not a VIN is valid
* It will return an array with two keys: status and message
* The "status" will either be boolean TRUE or FALSE
* The "message" will be a string describing the status
*/
public static function checksum($vin)
{
$vin = strtoupper($vin);
$length = strlen($vin);
$sum = 0;
if($length != 17)
{
return array('status'=>false, 'message'=>'VIN is not the right length');
}
for($x=0; $x<$length; $x++)
{
$char = substr($vin, $x, 1);
if(is_numeric($char))
{
$sum += $char * self::$weights[$x];
}
else
{
if(!isset(self::$transliteration[$char]))
{
return array('status'=>false, 'message'=>'VIN contains an invalid character.');
}
$sum += self::$transliteration[$char] * self::$weights[$x];
}
}
$remainder = $sum % 11;
$checkdigit = $remainder == 10 ? 'X' : $remainder;
if(substr($vin, 8, 1) != $checkdigit)
{
return array('status'=>false, 'message'=>'The VIN is not valid.');
}
return array('status'=>true, 'message'=>'The VIN is valid.');
}
}
Note that the wiki says:
"A check-digit validation is used for all road vehicles sold in the United States and Canada."
So if you're working with other countries you might want to loosen the check-digit validation
https://www.olschimke.eu/2012/08/02/dealing-with-vehicle-identification-numbers-vin-data-quality/
had some good tips.
Thanks to all for the algorithm etc. which I see is on Wikipedia. This is the version I put together based on the comments above. Note, there are problems with the versions above, for ex this 00000000000354888 returns OK for a vin. I took what was above and added an option to check year first if <1980, assume it isn't a 17digit real vin (have to), and also if there are sequences of a single character, assume invalid. This is good enough for my needs as I am comparing against values that are filled with 0s if not 17 in length. Also I know the year value so if I check that I can speed up the code by skipping the vin check (yes I could have put that in before the function) lol bye!.
<?php
/*
=======================================================================================
PURPOSE: VIN Validation (Check-digit validation is compulsory for all road vehicles sold in North America.)
DETAILS: Validates 17 digit VINs by checking their formatting
USAGE: returns boolean or returns an array with a detailed message
COMMENTS: This could be made more robust by checking the country codes etc..
MORE INFO: https://en.wikipedia.org/wiki/Vehicle_identification_number
=======================================================================================
*/
class vinValidation {
public static $transliteration = array(
'A'=>1, 'B'=>2, 'C'=>3, 'D'=>4, 'E'=>5, 'F'=>6, 'G'=>7, 'H'=>8,
'J'=>1, 'K'=>2, 'L'=>3, 'M'=>4, 'N'=>5, 'P'=>7, 'R'=>9,
'S'=>2, 'T'=>3, 'U'=>4, 'V'=>5, 'W'=>6, 'X'=>7, 'Y'=>8, 'Z'=>9,
);
public static $weights = array(8,7,6,5,4,3,2,10,0,9,8,7,6,5,4,3,2);
public function validateVIN($vin, $ret_array_status = false, $year = null) {
//validates US/NA 1980>= VINs, if before 1980, no vin standards, this returns false
if (!empty($year) && preg_match("/^[0-9]{4}/", $year)) {
if ($year < 1980) return ($ret_array_status ? array('status' => false, 'message' => 'Unable to check VIN, pre-dates 1980.') : false);
}
$vin_length = 17; // US vin requirements >= 1980
$vin = strtoupper(trim($vin));
$sum = 0;
//if (!preg_match('/^[^\Wioq]{17}$/', $vin))
//return ($ret_array_status ? array('status'=>false, 'message'=>'VIN is not valid, not the right length.') : false);
if (!preg_match('/^[A-HJ-NPR-Z0-9]{17}$/', $vin))
return ($ret_array_status ? array('status'=>false, 'message'=>'VIN is not valid, VIN formatting is incorrect.') : false);
if (preg_match('/(\w)\1{5,}/', $vin))
return ($ret_array_status ? array('status'=>false, 'message'=>'VIN contains invalid repeating character sequence.') : false);
for($x=0; $x < $vin_length; $x++) {
$char = substr($vin, $x, 1);
if(is_numeric($char)) {
$sum += $char * self::$weights[$x];
} else {
if(!isset(self::$transliteration[$char]))
return ($ret_array_status ? array('status'=>false, 'message'=>'VIN contains an invalid character.') : false);
$sum += self::$transliteration[$char] * self::$weights[$x];
}
}
$remainder = $sum % 11;
$checkdigit = $remainder == 10 ? 'X' : $remainder;
//echo " sum:".$sum." remain:".$remainder." check dig:".$checkdigit."\n";
if(substr($vin, 8, 1) != $checkdigit)
return ($ret_array_status ? array('status'=>false, 'message'=>'The VIN is not valid, failed checksum.') : false);
// all is good return true or a value and status.
return ($ret_array_status ? array('status'=>true, 'message'=>'The VIN is valid, passed checksum.') : true);
}
}
TESTING :
$vinClass = new vinValidation();
// not long enough not val
var_dump($vinClass->validateVIN('1I345678123456789', false, 2000));
var_dump($vinClass->validateVIN('1I345678123456789'));
echo "-----------------------------------------------------------\n";
// not valid
var_dump($vinClass->validateVIN('00000000012870842', true));
var_dump($vinClass->validateVIN('00000000012870842', 1968)); //assumes faulty by year
var_dump($vinClass->validateVIN('00000000012870842'));
echo "-----------------------------------------------------------\n";
// not valid
var_dump($vinClass->validateVIN('00000000000354888', true));
var_dump($vinClass->validateVIN('00000000000354888'));
echo "-----------------------------------------------------------\n";
// Fails Checksum test
var_dump($vinClass->validateVIN('368TU79MXH4763452',false,2000));
var_dump($vinClass->validateVIN('368TU79MXH4763452'));
echo "-----------------------------------------------------------\n";
// yachtzee, (returns true or array) !
var_dump($vinClass->validateVIN('WP1AF2A56GLB91679',true));
var_dump($vinClass->validateVIN('WP1AF2A56GLB91679'));
Here is the JavaScript class version as posted by Mike Q:
class VIN {
static transliteration = {
'A':1, 'B':2, 'C':3, 'D':4, 'E':5, 'F':6, 'G':7, 'H':8, 'J':1, 'K':2, 'L':3, 'M':4, 'N':5, 'P':7, 'R':9, 'S':2, 'T':3, 'U':4, 'V':5, 'W':6, 'X':7, 'Y':8, 'Z':9
}
static weights = [8,7,6,5,4,3,2,10,0,9,8,7,6,5,4,3,2];
validateVIN(vin, retArrayStatus = false, year = null) {
if (year != null && year.match(/^[0-9]{4}/)) {
if (year < 1980)
return retArrayStatus ? {'status': false, 'message': 'Unable to check VIN, pre-dates 1980.'} : false;
}
let vinLength = 17;
vin = vin.trim();
let sum = 0;
if (!vin.match(/^[A-HJ-NPR-Z0-9]{17}$/))
return retArrayStatus ? {'status': false, 'message': 'VIN is not valid, VIN formatting is incorrect [i, o, q].'} : false;
//if (!vin.match(/(\w)\1{5,}/))
// return retArrayStatus ? {'status': false, 'message': 'VIN contains invalid repeating character sequence.'} : false;
for (let x = 0; x < vinLength; x++) {
let char = vin.substr(x, 1);
if (!isNaN(char)) {
sum += char * VIN.weights[x];
}
else {
if (VIN.transliteration[char] == '')
return retArrayStatus ? {'status': false, 'message': 'VIN contains an invalid character.'} : false;
sum += VIN.transliteration[char] * VIN.weights[x];
}
}
let reminder = sum % 11;
let checkdigit = reminder == 10 ? 'X' : reminder;
if (vin.substr(8, 1) != checkdigit)
return retArrayStatus ? {'status': false, 'message': 'The VIN is not valid, failed checksum.'} : false;
return retArrayStatus ? {'status': true, 'message': 'The VIN is valid, passed checksum.'} : true;
}
}