1.20.x common.inc valid_number_step($value, $step, $offset = 0.0)

Verifies that a number is a multiple of a given step.

The implementation assumes it is dealing with IEEE 754 double precision floating point numbers that are used by PHP on most systems.

Parameters

mixed $value: The numeric value that needs to be checked, integer or float.

mixed $step: The step scale factor, integer or float. Must be positive.

mixed $offset: (optional) An offset, to which the difference must be a multiple of the given step, integer or float.

Return value

bool: TRUE if no step mismatch has occured, or FALSE otherwise.

Related topics

File

includes/common.inc, line 1468
Common functions that many Backdrop modules will need to reference.

Code

function valid_number_step($value, $step, $offset = 0.0) {
  // Avoid division by zero.
  if ($step == 0) {
    return FALSE;
  }

  // Consider offset, get absolute values.
  $value = abs($value - $offset);
  $step = abs($step);

  // Value and step are the same, no need for math.
  if ($value == $step) {
    return TRUE;
  }

  // Both numbers are integers - use modulo right away.
  if ((int) $value == $value && (int) $step == $step) {
    return $value % $step == 0;
  }

  // Convert from possible exponent notation to decimal strings to get the scale
  // values. Note: sprintf also rounds values but seems to give more reliable
  // results than number_format.
  // "10" is the maximum value allowed by the number widget, so higher scale
  // values do not make much sense.
  // But as several (also unrelated) tests require a higher value, we have to
  // provide at least a scale of 13 here.
  $max_scale = 13;
  $step_string = rtrim(sprintf("%.{$max_scale}F", $step), '0');
  $value_string = rtrim(sprintf("%.{$max_scale}F", $value), '0');

  // If the actual value has more significant decimals than expected, it has a
  // higher precision than desired and is not divisible by the step.
  // But if the scale value is high, and the length of string (minus comma) is
  // beyond the php setting for precision, it's more likely that the conversion
  // to string caused a problem with rounding. It's a compromise to allow it,
  // because there's no way for php to handle it reliably.
  $step_scale = strpos(strrev($step_string), '.');
  $value_scale = strpos(strrev($value_string), '.');
  if ($value_scale > $step_scale) {
    if ($value_scale > 10 && $step_scale > 0 && strlen($value_string) - 1 > ini_get('precision')) {
      $value = round($value, $step_scale);
    }
    else {
      return FALSE;
    }
  }

  // Because of the floating point precision problem, fmod() has a high error
  // rate. We convert the floats to integers instead by multiplying powers of 10
  // and use the modulo operator.
  $step_int = intval($step * pow(10, $step_scale));
  $value_int = intval($value * pow(10, $step_scale));
  return $value_int % $step_int == 0;
}