Advertisement
IPv4 and IPv6 · both directions · batch · no server

IP to Decimal Converter
and Decimal Back to IP

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

Try one:

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.

Up to 1,000 lines.
InputConvertedType

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 and Network Utility Expert, explaining the IP to decimal converter at TrustMyIP.com
Written & Verified By

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 Harrison
Advertisement

Why Would Anyone Store an IP Address as a Number?

Because 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.

It sorts in the wrong order

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.102.2.2.233,686,018
10.0.0.29.255.255.254167,772,158
100.64.0.110.0.0.2167,772,162
192.168.1.110.0.0.10167,772,170
2.2.2.2100.64.0.11,681,915,905
9.255.255.254192.168.1.13,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.

Range queries do not work

This is the expensive one. A range of addresses is contiguous as integers, so as integers you can ask for it directly:

-- everything in 192.168.0.0/16, as one indexed range scan SELECT * FROM access_log WHERE ip BETWEEN INET_ATON('192.168.0.0') AND INET_ATON('192.168.255.255');

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.

It costs more space and more index

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.

How Do You Work Out the Decimal by Hand?

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.

  1. The first octet is the top eight bits, so it is worth 224 = 16,777,216 each. 192 × 16,777,216 = 3,221,225,472
  2. The second octet is worth 216 = 65,536 each. 168 × 65,536 = 11,010,048
  3. The third octet is worth 28 = 256 each. 1 × 256 = 256
  4. The fourth octet is worth 1 each. 1 × 1 = 1
  5. Add them: 3,221,225,472 + 11,010,048 + 256 + 1 = 3,232,235,777

IPv6 works the same way, with much bigger numbers

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.

The two ends of the range

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.

How Do You Convert an IP to Decimal in Code?

Every language and database worth using has this built in. You almost never need to write the multiplication yourself.

MySQL — INET_ATON and INET_NTOA

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:

-- the column type matters more than the function CREATE TABLE access_log ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, ip INT UNSIGNED NOT NULL, -- 4 bytes, not VARCHAR(15) seen_at DATETIME NOT NULL, INDEX (ip, seen_at) ); INSERT INTO access_log (ip, seen_at) VALUES (INET_ATON('192.168.1.1'), NOW()); -- read it back as text SELECT INET_NTOA(ip) AS ip, seen_at FROM access_log; -- and the query that text columns cannot do properly SELECT COUNT(*) FROM access_log WHERE ip BETWEEN INET_ATON('10.0.0.0') AND INET_ATON('10.255.255.255');

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.

PHP — ip2long and long2ip

$n = ip2long('192.168.1.1'); // 3232235777 $ip = long2ip($n); // '192.168.1.1' // The trap. ip2long() returns false for bad input, // and 0.0.0.0 validly converts to 0. Both are falsy. if (!ip2long($ip)) { /* WRONG - rejects 0.0.0.0 */ } if (ip2long($ip) === false) { /* right */ }

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 — the ipaddress module

import ipaddress n = int(ipaddress.IPv4Address('192.168.1.1')) # 3232235777 ip = str(ipaddress.IPv4Address(n)) # '192.168.1.1' # and the reason to bother - correct sorting, in one line rows.sort(key=lambda r: int(ipaddress.IPv4Address(r['ip'])))

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.

Migrating a text column that is already live

Do it in four steps, and do not drop anything until the last one.

  1. Add the integer column alongside the old one. Nothing reads it yet.
  2. Backfill: UPDATE t SET ip_int = INET_ATON(ip_text)
  3. Check for rows that did not convert: count where the new column is null and the old one is not. It should return zero. Anything it finds is an address that failed to parse, and those are worth looking at rather than discarding — they are usually truncated values or a stray hostname.
  4. Switch the reads over, run for a while, then drop the old column.

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.

When Should You Not Store IPs as Integers?

Storing addresses as integers is the right default, not a universal answer.

An IPv6 integer has nowhere to live

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.

Raw columns are unreadable

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.

ORMs often will not help

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.

It loses the prefix

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.

Frequently asked questions about IP to decimal conversion

How do I convert an IP address to a decimal number?

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.

What is the decimal value of 127.0.0.1?

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.

Why do databases store IP addresses as integers?

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.

What are INET_ATON and INET_NTOA?

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.

Why must the column be INT UNSIGNED and not INT?

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.

Does PHP’s ip2long return negative numbers?

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.

Can I convert IPv6 to decimal too?

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.

Can I convert a whole list at once?

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.

Do I need this on PostgreSQL?

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.

What is the largest possible IP decimal value?

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.

Related IP tools

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