'This file is part of bxc/benford. 'bxc/benford aka Benford Bench is free software: you can redistribute 'it and/or modify it under the terms of the GNU General Public License 'as published by the Free Software Foundation, either version 3 of the 'License, or (at your option) any later version. 'bxc/benford is distributed in the hope that it will be useful, 'but WITHOUT ANY WARRANTY; without even the implied warranty of 'MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 'GNU General Public License for more details. 'You should have received a copy of the GNU General Public License 'along with bxc/benford. If not, see . 'bxc/benford is written by Jason S. Page as part of the Benford Bench 'project, benfordbench.org in operation since 2016. Benford Bench 'was created to crowd source efforts for fraud identification and reporting 'in big data. 10,000 digits are needed to get an idea if data fits the 'Benford curve. 'This program uses OS specific operations and will only work in 'Unix/Linux. There are no plans to make this available for DOS/Windows 'however may work and compile with GNU DOS command line tools in OS/2. ' 'The Benford Bench Project as of this writing consist of the following 'volunteers: ' 'Jason Page 'Morris Chukhman 'Padraig O'Hara 'Kevin Perez 'Michael Fiedler ' 'IMPROVED VERSION - Enhanced with animated graphs, chi-squared test, 'better performance, and improved error handling dim shared n(255) as integer ' Set array to handle data lines up to 255 chars dim shared benford(9) as double ' Expected Benford percentages dim shared toke as integer dim shared animate as integer ' Animation flag dim shared animate_interval as integer ' Update interval for animation dim shared make_gif as integer ' GIF generation flag dim shared frame_count as integer ' Frame counter for GIF dim shared temp_dir as string ' Temp directory for BMP frames dim shared analysis_title as string ' Title for analysis dim shared data_source as string ' Data source description dim shared analysis_info as string ' Additional information dim shared timestamp_str as string ' Generation timestamp ' Initialize Benford's Law expected percentages for first digit benford(1) = 30.1 benford(2) = 17.6 benford(3) = 12.5 benford(4) = 9.7 benford(5) = 7.9 benford(6) = 6.7 benford(7) = 5.8 benford(8) = 5.1 benford(9) = 4.6 ' Parse command line arguments express$ = command$(-1) print express$ toke = 0 animate = 0 animate_interval = 100 ' Default: update every 100 records make_gif = 0 frame_count = 0 temp_dir = "" ' Initialize variables load1$ = "" a12$ = "" prog$ = "" col$ = "" anim$ = "" gif$ = "" title$ = "" description$ = "" source$ = "" ' Simple flag parser if instr(express$, "-h") > 0 or instr(express$, "--help") > 0 then print "==================================" print "bxc - Benford Analysis Tool" print "==================================" print "Usage: bxc -f [file] -d [1|all] -l [length] -c [column] [options]" print "" print "Required flags:" print " -f [file] Data file to analyze (or URL)" print " -d [1|all] Analyze first digit (1) or all digits (all)" print " -l [number] Sample pool length (default: 10000)" print " -c [number] Column number (0 for single column)" print "" print "Optional flags:" print " -a [interval] Enable animated graph (update every N records, default: 100)" print " -g Generate animated GIF from animation (requires -a flag)" print " -t [text] Title for the analysis" print " -s [text] Data source description" print " -i [text] Additional information/description" print " -h, --help Show this help message" print "" print "Examples:" print " bxc -f data.dat -d 1 -l 10000 -c 0" print " bxc -f data.dat -d all -l 5000 -c 2 -a 50" print " bxc -f data.csv -d 1 -l 100 -c 3 -a 50 -g -t ""Sales Data"" -s ""Q4 2024""" print "" system end if ' Extract flag values call parse_flag("-f", load1$, express$) call parse_flag("-d", a12$, express$) call parse_flag("-l", prog$, express$) call parse_flag("-c", col$, express$) call parse_flag("-a", anim$, express$) call parse_flag("-t", title$, express$) call parse_flag("-s", source$, express$) call parse_flag("-i", description$, express$) ' Check if animation is requested if len(anim$) > 0 then animate = 1 if val(anim$) > 0 then animate_interval = val(anim$) end if end if ' Check if GIF generation is requested if instr(express$, "-g") > 0 then make_gif = 1 if animate = 0 then print "Warning: -g flag requires -a flag. Enabling animation with default interval." animate = 1 end if end if ' Set metadata from flags analysis_title = title$ data_source = source$ analysis_info = description$ ' Generate timestamp shell "date +'%Y-%m-%d %H:%M:%S' > .timestamp_tmp" open ".timestamp_tmp" for input as #98 if not eof(98) then line input #98, timestamp_str close #98 shell "rm .timestamp_tmp" timestamp_str = ltrim$(rtrim$(timestamp_str)) ' Validate required flags if len(load1$) = 0 or len(a12$) = 0 or len(prog$) = 0 or len(col$) = 0 then print "Error: Missing required flags. Use -h for help." print "" print "Interactive mode:" shell "ls *.dat 2>/dev/null" input "Load file: ", load1$ input "[A]ll Digits or [1]st Digit: ", a12$ input "Capture Average Every (default: 10000) Points: ", prog$ input "Which Column Number (0 = single column): ", col$ input "Enable animation? [y/n]: ", anim_choice$ if ucase$(left$(anim_choice$, 1)) = "Y" then animate = 1 input "Update interval (default: 100): ", anim_input$ if val(anim_input$) > 0 then animate_interval = val(anim_input$) end if end if end if ' Set defaults if len(prog$) = 0 then prog$ = "10000" if len(col$) = 0 then col$ = "0" if col$ = "0" then col$ = "1" ' Create temp directory for GIF frames if needed if make_gif = 1 then dim temp_timestamp as string temp_timestamp = ltrim$(rtrim$(str$(int(timer)))) temp_dir = ".bxc_frames_" + temp_timestamp shell "mkdir -p """ + temp_dir + """" frame_count = 0 end if ' Display configuration print "" print "Configuration:" print " File: "; load1$ print " Digits: "; a12$ print " Sample size: "; prog$ print " Column: "; col$ if animate = 1 then print " Animation: Enabled (update every "; animate_interval; " records)" if make_gif = 1 then print " GIF Output: Enabled (frames stored in "; temp_dir; ")" end if else print " Animation: Disabled" end if print "" ' Handle web downloads if left$(load1$, 3) = "ftp" or left$(load1$, 3) = "htt" then print "Downloading file..." shell "wget -c " + load1$ + " 2>&1" shell "ls -w1 -t | head -n1 > .temp_filename" open ".temp_filename" for input as #11 if not eof(11) then input #11, filenam$ close #11 shell "rm .temp_filename" load1$ = filenam$ print "Downloaded: "; filenam$ end if ' Verify file exists shell "test -f " + load1$ + " && echo 1 > .file_exists || echo 0 > .file_exists" open ".file_exists" for input as #11 input #11, file_exists close #11 shell "rm .file_exists" if file_exists = 0 then print "Error: File '"; load1$; "' not found!" system end if ' Extract column if needed if val(col$) <> 0 and val(col$) <> 1 then temp_file$ = "c" + col$ + "_" + load1$ print "Extracting column "; col$; "..." shell "cut -f" + ltrim$(rtrim$(col$)) + " -d',' " + load1$ + " > " + temp_file$ + " 2>&1" load1$ = temp_file$ end if ' Initialize output files log_values$ = load1$ + "_" + a12$ + "-" + prog$ + "-.log" log_percent$ = load1$ + "_" + a12$ + "-" + prog$ + "_.log" open log_values$ for output as #21 close #21 open log_percent$ for output as #22 close #22 ' Initialize counters (use long to prevent overflow with large datasets) dim total_processed as long, sample_pool_size as long tc = 0: c = 0: c1 = 0: c2 = 0: c3 = 0: c4 = 0: c5 = 0: c6 = 0: c7 = 0: c8 = 0: c9 = 0 total_processed = 0 sample_pool_size = val(prog$) ' Initialize cumulative counters for animation (never reset, use long for large datasets) dim cum_c as long, cum_c1 as long, cum_c2 as long, cum_c3 as long dim cum_c4 as long, cum_c5 as long, cum_c6 as long, cum_c7 as long dim cum_c8 as long, cum_c9 as long cum_c = 0: cum_c1 = 0: cum_c2 = 0: cum_c3 = 0: cum_c4 = 0 cum_c5 = 0: cum_c6 = 0: cum_c7 = 0: cum_c8 = 0: cum_c9 = 0 print "" print "========================================================================" print "Benford X-C Forensics Digital Analysis Tool by Jason Page" print "Improved Version with Animation & Statistical Testing" print "========================================================================" print "" if animate = 0 then color 12, 14 print " Record, 1, 2, 3, 4, 5, 6, 7, 8, 9" color 7, 0 end if ' Open data file open load1$ for input as #1 ' Skip header if exists if not eof(1) then line input #1, line1$ ' Main processing loop do if not eof(1) then line input #1, ot$ toke = toke + 1 total_processed = total_processed + 1 ' Remove non-numeric characters for analysis (including minus sign for absolute value) clean_ot$ = "" for clean_i = 1 to len(ot$) clean_char$ = mid$(ot$, clean_i, 1) ' Skip minus signs to get absolute value if clean_char$ >= "0" and clean_char$ <= "9" then clean_ot$ = clean_ot$ + clean_char$ end if next clean_i if len(clean_ot$) > 0 then ot$ = clean_ot$ else ' Skip this record if no valid digits goto skip_record end if end if ' Process digits based on mode if left$(ucase$(a12$), 1) = "A" then position = len(ot$) else position = 1 end if ' Count digits for i = 1 to position n(i) = val(mid$(ot$, i, 1)) select case n(i) case 1 c = c + 1: c1 = c1 + 1 cum_c = cum_c + 1: cum_c1 = cum_c1 + 1 case 2 c = c + 1: c2 = c2 + 1 cum_c = cum_c + 1: cum_c2 = cum_c2 + 1 case 3 c = c + 1: c3 = c3 + 1 cum_c = cum_c + 1: cum_c3 = cum_c3 + 1 case 4 c = c + 1: c4 = c4 + 1 cum_c = cum_c + 1: cum_c4 = cum_c4 + 1 case 5 c = c + 1: c5 = c5 + 1 cum_c = cum_c + 1: cum_c5 = cum_c5 + 1 case 6 c = c + 1: c6 = c6 + 1 cum_c = cum_c + 1: cum_c6 = cum_c6 + 1 case 7 c = c + 1: c7 = c7 + 1 cum_c = cum_c + 1: cum_c7 = cum_c7 + 1 case 8 c = c + 1: c8 = c8 + 1 cum_c = cum_c + 1: cum_c8 = cum_c8 + 1 case 9 c = c + 1: c9 = c9 + 1 cum_c = cum_c + 1: cum_c9 = cum_c9 + 1 end select next i ' Animation update - use cumulative counters to show progression towards Benford's Law if animate = 1 and total_processed > 0 and (total_processed mod animate_interval = 0) and cum_c > 0 then call draw_animated_chart(cum_c1, cum_c2, cum_c3, cum_c4, cum_c5, cum_c6, cum_c7, cum_c8, cum_c9, cum_c, total_processed) end if ' Segment reporting if c >= val(prog$) then ' Calculate percentages p1 = int(((c1 * 100.0) / (c * 1.0)) * 100) / 100.0 p2 = int(((c2 * 100.0) / (c * 1.0)) * 100) / 100.0 p3 = int(((c3 * 100.0) / (c * 1.0)) * 100) / 100.0 p4 = int(((c4 * 100.0) / (c * 1.0)) * 100) / 100.0 p5 = int(((c5 * 100.0) / (c * 1.0)) * 100) / 100.0 p6 = int(((c6 * 100.0) / (c * 1.0)) * 100) / 100.0 p7 = int(((c7 * 100.0) / (c * 1.0)) * 100) / 100.0 p8 = int(((c8 * 100.0) / (c * 1.0)) * 100) / 100.0 p9 = int(((c9 * 100.0) / (c * 1.0)) * 100) / 100.0 ' Format output strings cc1$ = ot$ + "," + str$(toke) + "," + str$(c1) + "," + str$(c2) + "," + str$(c3) + "," + str$(c4) + "," + str$(c5) + "," + str$(c6) + "," + str$(c7) + "," + str$(c8) + "," + str$(c9) cc2$ = ot$ + "," + str$(toke) + "," + str$(p1) + "," + str$(p2) + "," + str$(p3) + "," + str$(p4) + "," + str$(p5) + "," + str$(p6) + "," + str$(p7) + "," + str$(p8) + "," + str$(p9) ' Display (non-animated mode) if animate = 0 then color 14, 12 print "#: "; cc1$ color 12, 14 print "%: "; cc2$ color 7, 0 end if ' Write to log files using native file I/O open log_values$ for append as #21 print #21, cc1$ close #21 open log_percent$ for append as #22 print #22, cc2$ close #22 ' Reset counters c = 0: c1 = 0: c2 = 0: c3 = 0: c4 = 0: c5 = 0: c6 = 0: c7 = 0: c8 = 0: c9 = 0 end if skip_record: loop until eof(1) close #1 ' Final display if animation was on if animate = 1 then print "" print "" end if print "" print "========================================================================" print "Analysis Complete" print "========================================================================" print "Total records processed: "; str$(toke) print "" ' Calculate final statistics and chi-squared test print "Calculating final statistics and chi-squared test..." call generate_final_report(log_percent$, load1$, a12$, prog$) print "" print "Output files:" print " Values log: "; log_values$ print " Percent log: "; log_percent$ ' Generate animated GIF if frames were captured if make_gif = 1 and frame_count > 0 then print "" print "========================================================================" print "Generating Animated GIF" print "========================================================================" print "Total frames captured: "; frame_count print "Creating animated GIF..." dim gif_file$, convert_cmd$ gif_file$ = load1$ + "_" + a12$ + "-" + prog$ + "_animation.gif" ' Use ImageMagick to create animated GIF from BMP frames at 0.1 seconds per frame (10 fps) ' Crop to content area (remove excess black borders) and optimize convert_cmd$ = "convert -delay 10 -loop 0 """ + temp_dir + """/frame_*.bmp -trim +repage """ + gif_file$ + """" shell convert_cmd$ print "Animated GIF saved to: "; gif_file$ print "" print "Cleaning up temporary frames..." shell "rm -rf """ + temp_dir + """" print " GIF output: "; gif_file$ end if print "" system ' ============================================================================ ' SUBROUTINES ' ============================================================================ function format2$(value as double) ' Format a double to 2 decimal places dim result as double, int_part as integer, dec_part as integer dim result_str as string, dec_str as string ' Round to 2 decimal places result = int(value * 100 + 0.5) / 100 ' Extract integer and decimal parts int_part = int(result) dec_part = int((result - int_part) * 100 + 0.5) ' Handle case where rounding causes dec_part to be 100 if dec_part >= 100 then int_part = int_part + 1 dec_part = 0 end if ' Format decimal part with leading zero if needed if dec_part < 10 then dec_str = "0" + ltrim$(rtrim$(str$(dec_part))) else dec_str = ltrim$(rtrim$(str$(dec_part))) end if ' Combine parts result_str = ltrim$(rtrim$(str$(int_part))) + "." + dec_str format2$ = result_str end function sub parse_flag(flag$, result$, cmdline$) dim ppos as integer, i as integer, next_char$ ppos = instr(cmdline$, flag$ + " ") if ppos > 0 then result$ = "" i = ppos + len(flag$) + 1 while i <= len(cmdline$) next_char$ = mid$(cmdline$, i, 1) if mid$(cmdline$, i, 2) = " -" then exit while if next_char$ <> " " or len(result$) > 0 then result$ = result$ + next_char$ end if i = i + 1 wend result$ = rtrim$(ltrim$(result$)) end if end sub sub draw_animated_chart(c1 as long, c2 as long, c3 as long, c4 as long, c5 as long, c6 as long, c7 as long, c8 as long, c9 as long, total as long, records as long) dim p1 as double, p2 as double, p3 as double, p4 as double, p5 as double dim p6 as double, p7 as double, p8 as double, p9 as double dim bar_width as integer, i as integer ' Clear screen and move cursor to top print chr$(27); "[2J"; chr$(27); "[H"; ' Calculate percentages if total > 0 then p1 = (c1 * 100.0) / total p2 = (c2 * 100.0) / total p3 = (c3 * 100.0) / total p4 = (c4 * 100.0) / total p5 = (c5 * 100.0) / total p6 = (c6 * 100.0) / total p7 = (c7 * 100.0) / total p8 = (c8 * 100.0) / total p9 = (c9 * 100.0) / total else p1 = 0: p2 = 0: p3 = 0: p4 = 0: p5 = 0: p6 = 0: p7 = 0: p8 = 0: p9 = 0 end if print "========================================================================" if len(analysis_title) > 0 then print "Title: "; analysis_title else print "Benford X-C Live Analysis - Animated View (Cumulative)" end if print "========================================================================" if len(data_source) > 0 then print "Source: "; data_source if len(analysis_info) > 0 then print "Info: "; analysis_info if len(timestamp_str) > 0 then print "Generated: "; timestamp_str if len(data_source) > 0 or len(analysis_info) > 0 or len(timestamp_str) > 0 then print "------------------------------------------------------------------------" end if print "Records processed: "; records; " | Total digits analyzed: "; total print "------------------------------------------------------------------------" print "" print "Digit Actual Expected Deviation Chart" print "----- ------- -------- --------- ---------------------------------" call print_digit_row(1, p1, benford(1)) call print_digit_row(2, p2, benford(2)) call print_digit_row(3, p3, benford(3)) call print_digit_row(4, p4, benford(4)) call print_digit_row(5, p5, benford(5)) call print_digit_row(6, p6, benford(6)) call print_digit_row(7, p7, benford(7)) call print_digit_row(8, p8, benford(8)) call print_digit_row(9, p9, benford(9)) print "========================================================================" ' Capture frame for GIF if enabled if make_gif = 1 then frame_count = frame_count + 1 call capture_frame(p1, p2, p3, p4, p5, p6, p7, p8, p9, records, total) end if end sub sub capture_frame(p1 as double, p2 as double, p3 as double, p4 as double, p5 as double, p6 as double, p7 as double, p8 as double, p9 as double, records as long, total_count as long) dim frame_file$, txt_file$, frame_num$, cmd$, i as integer dim bar_char$, bar_len as integer ' Create zero-padded frame number frame_num$ = right$("00000" + ltrim$(rtrim$(str$(frame_count))), 5) txt_file$ = temp_dir + "/frame_" + frame_num$ + ".txt" frame_file$ = temp_dir + "/frame_" + frame_num$ + ".bmp" ' Write frame content to text file open txt_file$ for output as #99 print #99, "========================================================================" if len(analysis_title) > 0 then print #99, "Title: "; analysis_title else print #99, "Benford X-C Live Analysis - Animated View (Cumulative)" end if print #99, "========================================================================" if len(data_source) > 0 then print #99, "Source: "; data_source if len(analysis_info) > 0 then print #99, "Info: "; analysis_info if len(timestamp_str) > 0 then print #99, "Generated: "; timestamp_str if len(data_source) > 0 or len(analysis_info) > 0 or len(timestamp_str) > 0 then print #99, "------------------------------------------------------------------------" end if print #99, "Records processed: "; records; " | Total digits analyzed: "; total_count print #99, "------------------------------------------------------------------------" print #99, "" print #99, "Digit Actual Expected Deviation Chart" print #99, "----- ------- -------- --------- ---------------------------------" call write_digit_row_to_file(99, 1, p1, benford(1)) call write_digit_row_to_file(99, 2, p2, benford(2)) call write_digit_row_to_file(99, 3, p3, benford(3)) call write_digit_row_to_file(99, 4, p4, benford(4)) call write_digit_row_to_file(99, 5, p5, benford(5)) call write_digit_row_to_file(99, 6, p6, benford(6)) call write_digit_row_to_file(99, 7, p7, benford(7)) call write_digit_row_to_file(99, 8, p8, benford(8)) call write_digit_row_to_file(99, 9, p9, benford(9)) print #99, "========================================================================" close #99 ' Convert text to BMP using ImageMagick with better size control ' Use label: with proper sizing to fit content exactly cmd$ = "convert -background black -fill lime -font Courier-Bold -pointsize 14 label:""$(cat " + txt_file$ + ")"" -trim +repage -bordercolor black -border 10 " + frame_file$ + " 2>&1" ' Debug: print "Converting: "; cmd$ shell cmd$ end sub sub write_digit_row_to_file(fnum as integer, digit as integer, actual as double, expected as double) dim deviation as double, bar_length as integer, bar$, i as integer dim actual_str as string, expected_str as string, deviation_str as string deviation = actual - expected bar_length = int(actual / 2) if bar_length > 50 then bar_length = 50 bar$ = "" for i = 1 to bar_length bar$ = bar$ + "#" next i ' Format strings with fixed widths for alignment actual_str = format2$(actual) + "%" expected_str = format2$(expected) + "%" if deviation >= 0 then deviation_str = "+" + format2$(deviation) + "%" else deviation_str = format2$(deviation) + "%" end if ' Right-align percentages for consistent column positions print #fnum, " "; digit; " "; space$(7 - len(actual_str)); actual_str; print #fnum, " "; space$(7 - len(expected_str)); expected_str; print #fnum, " "; space$(8 - len(deviation_str)); deviation_str; print #fnum, " "; bar$ end sub sub print_digit_row(digit as integer, actual as double, expected as double) dim deviation as double, bar_length as integer, bar$, i as integer dim actual_str as string, expected_str as string, deviation_str as string deviation = actual - expected bar_length = int(actual / 2) if bar_length > 50 then bar_length = 50 bar$ = "" for i = 1 to bar_length bar$ = bar$ + chr$(9608) next i ' Format strings with fixed widths for alignment actual_str = format2$(actual) + "%" expected_str = format2$(expected) + "%" if deviation >= 0 then deviation_str = "+" + format2$(deviation) + "%" else deviation_str = format2$(deviation) + "%" end if ' Right-align percentages for consistent column positions print " "; digit; " "; space$(7 - len(actual_str)); actual_str; print " "; space$(7 - len(expected_str)); expected_str; print " "; space$(8 - len(deviation_str)); deviation_str; print " "; bar$ end sub sub generate_final_report(percent_file$, data_file$, digit_mode$, sample$) dim chi_squared as double, expected_count as double, actual_count as double, diff as double dim chart_file$, sample_count as integer, digit as integer dim throw1 as double, throw2 as double, throw3 as double, throw4 as double, throw5 as double dim throw6 as double, throw7 as double, throw8 as double, throw9 as double dim sum1 as double, sum2 as double, sum3 as double, sum4 as double, sum5 as double dim sum6 as double, sum7 as double, sum8 as double, sum9 as double, count as integer dim avg1 as double, avg2 as double, avg3 as double, avg4 as double, avg5 as double dim avg6 as double, avg7 as double, avg8 as double, avg9 as double dim toss$, recd$, empty$ ' Calculate average percentages from log file sum1 = 0: sum2 = 0: sum3 = 0: sum4 = 0: sum5 = 0 sum6 = 0: sum7 = 0: sum8 = 0: sum9 = 0: count = 0 open percent_file$ for input as #12 do while not eof(12) if not eof(12) then input #12, toss$, recd$, throw1, throw2, throw3, throw4, throw5, throw6, throw7, throw8, throw9 sum1 = sum1 + throw1 sum2 = sum2 + throw2 sum3 = sum3 + throw3 sum4 = sum4 + throw4 sum5 = sum5 + throw5 sum6 = sum6 + throw6 sum7 = sum7 + throw7 sum8 = sum8 + throw8 sum9 = sum9 + throw9 count = count + 1 end if loop close #12 if count > 0 then avg1 = sum1 / count avg2 = sum2 / count avg3 = sum3 / count avg4 = sum4 / count avg5 = sum5 / count avg6 = sum6 / count avg7 = sum7 / count avg8 = sum8 / count avg9 = sum9 / count ' Calculate chi-squared statistic chi_squared = 0 chi_squared = chi_squared + ((avg1 - benford(1)) * (avg1 - benford(1))) / benford(1) chi_squared = chi_squared + ((avg2 - benford(2)) * (avg2 - benford(2))) / benford(2) chi_squared = chi_squared + ((avg3 - benford(3)) * (avg3 - benford(3))) / benford(3) chi_squared = chi_squared + ((avg4 - benford(4)) * (avg4 - benford(4))) / benford(4) chi_squared = chi_squared + ((avg5 - benford(5)) * (avg5 - benford(5))) / benford(5) chi_squared = chi_squared + ((avg6 - benford(6)) * (avg6 - benford(6))) / benford(6) chi_squared = chi_squared + ((avg7 - benford(7)) * (avg7 - benford(7))) / benford(7) chi_squared = chi_squared + ((avg8 - benford(8)) * (avg8 - benford(8))) / benford(8) chi_squared = chi_squared + ((avg9 - benford(9)) * (avg9 - benford(9))) / benford(9) print "Final Statistics (Averaged across all samples):" print "------------------------------------------------" print "Digit Actual Expected Deviation" print "----- ------- -------- ---------" print " 1 "; format2$(avg1); "% "; format2$(benford(1)); "% "; format2$(avg1 - benford(1)); "%" print " 2 "; format2$(avg2); "% "; format2$(benford(2)); "% "; format2$(avg2 - benford(2)); "%" print " 3 "; format2$(avg3); "% "; format2$(benford(3)); "% "; format2$(avg3 - benford(3)); "%" print " 4 "; format2$(avg4); "% "; format2$(benford(4)); "% "; format2$(avg4 - benford(4)); "%" print " 5 "; format2$(avg5); "% "; format2$(benford(5)); "% "; format2$(avg5 - benford(5)); "%" print " 6 "; format2$(avg6); "% "; format2$(benford(6)); "% "; format2$(avg6 - benford(6)); "%" print " 7 "; format2$(avg7); "% "; format2$(benford(7)); "% "; format2$(avg7 - benford(7)); "%" print " 8 "; format2$(avg8); "% "; format2$(benford(8)); "% "; format2$(avg8 - benford(8)); "%" print " 9 "; format2$(avg9); "% "; format2$(benford(9)); "% "; format2$(avg9 - benford(9)); "%" print "" print "Chi-Squared Statistic: "; str$(int(chi_squared*10000)/10000) print "" ' Interpret chi-squared (df=8 for 9 digits) if chi_squared < 15.51 then print "Result: Data FITS Benford's Law (95% confidence)" print " No significant deviation detected." else print "Result: Data DOES NOT fit Benford's Law (95% confidence)" print " Significant deviation detected - possible fraud indicator!" end if end if ' Generate ASCII chart print "" print "Generating ASCII chart..." chart_file$ = "chart_" + percent_file$ + ".txt" open percent_file$ for input as #12 open chart_file$ for output as #13 sample_count = 0 do while not eof(12) if not eof(12) then input #12, toss$, recd$, throw1, throw2, throw3, throw4, throw5, throw6, throw7, throw8, throw9 sample_count = sample_count + 1 print #13, "========================================================================" print #13, "Sample #"; sample_count; " | Record: "; recd$; " | Value: "; toss$ print #13, "------------------------------------------------------------------------" call write_chart_line(13, 1, throw1) call write_chart_line(13, 2, throw2) call write_chart_line(13, 3, throw3) call write_chart_line(13, 4, throw4) call write_chart_line(13, 5, throw5) call write_chart_line(13, 6, throw6) call write_chart_line(13, 7, throw7) call write_chart_line(13, 8, throw8) call write_chart_line(13, 9, throw9) print #13, "" end if loop close #12 close #13 print "ASCII chart saved to: "; chart_file$ print "" print "Preview (first sample):" shell "head -n 15 " + chart_file$ print "" print "Preview (last sample):" shell "tail -n 12 " + chart_file$ end sub sub write_chart_line(file_num as integer, digit as integer, percentage as double) dim bar$, bar_length as integer, i as integer bar_length = int(percentage) if bar_length > 100 then bar_length = 100 bar$ = "" for i = 1 to bar_length bar$ = bar$ + str$(digit) next i print #file_num, " "; digit; " | "; format2$(percentage); "% | "; bar$ end sub