[{"data":1,"prerenderedAt":2090},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fmatch-excel-rounding-in-pandas":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fmatch-excel-rounding-in-pandas":2056,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fmatch-excel-rounding-in-pandas":2065,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fmatch-excel-rounding-in-pandas":2070,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fmatch-excel-rounding-in-pandas":2071},{"id":4,"title":5,"body":6,"dateModified":2022,"datePublished":2022,"description":2023,"extension":2024,"faq":2025,"keyword":2034,"kind":2035,"meta":2036,"navigation":272,"path":2044,"seo":2045,"slug":2048,"stem":2049,"topics":2050,"type":2054,"__hash__":2055},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fmatch-excel-rounding-in-pandas\u002Findex.md","Match Excel Rounding in pandas",{"type":7,"value":8,"toc":2009},"minimark",[9,42,192,197,228,231,374,380,384,387,396,434,437,441,444,624,647,801,805,816,995,998,1002,1014,1294,1322,1326,1329,1561,1564,1790,1794,1797,1888,1899,1903,1926,1930,1944,1948,1954,1960,1966,1972,1976,2005],[10,11,12,13,17,18,21,22,27,28,30,31,30,34,37,38,41],"p",{},"A finance team compares the Python report with the old workbook and finds VAT amounts a cent apart on a handful of lines. Nothing is broken; the two tools round differently. Excel's ",[14,15,16],"code",{},"ROUND"," sends exact halves away from zero. pandas, NumPy and Python's built-in ",[14,19,20],{},"round"," send them to the nearest even digit, and floating-point storage makes some values that look like halves fall slightly short. This guide, part of ",[23,24,26],"a",{"href":25},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002F","Excel Formula Equivalents in pandas",", shows the differences, gives vectorised versions of ",[14,29,16],{},", ",[14,32,33],{},"ROUNDUP",[14,35,36],{},"ROUNDDOWN"," and ",[14,39,40],{},"MROUND"," that match a spreadsheet exactly, and fixes totals that no longer add up after rounding.",[43,44,52,53,52,57,52,61,52,69,52,77,52,84,52,91,52,94,52,98,52,103,52,109,52,111,52,116,52,118,52,120,52,123,52,127,52,129,52,133,52,135,52,138,52,141,52,145,52,147,52,151,52,153,52,156,52,159,52,163,52,165,52,169,52,171,52,174,52,177,52,181,52,183,52,187,52,189],"svg",{"viewBox":45,"role":46,"ariaLabelledBy":47,"xmlns":50,"style":51},"4 0 752 242","img",[48,49],"rd-diff-t","rd-diff-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[54,55,56],"title",{"id":48},"The same values rounded by a spreadsheet and by pandas",[58,59,60],"desc",{"id":49},"Excel's ROUND sends 0.5, 2.5 and -2.5 to 1, 3 and -3, while pandas round sends them to 0, 2 and -2. Rounded to two decimals, 0.125 becomes 0.13 in Excel and 0.12 in pandas, and 1.005 becomes 1.01 in Excel but 1.00 in pandas.",[62,63],"rect",{"x":64,"y":65,"width":66,"height":67,"fill":68},"4","0","752","242","#ffffff",[62,70],{"x":71,"y":72,"width":73,"height":74,"rx":65,"fill":68,"stroke":75,"style":76},"20.0","16.0","152.2","40.0","var(--line,#cdd5e6)","stroke-width:1px",[62,78],{"x":79,"y":72,"width":80,"height":74,"rx":65,"fill":81,"stroke":82,"style":83},"172.2","283.9","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:1.2px",[85,86,90],"text",{"x":87,"y":88,"style":89},"314.2","41.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Excel ROUND",[62,92],{"x":93,"y":72,"width":80,"height":74,"rx":65,"fill":81,"stroke":82,"style":83},"456.1",[85,95,97],{"x":96,"y":88,"style":89},"598.1","pandas .round()",[62,99],{"x":71,"y":100,"width":73,"height":101,"rx":65,"fill":102,"stroke":75,"style":76},"56.0","34.0","#e7ebef",[85,104,108],{"x":105,"y":106,"style":107},"32.0","78.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start","0.5 to 0 decimals",[62,110],{"x":79,"y":100,"width":80,"height":101,"rx":65,"fill":68,"stroke":75,"style":76},[85,112,115],{"x":87,"y":113,"style":114},"77.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","1",[62,117],{"x":93,"y":100,"width":80,"height":101,"rx":65,"fill":68,"stroke":75,"style":76},[85,119,65],{"x":96,"y":113,"style":114},[62,121],{"x":71,"y":122,"width":73,"height":101,"rx":65,"fill":102,"stroke":75,"style":76},"90.0",[85,124,126],{"x":105,"y":125,"style":107},"112.0","2.5 to 0 decimals",[62,128],{"x":79,"y":122,"width":80,"height":101,"rx":65,"fill":68,"stroke":75,"style":76},[85,130,132],{"x":87,"y":131,"style":114},"111.0","3",[62,134],{"x":93,"y":122,"width":80,"height":101,"rx":65,"fill":68,"stroke":75,"style":76},[85,136,137],{"x":96,"y":131,"style":114},"2",[62,139],{"x":71,"y":140,"width":73,"height":101,"rx":65,"fill":102,"stroke":75,"style":76},"124.0",[85,142,144],{"x":105,"y":143,"style":107},"146.0","-2.5 to 0 decimals",[62,146],{"x":79,"y":140,"width":80,"height":101,"rx":65,"fill":68,"stroke":75,"style":76},[85,148,150],{"x":87,"y":149,"style":114},"145.0","-3",[62,152],{"x":93,"y":140,"width":80,"height":101,"rx":65,"fill":68,"stroke":75,"style":76},[85,154,155],{"x":96,"y":149,"style":114},"-2",[62,157],{"x":71,"y":158,"width":73,"height":101,"rx":65,"fill":102,"stroke":75,"style":76},"158.0",[85,160,162],{"x":105,"y":161,"style":107},"180.0","0.125 to 2 decimals",[62,164],{"x":79,"y":158,"width":80,"height":101,"rx":65,"fill":68,"stroke":75,"style":76},[85,166,168],{"x":87,"y":167,"style":114},"179.0","0.13",[62,170],{"x":93,"y":158,"width":80,"height":101,"rx":65,"fill":68,"stroke":75,"style":76},[85,172,173],{"x":96,"y":167,"style":114},"0.12",[62,175],{"x":71,"y":176,"width":73,"height":101,"rx":65,"fill":102,"stroke":75,"style":76},"192.0",[85,178,180],{"x":105,"y":179,"style":107},"214.0","1.005 to 2 decimals",[62,182],{"x":79,"y":176,"width":80,"height":101,"rx":65,"fill":68,"stroke":75,"style":76},[85,184,186],{"x":87,"y":185,"style":114},"213.0","1.01",[62,188],{"x":93,"y":176,"width":80,"height":101,"rx":65,"fill":68,"stroke":75,"style":76},[85,190,191],{"x":96,"y":185,"style":114},"1.00",[193,194,196],"h2",{"id":195},"prerequisites","Prerequisites",[198,199,204],"pre",{"className":200,"code":201,"language":202,"meta":203,"style":203},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas numpy openpyxl\n","bash","",[14,205,206],{"__ignoreMap":203},[207,208,211,215,219,222,225],"span",{"class":209,"line":210},"line",1,[207,212,214],{"class":213},"sMTad","pip",[207,216,218],{"class":217},"srMev"," install",[207,220,221],{"class":217}," pandas",[207,223,224],{"class":217}," numpy",[207,226,227],{"class":217}," openpyxl\n",[10,229,230],{},"The values used throughout — halves, negative halves, and two decimals that floating point cannot store exactly:",[198,232,236],{"className":233,"code":234,"language":235,"meta":203,"style":203},"language-python shiki shiki-themes github-light github-dark-high-contrast","import numpy as np\nimport pandas as pd\n\nvalues = pd.Series([0.5, 1.5, 2.5, -2.5, 2.675, 1.005, 0.125, 1234.5, -0.125])\nprint(values.round(0).tolist())\n# [0.0, 2.0, 2.0, -2.0, 3.0, 1.0, 0.0, 1234.0, -0.0]\nprint(values.round(2).tolist())\n# [0.5, 1.5, 2.5, -2.5, 2.68, 1.0, 0.12, 1234.5, -0.12]\n","python",[14,237,238,254,267,274,336,350,357,368],{"__ignoreMap":203},[207,239,240,244,248,251],{"class":209,"line":210},[207,241,243],{"class":242},"s-kum","import",[207,245,247],{"class":246},"skGVy"," numpy ",[207,249,250],{"class":242},"as",[207,252,253],{"class":246}," np\n",[207,255,257,259,262,264],{"class":209,"line":256},2,[207,258,243],{"class":242},[207,260,261],{"class":246}," pandas ",[207,263,250],{"class":242},[207,265,266],{"class":246}," pd\n",[207,268,270],{"class":209,"line":269},3,[207,271,273],{"emptyLinePlaceholder":272},true,"\n",[207,275,277,280,283,286,290,292,295,297,300,302,305,307,309,312,314,317,319,322,324,327,329,331,333],{"class":209,"line":276},4,[207,278,279],{"class":246},"values ",[207,281,282],{"class":242},"=",[207,284,285],{"class":246}," pd.Series([",[207,287,289],{"class":288},"sP0c6","0.5",[207,291,30],{"class":246},[207,293,294],{"class":288},"1.5",[207,296,30],{"class":246},[207,298,299],{"class":288},"2.5",[207,301,30],{"class":246},[207,303,304],{"class":242},"-",[207,306,299],{"class":288},[207,308,30],{"class":246},[207,310,311],{"class":288},"2.675",[207,313,30],{"class":246},[207,315,316],{"class":288},"1.005",[207,318,30],{"class":246},[207,320,321],{"class":288},"0.125",[207,323,30],{"class":246},[207,325,326],{"class":288},"1234.5",[207,328,30],{"class":246},[207,330,304],{"class":242},[207,332,321],{"class":288},[207,334,335],{"class":246},"])\n",[207,337,339,342,345,347],{"class":209,"line":338},5,[207,340,341],{"class":288},"print",[207,343,344],{"class":246},"(values.round(",[207,346,65],{"class":288},[207,348,349],{"class":246},").tolist())\n",[207,351,353],{"class":209,"line":352},6,[207,354,356],{"class":355},"s-wDw","# [0.0, 2.0, 2.0, -2.0, 3.0, 1.0, 0.0, 1234.0, -0.0]\n",[207,358,360,362,364,366],{"class":209,"line":359},7,[207,361,341],{"class":288},[207,363,344],{"class":246},[207,365,137],{"class":288},[207,367,349],{"class":246},[207,369,371],{"class":209,"line":370},8,[207,372,373],{"class":355},"# [0.5, 1.5, 2.5, -2.5, 2.68, 1.0, 0.12, 1234.5, -0.12]\n",[10,375,376,377,379],{},"The spreadsheet results for the same values, recalculated with ",[14,378,16],{},", are 1, 2, 3, -3, 3, 1, 0, 1235 and 0 for zero decimals, and 2.68, 1.01, 0.13 and -0.13 for the interesting two-decimal cases.",[193,381,383],{"id":382},"step-1-understand-the-two-differences","Step 1 — Understand the two differences",[10,385,386],{},"There are two separate effects. The first is the rule for exact halves. pandas uses round-half-to-even, so 0.5, 1.5 and 2.5 become 0, 2 and 2: half the halves go down, half go up, which avoids a statistical upward drift in large sums. Excel uses round-half-away-from-zero: every half goes up in magnitude.",[10,388,389,390,392,393,395],{},"The second is binary storage. ",[14,391,316],{}," cannot be represented exactly; the stored value is 1.00499999999999989… Any rounding rule applied to that number rounds down. Excel sidesteps this by treating numbers as having at most 15 significant digits, so to Excel the value is exactly 1.005, and it rounds up. Python's built-in ",[14,394,20],{}," shows the same storage effect even for 2.675, which pandas happens to round up:",[198,397,399],{"className":233,"code":398,"language":235,"meta":203,"style":203},"print(round(2.675, 2), values.round(2)[4])          # 2.67 2.68\n",[14,400,401],{"__ignoreMap":203},[207,402,403,405,408,410,412,414,416,418,421,423,426,428,431],{"class":209,"line":210},[207,404,341],{"class":288},[207,406,407],{"class":246},"(",[207,409,20],{"class":288},[207,411,407],{"class":246},[207,413,311],{"class":288},[207,415,30],{"class":246},[207,417,137],{"class":288},[207,419,420],{"class":246},"), values.round(",[207,422,137],{"class":288},[207,424,425],{"class":246},")[",[207,427,64],{"class":288},[207,429,430],{"class":246},"])          ",[207,432,433],{"class":355},"# 2.67 2.68\n",[10,435,436],{},"To match Excel you need to fix both: use the half-away-from-zero rule, and apply it to the value Excel thinks it has.",[193,438,440],{"id":439},"step-2-a-vectorised-round","Step 2 — A vectorised ROUND",[10,442,443],{},"Scale the absolute value, round away the floating-point dust at nine decimals, add a half and take the floor, then restore the sign:",[198,445,447],{"className":233,"code":446,"language":235,"meta":203,"style":203},"def excel_round(x, digits=0):\n    x = np.asarray(x, dtype=float)\n    factor = 10.0 ** digits\n    scaled = np.round(np.abs(x) * factor, 9)        # 100.49999999999999 -> 100.5\n    return np.sign(x) * np.floor(scaled + 0.5) \u002F factor + 0.0\n\nprint(excel_round(values, 0).tolist())\n# [1.0, 2.0, 3.0, -3.0, 3.0, 1.0, 0.0, 1235.0, 0.0]\nprint(excel_round(values, 2).tolist())\n# [0.5, 1.5, 2.5, -2.5, 2.68, 1.01, 0.13, 1234.5, -0.13]\nprint(excel_round([1234.5], -1).tolist())           # [1230.0]\n",[14,448,449,468,490,506,531,564,568,579,584,595,601],{"__ignoreMap":203},[207,450,451,454,458,461,463,465],{"class":209,"line":210},[207,452,453],{"class":242},"def",[207,455,457],{"class":456},"s_Opv"," excel_round",[207,459,460],{"class":246},"(x, digits",[207,462,282],{"class":242},[207,464,65],{"class":288},[207,466,467],{"class":246},"):\n",[207,469,470,473,475,478,482,484,487],{"class":209,"line":256},[207,471,472],{"class":246},"    x ",[207,474,282],{"class":242},[207,476,477],{"class":246}," np.asarray(x, ",[207,479,481],{"class":480},"sa561","dtype",[207,483,282],{"class":242},[207,485,486],{"class":288},"float",[207,488,489],{"class":246},")\n",[207,491,492,495,497,500,503],{"class":209,"line":269},[207,493,494],{"class":246},"    factor ",[207,496,282],{"class":242},[207,498,499],{"class":288}," 10.0",[207,501,502],{"class":242}," **",[207,504,505],{"class":246}," digits\n",[207,507,508,511,513,516,519,522,525,528],{"class":209,"line":276},[207,509,510],{"class":246},"    scaled ",[207,512,282],{"class":242},[207,514,515],{"class":246}," np.round(np.abs(x) ",[207,517,518],{"class":242},"*",[207,520,521],{"class":246}," factor, ",[207,523,524],{"class":288},"9",[207,526,527],{"class":246},")        ",[207,529,530],{"class":355},"# 100.49999999999999 -> 100.5\n",[207,532,533,536,539,541,544,547,550,553,556,559,561],{"class":209,"line":338},[207,534,535],{"class":242},"    return",[207,537,538],{"class":246}," np.sign(x) ",[207,540,518],{"class":242},[207,542,543],{"class":246}," np.floor(scaled ",[207,545,546],{"class":242},"+",[207,548,549],{"class":288}," 0.5",[207,551,552],{"class":246},") ",[207,554,555],{"class":242},"\u002F",[207,557,558],{"class":246}," factor ",[207,560,546],{"class":242},[207,562,563],{"class":288}," 0.0\n",[207,565,566],{"class":209,"line":352},[207,567,273],{"emptyLinePlaceholder":272},[207,569,570,572,575,577],{"class":209,"line":359},[207,571,341],{"class":288},[207,573,574],{"class":246},"(excel_round(values, ",[207,576,65],{"class":288},[207,578,349],{"class":246},[207,580,581],{"class":209,"line":370},[207,582,583],{"class":355},"# [1.0, 2.0, 3.0, -3.0, 3.0, 1.0, 0.0, 1235.0, 0.0]\n",[207,585,587,589,591,593],{"class":209,"line":586},9,[207,588,341],{"class":288},[207,590,574],{"class":246},[207,592,137],{"class":288},[207,594,349],{"class":246},[207,596,598],{"class":209,"line":597},10,[207,599,600],{"class":355},"# [0.5, 1.5, 2.5, -2.5, 2.68, 1.01, 0.13, 1234.5, -0.13]\n",[207,602,604,606,609,611,614,616,618,621],{"class":209,"line":603},11,[207,605,341],{"class":288},[207,607,608],{"class":246},"(excel_round([",[207,610,326],{"class":288},[207,612,613],{"class":246},"], ",[207,615,304],{"class":242},[207,617,115],{"class":288},[207,619,620],{"class":246},").tolist())           ",[207,622,623],{"class":355},"# [1230.0]\n",[10,625,626,627,630,631,634,635,638,639,642,643,646],{},"Every result matches the spreadsheet. The intermediate ",[14,628,629],{},"np.round(..., 9)"," is what handles 1.005: ",[14,632,633],{},"1.005 * 100"," is 100.49999999999999, and rounding that to nine decimals gives 100.5, which then rounds up. Negative ",[14,636,637],{},"digits"," work as in Excel, rounding to tens, hundreds and so on. The final ",[14,640,641],{},"+ 0.0"," turns the negative zero produced by ",[14,644,645],{},"np.sign(-0.125) * 0"," into a plain zero, so it is not written to Excel as \"-0\".",[43,648,52,653,52,656,52,659,52,662,52,668,52,679,52,685,52,689,52,693,52,698,52,703,52,706,52,712,52,717,52,721,52,725,52,728,52,733,52,740,52,745,52,748,52,751,52,755,52,758,52,763,52,770,52,775,52,778,52,782,52,785,52,788,52,794,52,798],{"viewBox":649,"role":46,"ariaLabelledBy":650,"xmlns":50,"style":51},"4 0 752 142",[651,652],"rd-steps-t","rd-steps-d",[54,654,655],{"id":651},"How excel_round reproduces the spreadsheet result",[58,657,658],{"id":652},"Take the absolute value and scale it by ten to the number of digits, round the scaled value to nine decimals to remove binary storage error, add one half and take the floor, then divide back and restore the original sign.",[62,660],{"x":64,"y":65,"width":66,"height":661,"fill":68},"142",[62,663],{"x":71,"y":72,"width":664,"height":665,"rx":666,"fill":81,"stroke":82,"style":667},"115.2","110.5","12","stroke-width:2px",[669,670,672],"g",{"transform":671},"translate(65.6,29.0) scale(1.000)",[673,674],"path",{"d":675,"fill":676,"stroke":677,"style":678},"M5 9h14 M4 15h14 M10 4l-2 16 M16 4l-2 16","none","var(--brand-strong,#4338ca)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[85,680,684],{"x":681,"y":682,"style":683},"77.6","75.5","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Absolute",[85,686,688],{"x":681,"y":687,"style":683},"93.0","value",[85,690,692],{"x":681,"y":691,"style":114},"112.5","|x|",[209,694],{"x1":695,"y1":696,"x2":697,"y2":696,"stroke":82,"style":667},"140.2","71.2","162.2",[699,700],"polygon",{"points":701,"fill":702},"166.2,71.2 157.2,76.2 157.2,66.2","#5b5cf0",[62,704],{"x":705,"y":72,"width":664,"height":665,"rx":666,"fill":81,"stroke":82,"style":667},"171.2",[669,707,709],{"transform":708},"translate(216.8,37.8) scale(1.000)",[673,710],{"d":711,"fill":676,"stroke":677,"style":678},"M13 2L4 14h7l-1 8 9-12h-7z",[85,713,716],{"x":714,"y":715,"style":683},"228.8","84.2","Scale",[85,718,720],{"x":714,"y":719,"style":114},"103.8","× 10^digits",[209,722],{"x1":723,"y1":696,"x2":724,"y2":696,"stroke":82,"style":667},"291.4","313.4",[699,726],{"points":727,"fill":702},"317.4,71.2 308.4,76.2 308.4,66.2",[62,729],{"x":730,"y":72,"width":664,"height":665,"rx":666,"fill":731,"stroke":732,"style":667},"322.4","#d9f4f1","var(--teal,#0f9488)",[669,734,736],{"transform":735},"translate(368.0,29.0) scale(1.000)",[673,737],{"d":738,"fill":676,"stroke":739,"style":678},"M3 5h18l-7 8v6l-4 2v-8z","var(--teal-ink,#0b6157)",[85,741,744],{"x":742,"y":682,"style":743},"380.0","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Remove float",[85,746,747],{"x":742,"y":687,"style":743},"dust",[85,749,750],{"x":742,"y":691,"style":114},"round to 9 dp",[209,752],{"x1":753,"y1":696,"x2":754,"y2":696,"stroke":82,"style":667},"442.6","464.6",[699,756],{"points":757,"fill":702},"468.6,71.2 459.6,76.2 459.6,66.2",[62,759],{"x":760,"y":72,"width":664,"height":665,"rx":666,"fill":761,"stroke":762,"style":667},"473.6","#fdefd8","var(--gold,#b4740a)",[669,764,766],{"transform":765},"translate(519.2,37.8) scale(1.000)",[673,767],{"d":768,"fill":676,"stroke":769,"style":678},"M5 12.5l4.5 4.5L19 7","var(--gold-ink,#7a4e06)",[85,771,774],{"x":772,"y":715,"style":773},"531.2","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Half up",[85,776,777],{"x":772,"y":719,"style":114},"floor(v + 0.5)",[209,779],{"x1":780,"y1":696,"x2":781,"y2":696,"stroke":82,"style":667},"593.8","615.8",[699,783],{"points":784,"fill":702},"619.8,71.2 610.8,76.2 610.8,66.2",[62,786],{"x":787,"y":72,"width":664,"height":665,"rx":666,"fill":81,"stroke":82,"style":667},"624.8",[669,789,791],{"transform":790},"translate(670.4,37.8) scale(1.000)",[673,792],{"d":793,"fill":676,"stroke":677,"style":678},"M20 11a8 8 0 1 0-2.3 5.7 M20 4v7h-7",[85,795,797],{"x":796,"y":715,"style":683},"682.4","Scale back",[85,799,800],{"x":796,"y":719,"style":114},"restore sign",[193,802,804],{"id":803},"step-3-check-it-against-an-exact-reference","Step 3 — Check it against an exact reference",[10,806,807,808,811,812,815],{},"A vectorised trick deserves a test. Python's ",[14,809,810],{},"decimal"," module can round exactly with ",[14,813,814],{},"ROUND_HALF_UP"," (which, despite its name, rounds halves away from zero), provided each Decimal is built from the value's 15-significant-digit text — the number Excel sees:",[198,817,819],{"className":233,"code":818,"language":235,"meta":203,"style":203},"from decimal import ROUND_HALF_UP, Decimal\n\ndef decimal_round(v, digits=2):\n    return float(Decimal(format(v, \".15g\")).quantize(Decimal(1).scaleb(-digits), rounding=ROUND_HALF_UP))\n\nrng = np.random.default_rng(1)\nsample = np.round(rng.uniform(-1000, 1000, 50_000), 3)\nexact = np.array([decimal_round(v) for v in sample])\nprint(int((excel_round(sample, 2) != exact).sum()))     # 0\n",[14,820,821,837,841,857,899,903,917,948,970],{"__ignoreMap":203},[207,822,823,826,829,831,834],{"class":209,"line":210},[207,824,825],{"class":242},"from",[207,827,828],{"class":246}," decimal ",[207,830,243],{"class":242},[207,832,833],{"class":288}," ROUND_HALF_UP",[207,835,836],{"class":246},", Decimal\n",[207,838,839],{"class":209,"line":256},[207,840,273],{"emptyLinePlaceholder":272},[207,842,843,845,848,851,853,855],{"class":209,"line":269},[207,844,453],{"class":242},[207,846,847],{"class":456}," decimal_round",[207,849,850],{"class":246},"(v, digits",[207,852,282],{"class":242},[207,854,137],{"class":288},[207,856,467],{"class":246},[207,858,859,861,864,867,870,873,876,879,881,884,886,889,892,894,896],{"class":209,"line":276},[207,860,535],{"class":242},[207,862,863],{"class":288}," float",[207,865,866],{"class":246},"(Decimal(",[207,868,869],{"class":288},"format",[207,871,872],{"class":246},"(v, ",[207,874,875],{"class":217},"\".15g\"",[207,877,878],{"class":246},")).quantize(Decimal(",[207,880,115],{"class":288},[207,882,883],{"class":246},").scaleb(",[207,885,304],{"class":242},[207,887,888],{"class":246},"digits), ",[207,890,891],{"class":480},"rounding",[207,893,282],{"class":242},[207,895,814],{"class":288},[207,897,898],{"class":246},"))\n",[207,900,901],{"class":209,"line":338},[207,902,273],{"emptyLinePlaceholder":272},[207,904,905,908,910,913,915],{"class":209,"line":352},[207,906,907],{"class":246},"rng ",[207,909,282],{"class":242},[207,911,912],{"class":246}," np.random.default_rng(",[207,914,115],{"class":288},[207,916,489],{"class":246},[207,918,919,922,924,927,929,932,934,936,938,941,944,946],{"class":209,"line":359},[207,920,921],{"class":246},"sample ",[207,923,282],{"class":242},[207,925,926],{"class":246}," np.round(rng.uniform(",[207,928,304],{"class":242},[207,930,931],{"class":288},"1000",[207,933,30],{"class":246},[207,935,931],{"class":288},[207,937,30],{"class":246},[207,939,940],{"class":288},"50_000",[207,942,943],{"class":246},"), ",[207,945,132],{"class":288},[207,947,489],{"class":246},[207,949,950,953,955,958,961,964,967],{"class":209,"line":370},[207,951,952],{"class":246},"exact ",[207,954,282],{"class":242},[207,956,957],{"class":246}," np.array([decimal_round(v) ",[207,959,960],{"class":242},"for",[207,962,963],{"class":246}," v ",[207,965,966],{"class":242},"in",[207,968,969],{"class":246}," sample])\n",[207,971,972,974,976,979,982,984,986,989,992],{"class":209,"line":586},[207,973,341],{"class":288},[207,975,407],{"class":246},[207,977,978],{"class":288},"int",[207,980,981],{"class":246},"((excel_round(sample, ",[207,983,137],{"class":288},[207,985,552],{"class":246},[207,987,988],{"class":242},"!=",[207,990,991],{"class":246}," exact).sum()))     ",[207,993,994],{"class":355},"# 0\n",[10,996,997],{},"Fifty thousand random values with three decimals — about one in ten of which end in a 5, the case where the rules differ — agree completely. The Decimal version is exact but runs in a Python loop, so it is best kept as the reference in a test rather than used on large columns.",[193,999,1001],{"id":1000},"step-4-roundup-rounddown-and-mround","Step 4 — ROUNDUP, ROUNDDOWN and MROUND",[10,1003,1004,1005,1007,1008,1010,1011,1013],{},"Excel's ",[14,1006,33],{}," rounds away from zero and ",[14,1009,36],{}," towards zero, whatever the next digit is. ",[14,1012,40],{}," rounds to the nearest multiple, with halves away from zero:",[198,1015,1017],{"className":233,"code":1016,"language":235,"meta":203,"style":203},"def excel_roundup(x, digits=0):\n    x = np.asarray(x, dtype=float)\n    factor = 10.0 ** digits\n    return np.sign(x) * np.ceil(np.round(np.abs(x) * factor, 9)) \u002F factor + 0.0\n\ndef excel_rounddown(x, digits=0):\n    x = np.asarray(x, dtype=float)\n    factor = 10.0 ** digits\n    return np.sign(x) * np.floor(np.round(np.abs(x) * factor, 9)) \u002F factor + 0.0\n\ndef excel_mround(x, multiple):\n    return np.round(excel_round(np.asarray(x, dtype=float) \u002F multiple, 0) * multiple, 10)\n\nprint(excel_roundup([2.675, -0.125], 1).tolist())     # [2.7, -0.2]\nprint(excel_rounddown([2.675, -0.125], 1).tolist())   # [2.6, -0.1]\nprint(excel_mround([0.125, 2.675], 0.05).tolist())    # [0.15, 2.7]\n",[14,1018,1019,1034,1050,1062,1090,1094,1109,1125,1137,1164,1168,1178,1212,1217,1243,1269],{"__ignoreMap":203},[207,1020,1021,1023,1026,1028,1030,1032],{"class":209,"line":210},[207,1022,453],{"class":242},[207,1024,1025],{"class":456}," excel_roundup",[207,1027,460],{"class":246},[207,1029,282],{"class":242},[207,1031,65],{"class":288},[207,1033,467],{"class":246},[207,1035,1036,1038,1040,1042,1044,1046,1048],{"class":209,"line":256},[207,1037,472],{"class":246},[207,1039,282],{"class":242},[207,1041,477],{"class":246},[207,1043,481],{"class":480},[207,1045,282],{"class":242},[207,1047,486],{"class":288},[207,1049,489],{"class":246},[207,1051,1052,1054,1056,1058,1060],{"class":209,"line":269},[207,1053,494],{"class":246},[207,1055,282],{"class":242},[207,1057,499],{"class":288},[207,1059,502],{"class":242},[207,1061,505],{"class":246},[207,1063,1064,1066,1068,1070,1073,1075,1077,1079,1082,1084,1086,1088],{"class":209,"line":276},[207,1065,535],{"class":242},[207,1067,538],{"class":246},[207,1069,518],{"class":242},[207,1071,1072],{"class":246}," np.ceil(np.round(np.abs(x) ",[207,1074,518],{"class":242},[207,1076,521],{"class":246},[207,1078,524],{"class":288},[207,1080,1081],{"class":246},")) ",[207,1083,555],{"class":242},[207,1085,558],{"class":246},[207,1087,546],{"class":242},[207,1089,563],{"class":288},[207,1091,1092],{"class":209,"line":338},[207,1093,273],{"emptyLinePlaceholder":272},[207,1095,1096,1098,1101,1103,1105,1107],{"class":209,"line":352},[207,1097,453],{"class":242},[207,1099,1100],{"class":456}," excel_rounddown",[207,1102,460],{"class":246},[207,1104,282],{"class":242},[207,1106,65],{"class":288},[207,1108,467],{"class":246},[207,1110,1111,1113,1115,1117,1119,1121,1123],{"class":209,"line":359},[207,1112,472],{"class":246},[207,1114,282],{"class":242},[207,1116,477],{"class":246},[207,1118,481],{"class":480},[207,1120,282],{"class":242},[207,1122,486],{"class":288},[207,1124,489],{"class":246},[207,1126,1127,1129,1131,1133,1135],{"class":209,"line":370},[207,1128,494],{"class":246},[207,1130,282],{"class":242},[207,1132,499],{"class":288},[207,1134,502],{"class":242},[207,1136,505],{"class":246},[207,1138,1139,1141,1143,1145,1148,1150,1152,1154,1156,1158,1160,1162],{"class":209,"line":586},[207,1140,535],{"class":242},[207,1142,538],{"class":246},[207,1144,518],{"class":242},[207,1146,1147],{"class":246}," np.floor(np.round(np.abs(x) ",[207,1149,518],{"class":242},[207,1151,521],{"class":246},[207,1153,524],{"class":288},[207,1155,1081],{"class":246},[207,1157,555],{"class":242},[207,1159,558],{"class":246},[207,1161,546],{"class":242},[207,1163,563],{"class":288},[207,1165,1166],{"class":209,"line":597},[207,1167,273],{"emptyLinePlaceholder":272},[207,1169,1170,1172,1175],{"class":209,"line":603},[207,1171,453],{"class":242},[207,1173,1174],{"class":456}," excel_mround",[207,1176,1177],{"class":246},"(x, multiple):\n",[207,1179,1181,1183,1186,1188,1190,1192,1194,1196,1199,1201,1203,1205,1207,1210],{"class":209,"line":1180},12,[207,1182,535],{"class":242},[207,1184,1185],{"class":246}," np.round(excel_round(np.asarray(x, ",[207,1187,481],{"class":480},[207,1189,282],{"class":242},[207,1191,486],{"class":288},[207,1193,552],{"class":246},[207,1195,555],{"class":242},[207,1197,1198],{"class":246}," multiple, ",[207,1200,65],{"class":288},[207,1202,552],{"class":246},[207,1204,518],{"class":242},[207,1206,1198],{"class":246},[207,1208,1209],{"class":288},"10",[207,1211,489],{"class":246},[207,1213,1215],{"class":209,"line":1214},13,[207,1216,273],{"emptyLinePlaceholder":272},[207,1218,1220,1222,1225,1227,1229,1231,1233,1235,1237,1240],{"class":209,"line":1219},14,[207,1221,341],{"class":288},[207,1223,1224],{"class":246},"(excel_roundup([",[207,1226,311],{"class":288},[207,1228,30],{"class":246},[207,1230,304],{"class":242},[207,1232,321],{"class":288},[207,1234,613],{"class":246},[207,1236,115],{"class":288},[207,1238,1239],{"class":246},").tolist())     ",[207,1241,1242],{"class":355},"# [2.7, -0.2]\n",[207,1244,1246,1248,1251,1253,1255,1257,1259,1261,1263,1266],{"class":209,"line":1245},15,[207,1247,341],{"class":288},[207,1249,1250],{"class":246},"(excel_rounddown([",[207,1252,311],{"class":288},[207,1254,30],{"class":246},[207,1256,304],{"class":242},[207,1258,321],{"class":288},[207,1260,613],{"class":246},[207,1262,115],{"class":288},[207,1264,1265],{"class":246},").tolist())   ",[207,1267,1268],{"class":355},"# [2.6, -0.1]\n",[207,1270,1272,1274,1277,1279,1281,1283,1285,1288,1291],{"class":209,"line":1271},16,[207,1273,341],{"class":288},[207,1275,1276],{"class":246},"(excel_mround([",[207,1278,321],{"class":288},[207,1280,30],{"class":246},[207,1282,311],{"class":288},[207,1284,613],{"class":246},[207,1286,1287],{"class":288},"0.05",[207,1289,1290],{"class":246},").tolist())    ",[207,1292,1293],{"class":355},"# [0.15, 2.7]\n",[10,1295,1296,1297,1299,1300,1303,1304,1307,1308,1311,1312,1314,1315,1318,1319,1321],{},"Note that ",[14,1298,33],{}," is not ",[14,1301,1302],{},"np.ceil",": for negative numbers ",[14,1305,1306],{},"np.ceil(-0.12)"," goes towards zero, while ",[14,1309,1310],{},"ROUNDUP(-0.12, 1)"," gives -0.2. Working on the absolute value and restoring the sign reproduces Excel. ",[14,1313,40],{}," with a price step of 0.05 is the usual \"round to the nearest five cents\" rule; the final ",[14,1316,1317],{},"np.round(..., 10)"," removes the tiny error that multiplying by 0.05 introduces. Excel's ",[14,1320,40],{}," returns an error when the number and the multiple have different signs; this version does not check, so add a guard if negative prices can occur.",[193,1323,1325],{"id":1324},"step-5-keep-rounded-parts-adding-up-to-the-total","Step 5 — Keep rounded parts adding up to the total",[10,1327,1328],{},"Rounding each line and then summing can disagree with rounding the sum. Three lines of 10.005 each round to 10.01, summing to 30.03, while the unrounded total 30.015 rounds to 30.02. On an invoice that one-cent gap is a question from the customer. Allocate the difference to the lines with the largest remainders:",[198,1330,1332],{"className":233,"code":1331,"language":235,"meta":203,"style":203},"def allocate(amounts, digits=2):\n    amounts = np.asarray(amounts, dtype=float)\n    factor = 10 ** digits\n    target = int(excel_round(amounts.sum(), digits)[()] * factor + 0.5 * np.sign(amounts.sum()))\n    floors = np.floor(np.round(amounts * factor, 9)).astype(int)\n    shortfall = target - floors.sum()\n    order = np.argsort(-(np.round(amounts * factor, 9) - floors), kind=\"stable\")\n    floors[order[:shortfall]] += 1\n    return floors \u002F factor\n\nlines = [10.005, 10.005, 10.005]\nprint(excel_round(lines, 2).sum().round(2), allocate(lines).tolist(), allocate(lines).sum().round(2))\n# 30.03 [10.01, 10.01, 10.0] 30.02\n",[14,1333,1334,1350,1368,1381,1408,1431,1446,1484,1495,1507,1511,1535,1556],{"__ignoreMap":203},[207,1335,1336,1338,1341,1344,1346,1348],{"class":209,"line":210},[207,1337,453],{"class":242},[207,1339,1340],{"class":456}," allocate",[207,1342,1343],{"class":246},"(amounts, digits",[207,1345,282],{"class":242},[207,1347,137],{"class":288},[207,1349,467],{"class":246},[207,1351,1352,1355,1357,1360,1362,1364,1366],{"class":209,"line":256},[207,1353,1354],{"class":246},"    amounts ",[207,1356,282],{"class":242},[207,1358,1359],{"class":246}," np.asarray(amounts, ",[207,1361,481],{"class":480},[207,1363,282],{"class":242},[207,1365,486],{"class":288},[207,1367,489],{"class":246},[207,1369,1370,1372,1374,1377,1379],{"class":209,"line":269},[207,1371,494],{"class":246},[207,1373,282],{"class":242},[207,1375,1376],{"class":288}," 10",[207,1378,502],{"class":242},[207,1380,505],{"class":246},[207,1382,1383,1386,1388,1391,1394,1396,1398,1400,1402,1405],{"class":209,"line":276},[207,1384,1385],{"class":246},"    target ",[207,1387,282],{"class":242},[207,1389,1390],{"class":288}," int",[207,1392,1393],{"class":246},"(excel_round(amounts.sum(), digits)[()] ",[207,1395,518],{"class":242},[207,1397,558],{"class":246},[207,1399,546],{"class":242},[207,1401,549],{"class":288},[207,1403,1404],{"class":242}," *",[207,1406,1407],{"class":246}," np.sign(amounts.sum()))\n",[207,1409,1410,1413,1415,1418,1420,1422,1424,1427,1429],{"class":209,"line":338},[207,1411,1412],{"class":246},"    floors ",[207,1414,282],{"class":242},[207,1416,1417],{"class":246}," np.floor(np.round(amounts ",[207,1419,518],{"class":242},[207,1421,521],{"class":246},[207,1423,524],{"class":288},[207,1425,1426],{"class":246},")).astype(",[207,1428,978],{"class":288},[207,1430,489],{"class":246},[207,1432,1433,1436,1438,1441,1443],{"class":209,"line":352},[207,1434,1435],{"class":246},"    shortfall ",[207,1437,282],{"class":242},[207,1439,1440],{"class":246}," target ",[207,1442,304],{"class":242},[207,1444,1445],{"class":246}," floors.sum()\n",[207,1447,1448,1451,1453,1456,1458,1461,1463,1465,1467,1469,1471,1474,1477,1479,1482],{"class":209,"line":359},[207,1449,1450],{"class":246},"    order ",[207,1452,282],{"class":242},[207,1454,1455],{"class":246}," np.argsort(",[207,1457,304],{"class":242},[207,1459,1460],{"class":246},"(np.round(amounts ",[207,1462,518],{"class":242},[207,1464,521],{"class":246},[207,1466,524],{"class":288},[207,1468,552],{"class":246},[207,1470,304],{"class":242},[207,1472,1473],{"class":246}," floors), ",[207,1475,1476],{"class":480},"kind",[207,1478,282],{"class":242},[207,1480,1481],{"class":217},"\"stable\"",[207,1483,489],{"class":246},[207,1485,1486,1489,1492],{"class":209,"line":370},[207,1487,1488],{"class":246},"    floors[order[:shortfall]] ",[207,1490,1491],{"class":242},"+=",[207,1493,1494],{"class":288}," 1\n",[207,1496,1497,1499,1502,1504],{"class":209,"line":586},[207,1498,535],{"class":242},[207,1500,1501],{"class":246}," floors ",[207,1503,555],{"class":242},[207,1505,1506],{"class":246}," factor\n",[207,1508,1509],{"class":209,"line":597},[207,1510,273],{"emptyLinePlaceholder":272},[207,1512,1513,1516,1518,1521,1524,1526,1528,1530,1532],{"class":209,"line":603},[207,1514,1515],{"class":246},"lines ",[207,1517,282],{"class":242},[207,1519,1520],{"class":246}," [",[207,1522,1523],{"class":288},"10.005",[207,1525,30],{"class":246},[207,1527,1523],{"class":288},[207,1529,30],{"class":246},[207,1531,1523],{"class":288},[207,1533,1534],{"class":246},"]\n",[207,1536,1537,1539,1542,1544,1547,1549,1552,1554],{"class":209,"line":1180},[207,1538,341],{"class":288},[207,1540,1541],{"class":246},"(excel_round(lines, ",[207,1543,137],{"class":288},[207,1545,1546],{"class":246},").sum().round(",[207,1548,137],{"class":288},[207,1550,1551],{"class":246},"), allocate(lines).tolist(), allocate(lines).sum().round(",[207,1553,137],{"class":288},[207,1555,898],{"class":246},[207,1557,1558],{"class":209,"line":1214},[207,1559,1560],{"class":355},"# 30.03 [10.01, 10.01, 10.0] 30.02\n",[10,1562,1563],{},"Each line is rounded down to the cent, and the missing cents go one at a time to the lines that lost the most. The parts now add up to the correctly rounded total. This version assumes positive amounts, which covers invoice lines and cost allocations; handle credits separately.",[43,1565,52,1571,52,1574,52,1577,52,1582,52,1586,52,1590,52,1596,52,1600,52,1604,52,1608,52,1612,52,1614,52,1617,52,1619,52,1622,52,1625,52,1627,52,1629,52,1632,52,1634,52,1637,52,1640,52,1643,52,1645,52,1647,52,1649,52,1651,52,1654,52,1657,52,1659,52,1661,52,1663,52,1665,52,1668,52,1672,52,1674,52,1677,52,1679,52,1682,52,1685,52,1688,52,1691,52,1694,52,1697,52,1699,52,1702,52,1704,52,1706,52,1708,52,1710,52,1712,52,1714,52,1716,52,1718,52,1720,52,1722,52,1724,52,1726,52,1728,52,1730,52,1732,52,1734,52,1736,52,1738,52,1740,52,1742,52,1744,52,1747,52,1749,52,1751,52,1753,52,1755,52,1757,52,1760,52,1766,52,1770,52,1775,52,1778,52,1785],{"viewBox":1566,"role":46,"ariaLabelledBy":1567,"xmlns":50,"style":1570},"132 0 495 220",[1568,1569],"rd-alloc-t","rd-alloc-d","width:100%;max-width:495px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[54,1572,1573],{"id":1568},"Allocating the lost cent so the lines match the total",[58,1575,1576],{"id":1569},"Three invoice lines of 10.005 each round individually to 10.01, adding up to 30.03, while the unrounded total of 30.015 rounds to 30.02. Largest-remainder allocation rounds every line down to 10.00 and then gives one extra cent to two of the lines, so they read 10.01, 10.01 and 10.00 and add up to 30.02.",[62,1578],{"x":1579,"y":65,"width":1580,"height":1581,"fill":68},"132","495","220",[62,1583],{"x":1584,"y":74,"width":105,"height":1585,"rx":65,"fill":102,"stroke":75,"style":76},"148.8","24.0",[62,1587],{"x":1588,"y":74,"width":1589,"height":1585,"rx":65,"fill":102,"stroke":75,"style":76},"180.8","50.4",[85,1591,1595],{"x":1592,"y":1593,"style":1594},"206.0","57.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[62,1597],{"x":1598,"y":74,"width":1599,"height":1585,"rx":65,"fill":102,"stroke":75,"style":76},"231.2","70.9",[85,1601,1603],{"x":1602,"y":1593,"style":1594},"266.6","B",[62,1605],{"x":1584,"y":1606,"width":105,"height":1607,"rx":65,"fill":102,"stroke":75,"style":76},"64.0","28.0",[85,1609,115],{"x":1610,"y":1611,"style":1594},"164.8","83.0",[62,1613],{"x":1588,"y":1606,"width":1589,"height":1607,"rx":65,"fill":68,"stroke":75,"style":76},[85,1615,209],{"x":1592,"y":1611,"style":1616},"font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:middle",[62,1618],{"x":1598,"y":1606,"width":1599,"height":1607,"rx":65,"fill":68,"stroke":75,"style":76},[85,1620,1621],{"x":1602,"y":1611,"style":1616},"amount",[62,1623],{"x":1584,"y":1624,"width":105,"height":1607,"rx":65,"fill":102,"stroke":75,"style":76},"92.0",[85,1626,137],{"x":1610,"y":131,"style":1594},[62,1628],{"x":1588,"y":1624,"width":1589,"height":1607,"rx":65,"fill":68,"stroke":75,"style":76},[85,1630,115],{"x":1592,"y":131,"style":1631},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle",[62,1633],{"x":1598,"y":1624,"width":1599,"height":1607,"rx":65,"fill":68,"stroke":75,"style":76},[85,1635,1636],{"x":1602,"y":131,"style":1631},"10.01",[62,1638],{"x":1584,"y":1639,"width":105,"height":1607,"rx":65,"fill":102,"stroke":75,"style":76},"120.0",[85,1641,132],{"x":1610,"y":1642,"style":1594},"139.0",[62,1644],{"x":1588,"y":1639,"width":1589,"height":1607,"rx":65,"fill":68,"stroke":75,"style":76},[85,1646,137],{"x":1592,"y":1642,"style":1631},[62,1648],{"x":1598,"y":1639,"width":1599,"height":1607,"rx":65,"fill":68,"stroke":75,"style":76},[85,1650,1636],{"x":1602,"y":1642,"style":1631},[62,1652],{"x":1584,"y":1653,"width":105,"height":1607,"rx":65,"fill":102,"stroke":75,"style":76},"148.0",[85,1655,64],{"x":1610,"y":1656,"style":1594},"167.0",[62,1658],{"x":1588,"y":1653,"width":1589,"height":1607,"rx":65,"fill":68,"stroke":75,"style":76},[85,1660,132],{"x":1592,"y":1656,"style":1631},[62,1662],{"x":1598,"y":1653,"width":1599,"height":1607,"rx":65,"fill":68,"stroke":75,"style":76},[85,1664,1636],{"x":1602,"y":1656,"style":1631},[62,1666],{"x":1584,"y":1667,"width":105,"height":1607,"rx":65,"fill":102,"stroke":75,"style":76},"176.0",[85,1669,1671],{"x":1610,"y":1670,"style":1594},"195.0","5",[62,1673],{"x":1588,"y":1667,"width":1589,"height":1607,"rx":65,"fill":68,"stroke":75,"style":76},[85,1675,1676],{"x":1592,"y":1670,"style":1631},"sum",[62,1678],{"x":1598,"y":1667,"width":1599,"height":1607,"rx":65,"fill":68,"stroke":75,"style":76},[85,1680,1681],{"x":1602,"y":1670,"style":1631},"30.03",[62,1683],{"x":1684,"y":74,"width":105,"height":1585,"rx":65,"fill":102,"stroke":75,"style":76},"457.9",[62,1686],{"x":1687,"y":74,"width":1589,"height":1585,"rx":65,"fill":102,"stroke":75,"style":76},"489.9",[85,1689,1595],{"x":1690,"y":1593,"style":1594},"515.1",[62,1692],{"x":1693,"y":74,"width":1599,"height":1585,"rx":65,"fill":102,"stroke":75,"style":76},"540.3",[85,1695,1603],{"x":1696,"y":1593,"style":1594},"575.8",[62,1698],{"x":1684,"y":1606,"width":105,"height":1607,"rx":65,"fill":102,"stroke":75,"style":76},[85,1700,115],{"x":1701,"y":1611,"style":1594},"473.9",[62,1703],{"x":1687,"y":1606,"width":1589,"height":1607,"rx":65,"fill":68,"stroke":75,"style":76},[85,1705,209],{"x":1690,"y":1611,"style":1616},[62,1707],{"x":1693,"y":1606,"width":1599,"height":1607,"rx":65,"fill":68,"stroke":75,"style":76},[85,1709,1621],{"x":1696,"y":1611,"style":1616},[62,1711],{"x":1684,"y":1624,"width":105,"height":1607,"rx":65,"fill":102,"stroke":75,"style":76},[85,1713,137],{"x":1701,"y":131,"style":1594},[62,1715],{"x":1687,"y":1624,"width":1589,"height":1607,"rx":65,"fill":68,"stroke":75,"style":76},[85,1717,115],{"x":1690,"y":131,"style":1631},[62,1719],{"x":1693,"y":1624,"width":1599,"height":1607,"rx":65,"fill":68,"stroke":75,"style":76},[85,1721,1636],{"x":1696,"y":131,"style":1631},[62,1723],{"x":1684,"y":1639,"width":105,"height":1607,"rx":65,"fill":102,"stroke":75,"style":76},[85,1725,132],{"x":1701,"y":1642,"style":1594},[62,1727],{"x":1687,"y":1639,"width":1589,"height":1607,"rx":65,"fill":68,"stroke":75,"style":76},[85,1729,137],{"x":1690,"y":1642,"style":1631},[62,1731],{"x":1693,"y":1639,"width":1599,"height":1607,"rx":65,"fill":68,"stroke":75,"style":76},[85,1733,1636],{"x":1696,"y":1642,"style":1631},[62,1735],{"x":1684,"y":1653,"width":105,"height":1607,"rx":65,"fill":102,"stroke":75,"style":76},[85,1737,64],{"x":1701,"y":1656,"style":1594},[62,1739],{"x":1687,"y":1653,"width":1589,"height":1607,"rx":65,"fill":68,"stroke":75,"style":76},[85,1741,132],{"x":1690,"y":1656,"style":1631},[62,1743],{"x":1693,"y":1653,"width":1599,"height":1607,"rx":65,"fill":68,"stroke":75,"style":76},[85,1745,1746],{"x":1696,"y":1656,"style":1631},"10.00",[62,1748],{"x":1684,"y":1667,"width":105,"height":1607,"rx":65,"fill":102,"stroke":75,"style":76},[85,1750,1671],{"x":1701,"y":1670,"style":1594},[62,1752],{"x":1687,"y":1667,"width":1589,"height":1607,"rx":65,"fill":68,"stroke":75,"style":76},[85,1754,1676],{"x":1690,"y":1670,"style":1631},[62,1756],{"x":1693,"y":1667,"width":1599,"height":1607,"rx":65,"fill":68,"stroke":75,"style":76},[85,1758,1759],{"x":1696,"y":1670,"style":1631},"30.02",[85,1761,1765],{"x":1762,"y":1763,"style":1764},"225.4","29.0","font-size:12.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","before",[85,1767,1769],{"x":1768,"y":1763,"style":1764},"534.6","after",[209,1771],{"x1":1772,"y1":1773,"x2":1774,"y2":1773,"stroke":82,"style":667},"312.1","122.0","443.9",[699,1776],{"points":1777,"fill":702},"447.9,122.0 438.9,127.0 438.9,117.0",[62,1779],{"x":1780,"y":1781,"width":1782,"height":1783,"rx":1784,"fill":68,"stroke":75,"style":83},"333.7","89.0","92.7","22.0","11",[85,1786,1789],{"x":742,"y":1787,"style":1788},"104.0","font-size:11.5px;font-weight:400;fill:var(--brand-strong,#4338ca);text-anchor:middle;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace","allocate()",[193,1791,1793],{"id":1792},"step-6-round-at-the-right-moment","Step 6 — Round at the right moment",[10,1795,1796],{},"Excel users often round only in the display — a number format with two decimals — while the cell keeps full precision, and later formulas use the full value. Rounding a pandas column early and then calculating with it gives different results. Decide where rounding belongs:",[198,1798,1800],{"className":233,"code":1799,"language":235,"meta":203,"style":203},"unit_price = pd.Series([0.125, 2.675])\nqty = pd.Series([1000, 400])\nline_full = excel_round(unit_price * qty, 2)\nline_rounded_price = excel_round(excel_round(unit_price, 2) * qty, 2)\nprint(line_full.tolist(), line_rounded_price.tolist())   # [125.0, 1070.0] [130.0, 1072.0]\n",[14,1801,1802,1819,1837,1856,1878],{"__ignoreMap":203},[207,1803,1804,1807,1809,1811,1813,1815,1817],{"class":209,"line":210},[207,1805,1806],{"class":246},"unit_price ",[207,1808,282],{"class":242},[207,1810,285],{"class":246},[207,1812,321],{"class":288},[207,1814,30],{"class":246},[207,1816,311],{"class":288},[207,1818,335],{"class":246},[207,1820,1821,1824,1826,1828,1830,1832,1835],{"class":209,"line":256},[207,1822,1823],{"class":246},"qty ",[207,1825,282],{"class":242},[207,1827,285],{"class":246},[207,1829,931],{"class":288},[207,1831,30],{"class":246},[207,1833,1834],{"class":288},"400",[207,1836,335],{"class":246},[207,1838,1839,1842,1844,1847,1849,1852,1854],{"class":209,"line":269},[207,1840,1841],{"class":246},"line_full ",[207,1843,282],{"class":242},[207,1845,1846],{"class":246}," excel_round(unit_price ",[207,1848,518],{"class":242},[207,1850,1851],{"class":246}," qty, ",[207,1853,137],{"class":288},[207,1855,489],{"class":246},[207,1857,1858,1861,1863,1866,1868,1870,1872,1874,1876],{"class":209,"line":276},[207,1859,1860],{"class":246},"line_rounded_price ",[207,1862,282],{"class":242},[207,1864,1865],{"class":246}," excel_round(excel_round(unit_price, ",[207,1867,137],{"class":288},[207,1869,552],{"class":246},[207,1871,518],{"class":242},[207,1873,1851],{"class":246},[207,1875,137],{"class":288},[207,1877,489],{"class":246},[207,1879,1880,1882,1885],{"class":209,"line":338},[207,1881,341],{"class":288},[207,1883,1884],{"class":246},"(line_full.tolist(), line_rounded_price.tolist())   ",[207,1886,1887],{"class":355},"# [125.0, 1070.0] [130.0, 1072.0]\n",[10,1889,1890,1891,1893,1894,1898],{},"Rounding the unit price to cents before multiplying by the quantity adds five euros to the first line and two to the second. Neither is wrong in itself — some price lists really are defined in whole cents — but the script and the workbook must make the same choice. Match the workbook: if its formulas wrap ",[14,1892,16],{}," around intermediate results, round at the same points; if it only formats cells, keep full precision and round when writing — or leave the values unrounded and set a number format, as shown in ",[23,1895,1897],{"href":1896},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fapplying-number-and-date-formats-in-excel\u002Fformat-numbers-in-thousands-and-millions-in-excel\u002F","Format Numbers in Thousands and Millions in Excel",".",[193,1900,1902],{"id":1901},"common-pitfalls","Common pitfalls",[1904,1905,1906,1914,1920],"ul",{},[1907,1908,1909,1913],"li",{},[1910,1911,1912],"strong",{},"Using Python's round in a loop."," It is half-to-even and also sees the stored binary value. Neither matches Excel.",[1907,1915,1916,1919],{},[1910,1917,1918],{},"Rounding before aggregating."," Sums of rounded values drift from rounded sums. Aggregate first unless the business rule says otherwise.",[1907,1921,1922,1925],{},[1910,1923,1924],{},"Comparing floats with ==."," After rounding, compare with a tolerance or compare integer cents.",[193,1927,1929],{"id":1928},"conclusion","Conclusion",[10,1931,1932,1933,1936,1937,30,1939,37,1941,1943],{},"pandas rounds exact halves to even and works with the stored binary value; Excel rounds halves away from zero and treats values as 15 significant digits. Reproduce Excel with a vectorised function that removes floating-point dust before applying the half-up rule, test it against ",[14,1934,1935],{},"Decimal",", use sign-aware versions of ",[14,1938,33],{},[14,1940,36],{},[14,1942,40],{},", and allocate pennies by largest remainder when rounded parts must add up to the total.",[193,1945,1947],{"id":1946},"frequently-asked-questions","Frequently asked questions",[10,1949,1950,1953],{},[1910,1951,1952],{},"Why does pandas round 2.5 to 2?","\npandas and NumPy use round-half-to-even, also called banker's rounding, so exact halves go to the nearest even digit — 0.5 to 0, 1.5 to 2 and 2.5 to 2. Excel's ROUND always rounds halves away from zero, giving 1, 2 and 3.",[10,1955,1956,1959],{},[1910,1957,1958],{},"Why does Python round 2.675 to 2.67?","\nThe float 2.675 is stored as slightly less than 2.675, so a naive rounding goes down. Excel compensates by working with 15 significant digits. Rounding the scaled value to a few decimals before applying the half-up rule reproduces Excel's 2.68.",[10,1961,1962,1965],{},[1910,1963,1964],{},"Does Decimal with ROUND_HALF_UP match Excel?","\nYes, if you build each Decimal from the value's 15-significant-digit text rather than from the float itself. It is exact but slow for large columns, so use it to test a vectorised version.",[10,1967,1968,1971],{},[1910,1969,1970],{},"Why do my rounded line items not add up to the rounded total?","\nEach line loses or gains a fraction of a cent, and those errors accumulate. Allocate the difference to the lines with the largest remainders so the parts add up to the rounded total exactly.",[193,1973,1975],{"id":1974},"related","Related",[1904,1977,1978,1984,1991,1998],{},[1907,1979,1980,1981,1983],{},"Up one level: ",[23,1982,26],{"href":25}," — Excel functions translated to pandas.",[1907,1985,1986,1990],{},[23,1987,1989],{"href":1988},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Frunning-totals-and-year-over-year-growth-in-pandas\u002F","Running Totals and Year-over-Year Growth in pandas"," — calculations where rounding order matters.",[1907,1992,1993,1997],{},[23,1994,1996],{"href":1995},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Faverageifs-maxifs-minifs-in-pandas\u002F","AVERAGEIFS, MAXIFS and MINIFS Equivalents in pandas"," — more formula translations checked against the workbook.",[1907,1999,2000,2004],{},[23,2001,2003],{"href":2002},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fconvert-excel-text-columns-to-numbers-with-pandas\u002F","Convert Excel Text Columns to Numbers with pandas"," — getting clean numbers to round in the first place.",[2006,2007,2008],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}",{"title":203,"searchDepth":256,"depth":256,"links":2010},[2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021],{"id":195,"depth":256,"text":196},{"id":382,"depth":256,"text":383},{"id":439,"depth":256,"text":440},{"id":803,"depth":256,"text":804},{"id":1000,"depth":256,"text":1001},{"id":1324,"depth":256,"text":1325},{"id":1792,"depth":256,"text":1793},{"id":1901,"depth":256,"text":1902},{"id":1928,"depth":256,"text":1929},{"id":1946,"depth":256,"text":1947},{"id":1974,"depth":256,"text":1975},"2026-09-23","pandas and Python round halves to the nearest even number, while Excel's ROUND rounds them away from zero — so 0.125 becomes 0.12 in pandas and 0.13 in Excel. Reproduce ROUND, ROUNDUP, ROUNDDOWN and MROUND exactly, and fix totals that differ by a penny.","md",[2026,2028,2030,2032],{"q":1952,"a":2027},"pandas and NumPy use round-half-to-even, also called banker's rounding, so exact halves go to the nearest even digit — 0.5 to 0, 1.5 to 2 and 2.5 to 2. Excel's ROUND always rounds halves away from zero, giving 1, 2 and 3.",{"q":1958,"a":2029},"The float 2.675 is stored as slightly less than 2.675, so a naive rounding goes down. Excel compensates by working with 15 significant digits. Rounding the scaled value to a few decimals before applying the half-up rule reproduces Excel's 2.68.",{"q":1964,"a":2031},"Yes, if you build each Decimal from the value's 15-significant-digit text rather than from the float itself. It is exact but slow for large columns, so use it to test a vectorised version.",{"q":1970,"a":2033},"Each line loses or gains a fraction of a cent, and those errors accumulate. Allocate the difference to the lines with the largest remainders so the parts add up to the rounded total exactly.","match excel rounding in pandas",null,{"breadcrumb":2037},[2038,2040,2043],{"name":2039,"item":555},"Home",{"name":2041,"item":2042},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":26,"item":25},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fmatch-excel-rounding-in-pandas",{"title":2046,"description":2047},"Match Excel ROUND in pandas (half away from zero, not banker's)","Make pandas round like Excel — why round() gives 0.12 for 0.125, vectorised ROUND, ROUNDUP, ROUNDDOWN and MROUND equivalents tested against a spreadsheet, negative zero, and allocating pennies so rounded parts match the total.","match-excel-rounding-in-pandas","advanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fmatch-excel-rounding-in-pandas\u002Findex",[2051,2052,2053],"formula-equivalents","number-formats","pandas","how-to","4tT4_HNYJMvTJ2ZKVzLCyxcYemf2_v_FvyBTLJTeJMk",[2057,2061],{"title":2058,"path":2059,"stem":2060,"children":-1},"IPMT, PPMT, EFFECT and NOMINAL Equivalents in Python","\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fipmt-ppmt-effect-and-nominal-equivalents-in-python","advanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fipmt-ppmt-effect-and-nominal-equivalents-in-python\u002Findex",{"title":2062,"path":2063,"stem":2064,"children":-1},"MEDIAN, MODE and QUARTILE Equivalents in pandas","\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fmedian-mode-and-quartile-equivalents-in-pandas","advanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fmedian-mode-and-quartile-equivalents-in-pandas\u002Findex",[2066,2068],{"path":2067,"title":26},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas",{"path":2069,"title":2041},"\u002Fadvanced-data-transformation-and-cleaning",[],[2072,2075,2078,2081,2084,2087],{"path":2073,"title":2074,"kind":2054},"\u002Fadvanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes\u002Fvlookup-equivalent-in-pandas-for-excel-files","The VLOOKUP Equivalent in pandas for Excel Files",{"path":2076,"title":2077,"kind":2054},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fextract-numbers-and-codes-from-text-with-pandas","Extract Numbers and Codes from Text with pandas",{"path":2079,"title":2080,"kind":2054},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fsplit-full-names-into-first-and-last-names-with-pandas","Split Full Names into First and Last Names with pandas",{"path":2082,"title":2083,"kind":2054},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbin-values-into-ranges-with-pandas-cut","Bin Values into Ranges Like Excel FREQUENCY with pd.cut",{"path":2085,"title":2086,"kind":2054},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fcalculate-percent-of-total-per-group-in-pandas","Calculate Percent of Total per Group in pandas",{"path":2088,"title":2089,"kind":2054},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fcount-distinct-values-per-group-in-pandas","Count Distinct Values per Group in pandas",1790972088367]