Type an IPv4 or IPv6 address to get its decimal integer, or type an integer to get the address back. Paste a whole column at once if you are migrating a database. The arithmetic appears underneath so you can see where the number comes from — and below that, the reason anyone needs this: databases store IP addresses as integers because text columns sort them wrong and index them slowly.
Where the number comes from
Paste a column of addresses or a column of integers — mixed is fine, each line is worked out on its own. This is the shape you want when migrating a database column.
| Input | Converted | Type |
|---|
Quick Answer: How Do You Convert an IP to Decimal?
Multiply the four octets by 16,777,216 · 65,536 · 256 · 1 and add them up. 192.168.1.1 comes to 3,232,235,777. In MySQL that is INET_ATON(); in PHP, ip2long(); in Python, int(ipaddress.IPv4Address(ip)).
Robert Harrison
OSINT & Network Utility Expert
Robert works on network diagnostics and the practical end of IP tooling — the conversions, lookups and calculations that sit underneath everything else.
The conversion itself is four multiplications, and if that were all there was to it this page would not be worth writing. What makes it worth knowing is the moment it usually gets discovered: somebody sorts an IP address column and gets 10.0.0.10 above 10.0.0.2, or writes a range query that quietly misses half its rows. Both come from storing an address as text. Both go away when you store the integer instead.
Last reviewed 24 August 2026 · Runs entirely in your browser · Nothing you type is sent anywhere
View all articles by Robert HarrisonBecause a dotted-quad address was never really four numbers separated by dots. It is one 32-bit number, and the dots are a convenience for humans reading it. Storing it as text keeps the convenience and throws away everything the computer could have done with it.
Three things go wrong when an IP address lives in a VARCHAR column, and the third is the one that bites hardest.
Text sorting compares character by character, so 1 comes before 9 and that is the end of the comparison. Take six real addresses and order them both ways:
| ORDER BY a text column | ORDER BY the integer | Integer value |
|---|---|---|
| 10.0.0.10 | 2.2.2.2 | 33,686,018 |
| 10.0.0.2 | 9.255.255.254 | 167,772,158 |
| 100.64.0.1 | 10.0.0.2 | 167,772,162 |
| 192.168.1.1 | 10.0.0.10 | 167,772,170 |
| 2.2.2.2 | 100.64.0.1 | 1,681,915,905 |
| 9.255.255.254 | 192.168.1.1 | 3,232,235,777 |
Not one row lands in the same place. 2.2.2.2 sorts fifth in the text column and first in the integer column.
Anyone who has exported addresses to a spreadsheet and wondered why the list looked scrambled has already met this. The same rule applies there, because a spreadsheet cell holding 10.0.0.2 is text too. Converting the column to integers first — the list tab above will do a thousand rows at once — makes the sort come out right, in a spreadsheet just as much as in a database.
This is the expensive one. A range of addresses is contiguous as integers, so as integers you can ask for it directly:
Against a text column there is no equivalent. BETWEEN on strings sweeps in anything that happens to sort between those two pieces of text and misses addresses that should be included. The usual workaround is LIKE '192.168.%', which only works for boundaries that fall on a dot, cannot express a real CIDR block, and cannot use an index efficiently.
An INT UNSIGNED is four bytes, fixed. A VARCHAR(15) holding 255.255.255.255 is fifteen characters plus a length byte. On a logging table with tens of millions of rows, that difference shows up in the index size and in how much of the index fits in memory.
Which shows up in query time. Comparing two integers is one machine instruction; comparing two strings is a loop.
One exception worth knowing. If you are on PostgreSQL, skip all of this. It has native inet and cidr types that store an address properly and support comparison, ordering and containment operators directly — WHERE ip << '192.168.0.0/16' just works. PostgreSQL's own documentation says to use these types rather than plain text, because they bring input validation and specialised operators with them. Converting to an integer there gains nothing and loses the type checking. This is a MySQL, SQLite and application-code problem.
Take 192.168.1.1. Each octet holds eight bits, and the four octets sit side by side in one 32-bit number — so each one is worth 256 times the one to its right.
An IPv6 address is 128 bits rather than 32, so the same place-value logic applies across eight sixteen-bit groups instead of four eight-bit ones. The arithmetic is identical; the numbers are not. 2001:db8::1 comes to 42,540,766,411,282,592,856,903,984,951,653,826,561, and the ceiling — every bit set — is 340,282,366,920,938,463,463,374,607,431,768,211,455.
The converter handles both, and computes IPv6 with BigInt because a number that size loses precision in an ordinary JavaScript number. It also writes the address back in the compressed form RFC 5952 specifies, collapsing the longest run of zero groups rather than the first one. If what you want is the address written out in full rather than as an integer, the IPv6 expansion tool is the one for that.
Going the other way is the same operation reversed. Divide by 16,777,216 and the whole part is the first octet; take the remainder and divide by 65,536 for the second; remainder again over 256 for the third; whatever is left is the fourth. The converter at the top does exactly this and shows the working as you type.
0.0.0.0 is 0. 255.255.255.255 is 4,294,967,295, which is 232 − 1 — every bit set. Every valid IPv4 address falls between those, which is the whole reason four bytes is exactly enough to hold one and why the column type is INT UNSIGNED rather than anything larger.
Use UNSIGNED, and know why. A signed 32-bit integer stops at 2,147,483,647. Any address whose first octet is above 127 converts to a bigger number than that — 192.168.1.1 is 3,232,235,777. In a signed column it overflows. It is worth taking seriously because the bug only appears on part of your data: everything in 10.x works perfectly while everything in 192.168.x quietly corrupts.
That overflow is easier to understand once you have watched it happen. A ten-digit phone number is larger than an IPv4 address can hold, so most of them wrap the moment you convert one — the phone number to IP address converter shows the wrap step by step, which is the same failure a signed column produces silently.
If you want to see the same address as bits rather than as one number, the IP to binary converter is the page for that. It is the right tool for subnet masks and bitwise work, where seeing the individual bits is the point. This page is about the single integer you store.
Every language and database worth using has this built in. You almost never need to write the multiplication yourself.
The names are short for address-to-number and number-to-address. Store with one, read with the other, and the application never sees an integer at all:
MySQL also has INET6_ATON() and INET6_NTOA(), which handle IPv4 and IPv6 both and return VARBINARY(16) rather than an integer. If your table needs to hold IPv6 as well, those are what you want instead.
Older tutorials wrap this in sprintf with a %u format. That advice comes from 32-bit PHP, where the integer type is 32 bits and signed, so any address above 127.x came back negative. On the 64-bit builds essentially everyone runs now, the PHP manual page for ip2long returns a plain positive number and the workaround is unnecessary. The === false check above is the one that still matters.
Python's standard library validates as it converts, so an invalid address raises rather than returning a falsy value you might not check. That is one fewer trap than the PHP version. It also handles IPv6 with the same call — int(ipaddress.ip_address(x)) works for either family and returns a Python integer, which has no size limit.
Do it in four steps, and do not drop anything until the last one.
UPDATE t SET ip_int = INET_ATON(ip_text)The list tab on the converter above does the same conversion outside the database, which is useful for checking a sample before you run the backfill on a table you cannot easily undo.
Storing addresses as integers is the right default, not a universal answer.
The converter will give you one — 128 bits, 39 digits, computed with BigInt. What it will not do is fit in a 32-bit column, or in a native integer in most languages. Use VARBINARY(16) with INET6_ATON(), or PostgreSQL's inet type. The number is useful for comparison and for understanding; it is not a storage plan.
Anyone querying the table by hand sees 3232235777 rather than an address. Wrap reads in INET_NTOA(), or add a view that does it, or the first person to debug a production issue at 3am will not thank you.
Vendor-specific functions like INET_ATON() are frequently unavailable through an ORM's query language, so the conversion moves into application code and has to be applied consistently everywhere.
An integer holds an address, not a network. If you need to store 192.168.0.0/16 as a unit you need two integers for the bounds, or a proper cidr type.
To turn a prefix into the addresses it covers, the CIDR calculator expands it into a range, and the subnet calculator handles the mask arithmetic that goes with it.
And if the addresses you are storing came out of an access log, the bot verifier will tell you which of them are real search crawlers before you build a rule around them.
Multiply each octet by its place value and add them: the first octet by 16,777,216, the second by 65,536, the third by 256, and the last by 1. So 192.168.1.1 becomes 3,221,225,472 + 11,010,048 + 256 + 1 = 3,232,235,777. The converter above does it as you type, and the worked example below shows every step.
2130706433. It is the most recognisable IP integer there is, because localhost turns up in test fixtures and example code everywhere. If you see 2130706433 in a database column or a log field, it is 127.0.0.1.
Three reasons, and size is the least interesting one. An INT UNSIGNED column is a fixed four bytes against up to sixteen for VARCHAR(15). It indexes and compares faster because integer comparison is a single machine operation. And it sorts correctly — a text column puts 10.0.0.10 before 10.0.0.2, which is wrong and surprises people at exactly the wrong moment.
MySQL’s built-in converters. INET_ATON() turns a dotted-quad string into an integer — the name is short for address-to-number. INET_NTOA() reverses it. They are the reason you rarely need to do this conversion in application code when you are on MySQL: INSERT ... VALUES (INET_ATON('192.168.1.1')) and SELECT INET_NTOA(ip) handle both directions.
Because a signed 32-bit integer stops at 2,147,483,647, and any address whose first octet is above 127 converts to a larger number than that. 192.168.1.1 is 3,232,235,777. Put that in a signed column and it overflows — MySQL’s own documentation recommends UNSIGNED for exactly this reason. It is a bug that only shows up on half your data, which is the worst kind.
Only on 32-bit builds, where PHP’s integer is 32 bits and signed. On the 64-bit builds almost everyone runs now it returns a plain positive number, and the sprintf('%u', ...) workaround you will see in older tutorials is no longer needed. The trap that is still live: ip2long() returns false for invalid input, and 0.0.0.0 validly converts to 0. Both are falsy, so a truthiness test rejects a real address. Compare with === false.
Yes — paste an IPv6 address and you get its integer, computed with BigInt because 128 bits will not fit in a JavaScript number. The result runs to 39 digits. Be clear about what you do with it though: an IPv6 integer will not go in an INT UNSIGNED column. Use VARBINARY(16) with MySQL’s INET6_ATON(), or PostgreSQL’s inet type.
Yes. Switch to the list tab and paste a column — addresses, integers, IPv4, IPv6, mixed together. Each line is worked out on its own and the direction is decided per line, so a column of addresses and a column of numbers both work without telling the tool which is which. Results copy as tab-separated text for pasting straight into a spreadsheet, or download as CSV.
No. PostgreSQL has native inet and cidr types that store an address properly and support comparison, ordering and containment operators directly. Its own documentation says to use them rather than plain text types. Converting to an integer there gains you nothing and loses the type checking. This conversion is mainly a MySQL, SQLite and application-layer concern.
4294967295, which is 255.255.255.255. That is 232 − 1 — every bit of a 32-bit address set to one. The smallest is 0, which is 0.0.0.0. Every valid IPv4 address falls somewhere in that range, which is exactly why four bytes is enough to hold one.
Other ways to look at the same address.
Browse the full set on the TrustMyIP tools directory.
Last updated 24 August 2026 · Converts in your browser · Nothing you type is sent anywhere