@@ -69,9 +69,46 @@ def validate_numeric_input(key, input, input_data, value, errors)
6969 # and will be validated after balancing
7070 return if input . share_group . present?
7171
72+ # Validate step alignment first - skip min/max check if step invalid
73+ step_valid = validate_step ( key , value , input_data [ :min ] , input_data [ :step ] , errors )
74+ return unless step_valid
75+
7276 validate_min_max ( key , value , input_data [ :min ] , input_data [ :max ] , errors )
7377 end
7478
79+ def validate_step ( key , value , min , step , errors )
80+ return true unless step . present? && step . positive?
81+ return true if value_on_valid_step? ( value , min , step )
82+
83+ nearest_lower = calculate_nearest_step ( value , min , step )
84+ nearest_higher = nearest_lower + step
85+ precision = decimal_places ( step )
86+
87+ errors << "Input #{ key } value #{ value } must align with step size #{ step } . " \
88+ "Nearest valid values: #{ nearest_lower . round ( precision ) } or #{ nearest_higher . round ( precision ) } "
89+
90+ false
91+ end
92+
93+ def value_on_valid_step? ( value , min , step )
94+ remainder = ( ( value - min ) % step ) . abs
95+ remainder < 0.0001 || ( step - remainder ) . abs < 0.0001
96+ end
97+
98+ def calculate_nearest_step ( value , min , step )
99+ steps_from_min = ( ( value - min ) / step ) . round
100+ min + ( steps_from_min * step )
101+ end
102+
103+ def decimal_places ( num )
104+ return 0 if num . to_i == num
105+
106+ num_str = num . to_s
107+ return 0 unless num_str . include? ( '.' )
108+
109+ num_str . split ( '.' ) [ 1 ] . gsub ( /0+$/ , '' ) . length
110+ end
111+
75112 def validate_min_max ( key , value , min , max , errors )
76113 if value < min
77114 errors << "Input #{ key } cannot be less than #{ min } "
0 commit comments